Using a Long Press Gesture only if certain conditions are met - ios

I am trying to create a long press gesture that presents a second view controller, once the press has been held for 3 seconds. However, I only want the second view controller presented if the device is in a certain accelerometer orientation for the ENTIRE 3 seconds. That is, if the gesture is not held long enough or the device is tilted too much, the gesture is dismissed and the user must try again.
// In FirstViewController.h
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
#interface FirstViewController : UIViewController
#property (nonatomic, strong) CMMotionManager *motionManager;
#end
// In FirstViewController.m
#import "FirstViewController"
#import "SecondViewController"
#implementation motionManager;
- (void) viewDidLoad
{
[super viewDidLoad];
self.motionManager = [[CMMotionManager alloc]init];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(handleLongPress:)];
longPress.minimumPressDuration = 3.0;
[self.view addGestureRecognizer:longPress];
}
- (void) handleLongPress: (UILongPressGestureRecognizer *)sender
{
// Not sure what to do here
}
I have previously tried chunks of code in the last method but it looks obnoxious and just is not correct. Instead, I have listed several lines of code below that I know work individually, but I need assistance in making them all work together.
// Accelerometer
if ([self.motionManager isAccelerometerAvailable])
{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[self.motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData,NSError *error)
{
if (ABS(accelerometerData.acceleration.x) < 0.3 && ABS(accelerometerData.acceleration.y) < 0.30 && ABS(accelerometerData.acceleration.z) > 0.70) // Phone is flat and screen faces up
{
NSLog(#"Correct Orientation!!!");
[self.motionManager stopAccelerometerUpdates];
}
else
{
NSLog(#"Incorrect orientation!!!");
[self.motionManager stopAccelerometerUpdates];
}];
}
else
{
NSLog(#"Accelerometer is not available.");
}
// Go to second view controller
if (sender.state == UIGestureRecognizerStateBegan)
{
SecondViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
[self presentViewController:svc animated:YES completion:nil];
}
Any ideas? Or even a more general way to cancel the gesture unless a condition is met would be very helpful.

I hope the code is verbose enough for you to read, it's pretty self explanatory - I've stuck with your accelerometer code, and used the same variable names.
#import "ViewController.h"
#import <CoreMotion/CoreMotion.h>
#interface ViewController () {
NSOperationQueue *motionQueue;
NSTimer *touchTimer;
NSTimeInterval initialTimeStamp;
BOOL touchValid;
float timerPollInSeconds;
float longPressTimeRequired;
}
#property (strong, nonatomic) NSTimer *touchTimer;
#property (assign, nonatomic) NSTimeInterval initialTimeStamp;
#property (assign, nonatomic) BOOL touchValid;
#property (assign, nonatomic) float timerPollInSeconds;
#property (assign, nonatomic) float longPressTimeRequired;
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) NSOperationQueue *motionQueue;
#end
#implementation ViewController
#synthesize touchTimer = _touchTimer, initialTimeStamp, touchValid, motionQueue = _motionQueue;
#synthesize timerPollInSeconds, longPressTimeRequired, motionManager = _motionManager;
- (void)viewDidLoad
{
self.timerPollInSeconds = 0.25f;
self.longPressTimeRequired = 3.0f;
self.touchTimer = nil;
self.touchValid = NO;
self.initialTimeStamp = NSTimeIntervalSince1970;
self.motionManager = [[CMMotionManager alloc] init];
self.motionQueue = [[NSOperationQueue alloc] init];
[_motionQueue setName:#"MotionQueue"];
[_motionQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];
[super viewDidLoad];
self.view.multipleTouchEnabled = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Operations
-(void) startLongPressMonitorWithTimeStamp:(NSTimeInterval) timeStamp {
NSLog(#"Starting monitoring - %g", timeStamp);
if( self.touchTimer ) {
if( [_touchTimer isValid] ) {
[_touchTimer invalidate];
}
}
self.touchTimer = [NSTimer timerWithTimeInterval:self.timerPollInSeconds target:self selector:#selector(timerPolled:) userInfo:nil repeats:YES];
if( [_motionManager isAccelerometerAvailable] ) {
NSLog(#"Accelerometer Available");
if( ![_motionManager isAccelerometerActive] ) {
NSLog(#"Starting Accelerometer");
[_motionManager startAccelerometerUpdatesToQueue:self.motionQueue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (ABS(accelerometerData.acceleration.x) < 0.3 && ABS(accelerometerData.acceleration.y) < 0.30 && ABS(accelerometerData.acceleration.z) > 0.70) // Phone is flat and screen faces up
{
dispatch_sync(dispatch_get_main_queue(), ^{
self.touchValid = YES;
});
}
else
{
dispatch_sync(dispatch_get_main_queue(), ^{
self.touchValid = NO;
[self stopLongPressMonitoring:YES];
});
};
}];
}
else {
NSLog(#"Accelerometer already active");
}
}
else {
NSLog(#"Accelerometer not available");
}
self.initialTimeStamp = timeStamp;
self.touchValid = YES;
[_touchTimer fire];
[[NSRunLoop mainRunLoop] addTimer:self.touchTimer forMode:NSRunLoopCommonModes];
}
-(void) stopLongPressMonitoring:(BOOL) touchSuccessful {
[_motionManager stopAccelerometerUpdates];
[_touchTimer invalidate];
self.touchValid = NO;
if( touchSuccessful ) {
NSLog(#"Yes");
}
else {
NSLog(#"No");
}
}
#pragma mark - User Interaction
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//We're using the current times, interval since the touches timestamp refers to system boot up
// it is more than feasible to use this boot up time, but for simplicity, I'm just using this
NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
[self startLongPressMonitorWithTimeStamp:timestamp];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if( self.touchValid && [NSDate timeIntervalSinceReferenceDate] - self.initialTimeStamp == self.longPressTimeRequired ) {
[self stopLongPressMonitoring:YES];
}
else {
[self stopLongPressMonitoring:NO];
}
}
#pragma mark - Timer Call back
-(void) timerPolled:(NSTimer *) timer {
NSTimeInterval firedTimeStamp = [NSDate timeIntervalSinceReferenceDate];
NSLog(#"Timer polled - %g", firedTimeStamp);
if( self.touchValid ) {
NSLog(#"Time elapsed: %d", (int)(firedTimeStamp - self.initialTimeStamp));
if( firedTimeStamp - self.initialTimeStamp >= self.longPressTimeRequired ) {
NSLog(#"Required time has elapsed");
[self stopLongPressMonitoring:YES];
}
}
else {
NSLog(#"Touch invalidated");
[self stopLongPressMonitoring:NO];
}
}
#end

You may be able to do what you want by subclassing UIGestureRecognizer to make your own gesture recognizer similar to UILongPressGestureRecognizer that listens for accelerometer data in addition to presses of at least 3 second duration.

I'd probably override the onTouchesBegan, and onTouchesEnded methods, rather than using a gesture recogniser.
I'd then create a NSTimer object, an NSTimeInterval var and a BOOL in your view controller; for the sake of it, I'll call them, touchTimer, initialTouchTimeStamp, and touchValid.
for the sake of complexity, it's an assumption that the viewControllers view is not multi-touch.
Assume the repeatTime = 0.25f;
longPressTimeRequired = 3;
The timer selector would incorporate your accelerometer method, if the data inside your accelerometer method is invalid, I'd set a touchValid to false (and invalidate the timer), otherwise I'd set it to true After checking the accelerometer, I'd check if my initialTouchTimeStamp var is longPressTimeRequired or more seconds prior to [touchTimer fireDate] - repeatTime , if it is, and the touchValid is true, then I would go to my second controller.
onTouchesBegan, I'd invalidate touchTimer and create a new one that would repeat every repearTime seconds, and lasts for y seconds. Set touchValid to NO, and set initialTouchTimeStamp to touch.timestamp.
onTouchesEnded, I'd invalidate touchTimer, and check if my initialTouchTimeStamp var is longPressTimeRequired or more seconds prior to [touchTimer fireDate] - repeatTime , if it is, and the touchValid is true, then I would go to my second controller.
there are many common elements in here, and it's probably not the most elegant way of doing things, but it should work. Hope this helps.

Related

I have an issue with an UIAlertView showing repeatedly can't find the source

My problem is this, in the app when a user clicks somewhere not important an alertView is raised that's ok, I can find the call to that view, but then is showing again and again empty and I have placed breakpoint everywhere I see a call to any alert. But the ghost alert is not breaking anywhere I have no idea who is throwing it is just a sentient view.
Can you give some tips on how to pin point where is the view being called?
EDIT:
Code for the viewController:
#import <CoreLocation/CoreLocation.h>
#import "FormViewController.h"
#import "FormPageViewController.h"
#import "FormElement+UtilityMethods.h"
#import "UserBO.h"
#import "RecordBO.h"
#import "RecordAnswer.h"
#import "UserDefaultsUtilities.h"
#import "TimeTrackingUtilities.h"
#import "DxColors.h"
#import "EDQueueUtilities.h"
#import "GroupAnswerMetadata.h"
#import "RecordAnswer+UtilityMethods.h"
#import "Record+UtilityMethods.h"
#import "FormPageIndexViewController.h"
#import "ManagedObjectUtilities.h"
#import "EDQueue.h"
#import "EDQueueUtilities.h"
#import "DxAnswerObject.h"
#import "ImageAnswerMetadata.h"
#import "DateUtilities.h"
#import <ifaddrs.h>
#import "CarbonKit.h"
#define INITIAL_CONTROLLER_INDEX 0
#define FORM_RECORDS_TEMP_NAME #"<~TMP>"
#define TAG_RETURN_BUTTON 0
#define TAG_SAVE_BUTTON 1
#define TAG_SEND_BUTTON 2
typedef NS_ENUM(NSUInteger, AlertViewPurpose) {
ALERT_VIEW_FORM_NONE = 0,
ALERT_VIEW_FORM_SEND_SUCCESS = 1,
ALERT_VIEW_FORM_SEND_FAILURE = 2,
ALERT_VIEW_FORM_SAVE_PROMPT = 3,
ALERT_VIEW_FORM_FILE_NAME_PROMPT = 4,
ALERT_VIEW_FORM_ASYNC_SEND_SUCCESS = 5,
ALERT_VIEW_FORM_COULDNT_SEND = 6,
ALERT_VIEW_FORM_WANT_TO_SEND = 7,
ALERT_VIEW_FORM_SAVE_IN_CONTEXT_PROMPT = 8,
ALERT_VIEW_FORM_FILE_NAME_IN_CTXT_SAVE_PROMPT = 9,
ALERT_VIEW_FORM_REQUIRED_INTERNET_CONECTION = 10,
// Enumeration counter.
ALERT_VIEW_PURPOSE_COUNT
};
// Based on:
// Ref.: http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/
#interface FormViewController () <RecordBOProtocol, FieldElementProtocol,
CLLocationManagerDelegate, FormPageIndexProtocol,CarbonTabSwipeNavigationDelegate>
{
AlertViewPurpose _currentAlertViewPurpose;
CarbonTabSwipeNavigation *_carbonTabSwipeNavigation;
BOOL _unedited;
BOOL _formRecordNilAtStartUp;
BOOL _timestampTaken;
CLLocationManager *_locationManager;
CLLocation *_location;
NSDate *_timeSpentBaseTimestamp;
NSArray *_sortedPages;
NSUInteger _currentPageIndex;
NSString *formID;
NSArray *_pagesNames;
}
#property (weak, nonatomic) IBOutlet UILabel *lblFormTitle;
#property (weak, nonatomic) IBOutlet UIButton *btnSmallReturn;
#property (weak, nonatomic) IBOutlet UIButton *btnSmallSave;
#property (weak, nonatomic) IBOutlet UIButton *btnSmallSend;
#property (weak, nonatomic) IBOutlet UIButton *btnBigSend;
#property (weak, nonatomic) IBOutlet UIBarButtonItem *btnReturn;
#property (strong, nonatomic) IBOutlet UIButton *lblBack;
#property (strong, nonatomic) IBOutlet UIButton *lblSave;
#end
#implementation FormViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
_currentAlertViewPurpose = ALERT_VIEW_FORM_NONE;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self localizedButtons];
// Starting up location manager if form requires it.
// Ref.:
// https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instm/CLLocationManager/requestAlwaysAuthorization
if ([self.form.geolocationEnabled boolValue]) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
if ([CLLocationManager locationServicesEnabled]) {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusNotDetermined) {
// Requesting authorization.
if ([CLLocationManager instancesRespondToSelector:#selector(requestWhenInUseAuthorization)]) {
#ifdef DEBUG_MODE
NSAssert(
[[[NSBundle mainBundle] infoDictionary] valueForKey:#"NSLocationWhenInUseUsageDescription"],
#"For iOS 8 and above, your app must have a value for NSLocationWhenInUseUsageDescription in its Info.plist");
#endif // DEBUG_MODE
[_locationManager requestWhenInUseAuthorization];
}
} else if (status == kCLAuthorizationStatusAuthorizedAlways ||
status == kCLAuthorizationStatusAuthorizedWhenInUse) {
_locationManager.distanceFilter = kCLDistanceFilterNone;
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[_locationManager startUpdatingLocation];
}
}
}
self.lblFormTitle.text = self.form.name ;
// Saving whether self.formRecord was nil at beginning.
// Important for time spent tap calculations.
_formRecordNilAtStartUp = self.formRecord == nil;
[self setup];
//Take the time for counting
_timeSpentBaseTimestamp = [NSDate date];
_unedited = YES;
}
-(void)localizedButtons
{
[self.lblBack setTitle:NSLocalizedString(#"Back", #"Regresar") forState:UIControlStateNormal];
[self.lblSave setTitle:NSLocalizedString(#"Save", #"Guardar") forState:UIControlStateNormal];
[self.btnBigSend setTitle:NSLocalizedString(#"Send", #"Enviar") forState:UIControlStateNormal];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// Overriding from DxBaseViewController.
-(void)refresh
{
}
-(void)setup
{
// Obtaining sorted pages array.
_sortedPages = [[self.form.pages allObjects]
sortedArrayUsingComparator:^NSComparisonResult(Page *obj1, Page * obj2) {
return [obj1.pageNumber compare: obj2.pageNumber];
}];
//Adding toolBar
NSMutableArray *namesPages = [[NSMutableArray alloc]init];
for (Page *page in _sortedPages) {
NSString *namePage = page.name;
[namesPages addObject:namePage];
}
_pagesNames = [namesPages copy] ;
// Creating by default a record in case there's none.
if (self.formRecord == nil) {
self.formRecord = [Record createInContext:self.managedObjectContext];
// Filling in basic record information.
self.formRecord.name = FORM_RECORDS_TEMP_NAME;
self.formRecord.editable = self.form.editableRecords;
self.formRecord.dateLastSaved = self.formRecord.dateCreated = [NSDate date];
self.formRecord.syncStatusId = [NSNumber numberWithInt:SYNC_STATUS_NOT_SYNCED];
self.formRecord.user = [UserBO loggedInUser];
self.formRecord.form = self.form;
self.formRecord.formId = self.form.pkey;
self.formRecord.temporary = [NSNumber numberWithBool:YES];
self.formRecord.isBeingEdited = [NSNumber numberWithBool:YES];
// Committing record information as is. It will be removed if user doesn't
// want to save changes.
if (![Record commitChangesFromContext:self.managedObjectContext]) {
DebugLog(#"Temp form record couldn't be saved! Check!");
}
// Initializing page view controller.
_carbonTabSwipeNavigation =[[CarbonTabSwipeNavigation alloc] initWithItems:_pagesNames
delegate:self];
_carbonTabSwipeNavigation.toolbar.barTintColor = [DxColors colorWithHexRGB:NEW_FORMS_GREEN];
[_carbonTabSwipeNavigation setNormalColor:[UIColor whiteColor]];
[_carbonTabSwipeNavigation setIndicatorColor:[UIColor whiteColor]];
[_carbonTabSwipeNavigation setSelectedColor:[UIColor whiteColor]];
} else {
[self prepareControllerForEdition];
}
[_carbonTabSwipeNavigation insertIntoRootViewController:self];
self.pageViewController = _carbonTabSwipeNavigation.pageViewController;
}
- (UIViewController *)carbonTabSwipeNavigation:(CarbonTabSwipeNavigation *)carbontTabSwipeNavigation
viewControllerAtIndex:(NSUInteger)index {
_currentPageIndex = index;
// Create a new view controller and pass suitable data.
FormPageViewController *formPageViewController = [[FormPageViewController alloc] init];
formPageViewController.pageIndex = index;
formPageViewController.formPage = _sortedPages[index];
formPageViewController.managedObjectContext = self.managedObjectContext;
formPageViewController.formRecord = self.formRecord;
formPageViewController.observer = self;
formPageViewController.view.frame = CGRectMake(0,
0,
self.view.frame.size.width,
self.view.frame.size.height);
return formPageViewController;
}
#pragma mark - Button Actions (IBActions)
-(IBAction)send:(id)sender
{
_timer = [NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(isAlertViewShowing:)
userInfo:nil
repeats:YES];
[self setButtonWithTag:self.btnBigSend.tag toHighlight:NO];
// Disabling button to avoid double submissions.
self.btnBigSend.enabled = NO;
// Show alert.
[self showAreYouReadyToSubmitFormMsg];
}
... can't paste it all
For testing only:
Subclass UIAlertView i.e. #interface MyAlertView : UIAlertView
Then replace all instances of UIAlertView from MyAlertView
i.e. MyAlertView *someAlert = [[MyAlertView alloc] init.......];
Then override
-(void)show {
[super show];
//Your breakpoint here
OR
NSLog([NSThread callStackSymbols]);
}
Check your viewcontroller that has an uialertviewdelegate.
Log your alertview.delegate
Check your super class of a viewcontroller that it doesn't call uialertviewdelegate function.
If it is an UIAlertController, check viewwillappear, viewdidappear, viewwilldisappear (super class too) and find out they don't call [alertview show]
Why you take enum for alertview ? just make instance of UIAlertView where it require's to show. you can make one method in which you can pass two string parameters alertview massage and title and method shows alertview with this title and massage.
You can catch the content of your AlertView, if it has no content at all, don't present it!
To do this check the message you are passing to the method that presents the alertView.
However, I can't seem to find your method showAreYouReadyToSubmitFormMsg.

IBAction disabling not working

If the user keeps clicking on button1 one or two , progress2.progress keeps increasing/decreasing on each click and progress1.progress keeps the same value until the user stops clicking. And in case he will surely lose , if he also keeps clicking nothing happens until he stops clicking. I don't want it to be that way since I want to hide/disable the buttons as soon as it's confirmed that he's losing to fix this issue. Any way to fix that?
Here is my .m :
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (BOOL)prefersStatusBarHidden { return YES; }
- (void)viewDidLoad
{
progress1.progress=arc4random() % 11 * 0.1;
count1=0;
count2=0;
label1.hidden = NO;
gameOver.hidden = YES;
score=0;
[super viewDidLoad];
;
}
// Do any additional setup after loading the view, typically from a nib.
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)regulator{
if(timer1)
{
[timer1 invalidate];
timer1 = nil;
}
if(timer4)
{
[timer4 invalidate];
timer4 = nil;
}
timer4 =[NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:#selector(conditioner) userInfo:nil repeats:YES];
;}
-(void)conditioner {
if (fabs(progress2.progress-progress1.progress)<=0.25 )
{
score=score+1;
scorenumber.text= [NSString stringWithFormat:#"%i",score];
[self newGame];
;
} else{
stop1=YES;
stop2=YES;
gameOver.hidden=NO;
stick.hidden=YES;
bg.hidden=YES;
progress1.hidden=YES;
progress2.hidden=YES;
supply.hidden=YES;
demand.hidden=YES;
}}
-(void)newGame{
progress1.progress=arc4random() % 11 * 0.1;}
- (IBAction)start:(UIButton *)sender {
progress2.progress=arc4random() % 11 * 0.1;
if(timer4)
{
[timer4 invalidate];
timer4 = nil;
timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(regulator) userInfo:nil repeats:YES];
[self regulator];
stop1=NO;
stop2=NO;
label1.hidden=YES;
UIButton *button1 = (UIButton *)sender;
button1.enabled = NO;
UIButton *button2 = (UIButton *)sender;
button2.enabled = NO;
}
- (IBAction)button1:(UIButton *)sender {
if(stop1==YES){button12.hidden = TRUE;}
progress2.progress=progress2.progress-0.05;
;
[self regulator];
count2=0;
count1 = count1 +1;
}
- (IBAction)button2:(UIButton *)sender {
[self regulator];
progress2.progress=progress2.progress+0.05;
if(stop2==YES){button22.hidden = TRUE;}
count1 =0;
count2 = count2+1;
}
#end
and my .h:
#import <UIKit/UIKit.h>
int count1;
int count2;
int score;
void *regulator;
void *newGame;
void *conditioner;
BOOL stop1;
BOOL stop2;
void *firstLaunch;
#interface ViewController : UIViewController{
IBOutlet UILabel *scorenumber;
IBOutlet UIImageView *stick;
IBOutlet UILabel *label1;
IBOutlet UIImageView *bg;
IBOutlet UILabel *supply;
IBOutlet UILabel *demand;
IBOutlet UILabel *gameOver;
IBOutlet UIProgressView *progress1;
IBOutlet UIProgressView *progress2;
IBOutlet UIButton *button12;
IBOutlet UIButton *button22;
NSTimer *timer1;
NSTimer *timer2;
NSTimer *timer3;
NSTimer *timer4;
}
- (IBAction)button1:(UIButton *)sender;
- (IBAction)button2:(UIButton *)sender;
#end
Thanks a lot for any help or information. I edited my question with the full code to give further explanation about the issue I'm facing. Regards.
This is actually a coding issue. MVC basics.
I believe you miss some understanding of things. So I'll explain:
IBAction - It's an action sent from the view to the controller.
IBOutlet - Meant for the controller to control the view.
On your code you are getting the sender (which should be read only when coding right) and you are trying to set it up. I assume you need to define a new IBOutlet to represent the button then connect it on your storyboard and then inside this function to make it enable/disabled.
Also a good practice would be to use "TRUE" and "FALSE" and not "YES/NO".
Hope this helps.
There are couple of ways you can approach this to make sure when user touches the button then it doesn't do anything.
It wasn't clear from your question so I assume on touch down of button you call (IBAction)button1. So Try
- (IBAction)button1:(UIButton *)sender
{
if(stop1==YES)
{
//game has stopped so don't do anything
}
else
{
progress2.progress=progress2.progress-0.05;
[self regulator];
}
}
If you want to hide it so that the user can't do anything try this
- (IBAction)button1:(UIButton *)sender
{
if(stop1==YES)
{
//game has stopped so hide this button
//assuming you have connected an IBOutlet to your button1
button1.hidden = YES;
}
else
{
button1.hidden = NO;
progress2.progress=progress2.progress-0.05;
[self regulator];
}
}

Can't create two stopwatches [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
I'm trying to create an app where one of the features is the ability to time multiple things at once. I have two UILabels and two UIButtons on a view, and have code to make one label start timing when its respective button is pressed. As you can see from my code, I have two of everything:
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *display;
- (IBAction)startPressed:(id)sender;
#property (weak, nonatomic) IBOutlet UILabel *display2;
- (IBAction)startPressed2:(id)sender;
#end
#implementation ViewController {
bool start;
bool start2;
NSTimeInterval time;
NSTimeInterval time2;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.display.text = #"0:00";
self.display2.text = #"0:00";
start = false;
start2 = false;
}
-(void) update1 {
if (start == false) {
return;
}
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsedTime = currentTime - time;
int minutes = (int)(elapsedTime / 60.0);
int seconds = (int)(elapsedTime = elapsedTime - (minutes * 60));
self.display.text = [NSString stringWithFormat:#"%u:%02u", minutes, seconds];
[self performSelector:#selector(update1) withObject:self afterDelay:0.1];
}
-(void) update2 {
if (start2 == false) {
return;
}
NSTimeInterval currentTime2 = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsedTime2 = currentTime2 - time2;
int minutes2 = (int)(elapsedTime2 / 60.0);
int seconds2 = (int)(elapsedTime2 = elapsedTime2 - (minutes2 * 60));
self.display2.text = [NSString stringWithFormat:#"%u:%02u", minutes2, seconds2];
[self performSelector:#selector(update2) withObject:self afterDelay:0.1];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)startPressed:(id)sender {
if (start == false) {
start = true;
time = [NSDate timeIntervalSinceReferenceDate];
[sender setTitle:#"Stop" forState:UIControlStateNormal];
[self update1];
}else {
start = false;
[sender setTitle:#"Start" forState:UIControlStateNormal];
}
}
- (IBAction)startPressed2:(id)sender {
if (start2 == false) {
start2 = true;
time2 = [NSDate timeIntervalSinceReferenceDate];
[sender setTitle:#"Stop" forState:UIControlStateNormal];
[self update2];
}else {
start2 = false;
[sender setTitle:#"Start" forState:UIControlStateNormal];
}
}
#end
However, when I run the app, no matter which button I press, the first Label starts counting. Please help, I can't get both timers running at the same time.
Thanks!
Note: The question code has been substantially updated since this answer was first written.
Both update and update2 call the same method:
[self performSelector:#selector(update) withObject:self afterDelay:0.1];
update2 should call:
[self performSelector:#selector(update2) withObject:self afterDelay:0.1];
The best way to avoid errors like these is through good naming, update1 and update2 would tend to avoid this error. Also there is considerable code that could be factored into common menthols ensuring future changes that are common to both timers get made with one code change.
Objective-C uses BOOL with YES and NO as boolean constants not bool with true and false. It generally makes it easier to use the conventions of the system.
Naming is so important as is eliminating duplicate code. Here is a demonstration implementation:
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *display1;
#property (weak, nonatomic) IBOutlet UILabel *display2;
#property (nonatomic) NSTimeInterval time1;
#property (nonatomic) NSTimeInterval time2;
#property (nonatomic, getter = isRunning1) BOOL running1;
#property (nonatomic, getter = isRunning2) BOOL running2;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.display1.text = [self elapsedTimeInterval:0];
self.display2.text = [self elapsedTimeInterval:0];
self.running1 = NO;
self.running2 = NO;
}
-(void) update {
if (self.isRunning1) {
self.display1.text = [self elapsedTimeInterval:self.time1];
}
if (self.isRunning2) {
self.display2.text = [self elapsedTimeInterval:self.time2];
}
if (self.isRunning1 || self.isRunning2) {
[self performSelector:#selector(update) withObject:self afterDelay:0.1];
}
}
- (NSString *)elapsedTimeInterval:(NSTimeInterval)timeInterval {
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
if (timeInterval == 0) {
timeInterval = currentTime;
}
int elapsedTime = currentTime - timeInterval;
int minutes = elapsedTime / 60;
int seconds = elapsedTime % 60;
NSString *displayText = [NSString stringWithFormat:#"%u:%02u", minutes, seconds];
return displayText;
}
- (IBAction)startPressed1:(UIButton *)button {
self.running1 = !self.isRunning1;
if (self.running1 == YES) {
self.time1 = [NSDate timeIntervalSinceReferenceDate];
[self update];
}
[self setTitleOfButton:button state:self.isRunning1];
}
- (IBAction)startPressed2:(UIButton *)button {
self.running2 = !self.isRunning2;
if (self.running2 == YES) {
self.time2 = [NSDate timeIntervalSinceReferenceDate];
[self update];
}
[self setTitleOfButton:button state:self.isRunning2];
}
- (void)setTitleOfButton:(UIButton *)button state:(BOOL)state {
NSString *title = state ? #"Start" : #"Stop";
[button setTitle:title forState:UIControlStateNormal];
}
#end
Note, there are a number of poor practices in this code which I purposely did not address.
Check your xib file, more specifically, check that the connections for display1 and display2 are correct.
It is possible that when copy/pasting your label you mis connected the labels.

UIView > SKView... willMoveFromView: not being called when expected and sprites not being "updated"

Maybe someone can point me in the right direction, here, cuz I've been beating my head against a wall over this. Major problems getting my head wrapped around Sprite Kit and UIKit interoperability.
My game starts at a table view, which holds all of the player's games in separate cells (a la with Friends games). When a cell is tapped, I load an SKView, which presents an SKScene containing all relevant game data downloaded from Parse.com beforehand.
The problem is, I can't figure out how to get the scene to "update," for lack of a better term, with all the current game data. The presented scene just shows the background image and a few other images, as expected, but the sprites that should be on screen are not. Instead, it's the sprites that WERE on screen when I swiped out of the SKScene last. I can log all the passed in game data into the console, so I know there's no problem there. Also, when I perform some kind of action on the scene's sprites (removing, refreshing, etc.), it causes the scene and all its sprite to kind of "wake up," and all the sprites that were supposed to be there show up.
A second, possibly related, problem is that, when I swipe from the SKScene back to the main table view, willMoveFromView: does not get called. (This is where I do all the cleanup code for the sprites, background, buttons, etc.) It's only when I tap that same table view cell and go back into the same game scene that willMoveFromView: gets called. Shouldn't it get called upon swiping out of the SKScene/SKView? But, again, I run into the same "updating" problem where the scene is kind of frozen with the old data/sprites. Any ideas? I've tried to include all relevant code, but I don't think you'll find anything out of the ordinary. My feeling is this is more of a conceptual problem and my comments above are sufficient:
#interface MGGameMenuViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate, NSURLConnectionDataDelegate, UIAlertViewDelegate>
#property (nonatomic, retain) UITableView *tView;
#property (nonatomic, readwrite) MGSpriteKitViewController *skvc;
#end
#implementation MGGameMenuViewController
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
self.matchtoBePassedIn = [self.yourTurnArray objectAtIndex:indexPath.row];
[self launchGamePlaySceneWithMatchData:self.matchtoBePassedIn];
}
else if (indexPath.section == 1) {
self.matchtoBePassedIn.cardsDealt = [NSMutableArray arrayWithCapacity:9];
self.matchtoBePassedIn = [self.theirTurnArray objectAtIndex:indexPath.row];
[self launchGamePlaySceneWithMatchData:self.matchtoBePassedIn];
}
else {
NSLog(#"section chosen was neither 0 nor 1.");
}
}
// launch gameplay
-(void)launchGamePlaySceneWithMatchData:(MGMatch *)match {
if (self.skvc == nil) {
MGSpriteKitViewController *spriteKitVC = [[MGSpriteKitViewController alloc] initWithNibName:#"MGSpriteKitViewController" bundle:nil];
spriteKitVC.matchToBePassedFromGameMenuToGameplayScene = match;
self.skvc = spriteKitVC;
}
if (self.skvc) {
#try {
if (![self.skvc isBeingPresented]) {
self.skvc.matchToBePassedFromGameMenuToGameplayScene = match;
[self.navigationController pushViewController:self.skvc animated:YES];
}
}
#catch (NSException *exception) {
NSRange range = [exception.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
NSRange range2 = [exception.reason rangeOfString:#"Tried to pop to a view controller that doesn't exist"];
if([exception.name isEqualToString:#"NSInvalidArgumentException"] &&
range.location != NSNotFound) {
NSLog(#"[MGGameMenuViewController] NSInvalidArgumentException caught.");
if (![self.skvc isBeingPresented]) {
self.skvc.matchToBePassedFromGameMenuToGameplayScene = match;
[self.navigationController popToViewController:self.skvc animated:YES];
}
}
if ([exception.name isEqualToString:#"NSInternalInconsistencyException"] && range2.location != NSNotFound) {
if (![self.skvc isBeingPresented]) {
self.skvc.matchToBePassedFromGameMenuToGameplayScene = match;
[self.navigationController pushViewController:self.skvc animated:YES];
}
}
}
#finally {
NSLog(#"[MGGameMenuViewController] finally");
}
}
[self.navigationController.navigationBar setHidden:YES];
}
// the SKView
SKView *spriteView;
#interface MGSpriteKitViewController : UIViewController
#property (nonatomic, retain) MGMatch *matchToBePassedFromGameMenuToGameplayScene;
#property (nonatomic, retain) MGGameplayScene *gameplayScene;
#end
#implementation MGSpriteKitViewController
#synthesize matchToBePassedFromGameMenuToGameplayScene, gameplayScene;
-(void)viewDidLoad {
[super viewDidLoad];
spriteView = (SKView *)self.view;
spriteView.showsDrawCount = NO;
spriteView.showsFPS = NO;
spriteView.showsNodeCount = YES;
}
-(void)viewWillAppear:(BOOL)animated {
self.gameplayScene = [[MGGameplayScene alloc] initWithSize:CGSizeMake(320.0f, 568.0f)];
self.gameplayScene.passedInMatch = self.matchToBePassedFromGameMenuToGameplayScene;
self.gameplayScene.playerImageCache = self.playerImageCache;
[spriteView presentScene:self.gameplayScene];
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:YES];
self.gameplayScene = nil;
}
#end
// SKScene where the gameplay happens
#interface MGGameplayScene : SKScene
#property (nonatomic) contentCreated;
... various assets
#end
#implementation MGGameplayScene
-(void)didMoveToView:(SKView *)view {
if (self.contentCreated == NO) {
[self createSceneContents];
self.contentCreated = YES;
}
}
-(void)willMoveFromView:(SKView *)view {
[self removeAllChildren];
}
// for practicies
-(void)createSceneContents {
self.backgroundColor = [UIColor whiteColor];
self.scaleMode = SKSceneScaleModeAspectFit;
[self setAllAssetsToNil];
[self recreateAssetsWithRelevantData];
}
#end
In Storyboard assign the class of the uiview in MGSpriteKitViewController as skview.
The issue is that MGSpriteKitViewController is, by default, creating a UIView for its view property.
To programmatically create the view as an SKView, you have to override the loadView method of your MGSpriteKitViewController:
#implementation MGSpriteKitViewController
- (void)loadView
{
self.view = [[SKView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
}
// Rest of Code
...

UIButton press and hold - repeat action until let go

I want to have a certain method run and repeat itself for as long as someones finger is pressed down on a button. I want that method to stop repeating itself when the finger is not on the button anymore
Is there a way to check if the touchDown is still occurring during the method implementation? Help!
You can use the UIControlEventTouchDown control event to start the method running, and UIControlEventTouchUpInside, or similar, to detect when the button is no longer being "pressed".
Set up the actions to the button, e.g.:
[myButton addTarget:self action:#selector(startButtonTouch:) forControlEvents:UIControlEventTouchDown];
[myButton addTarget:self action:#selector(endButtonTouch:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
(Note the above will cause touch up inside and outside the button to invoke the endButtonTouch: method.)
Then add the startButtonTouch: and endButtonTouch methods, e.g., :
- (void)startButtonTouch:(id)sender {
// start the process running...
}
- (void)endButtonTouch:(id)sender {
// stop the running process...
}
Drawing upon the bobnoble's answer here's a helper view
#import <UIKit/UIKit.h>
#interface YOIDCAutorepeatingButton : UIButton
// you COULD pinch pennies switching to nonatomic, but consider
// how much time it would take to debug if some day some moron decides without checking this spec
// to alter this prop off another thread
#property (atomic) NSTimeInterval delayUntilAutorepeatBegins;
#property NSTimeInterval delayBetweenPresses;
#property (weak) id<NSObject> recipient;
#property SEL touchActionOnRecipient;
#end
-------------> .m
#import "YOIDCAutorepeatingButton.h"
#interface YOIDCAutorepeatingButton()
{
NSTimeInterval _pressStartedAt;
}
#end
#implementation YOIDCAutorepeatingButton
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self addTarget:self action:#selector(startButtonTouch:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:#selector(endButtonTouch:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
self.delayUntilAutorepeatBegins = .250;
self.delayBetweenPresses = .080;
}
return self;
}
-(void)killLastCharacter:(id)sender
{
[self.recipient performSelector:self.touchActionOnRecipient withObject:sender];
}
- (void)performAutorepeat:(id)sender
{
if(!self.delayBetweenPresses) {
// bail
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.delayBetweenPresses * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(_pressStartedAt) {
[self killLastCharacter:sender];
[self performAutorepeat:sender];
}
});
}
- (void)startButtonTouch:(id)sender {
[self killLastCharacter:sender];
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
_pressStartedAt = now;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.delayUntilAutorepeatBegins * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(_pressStartedAt) {
[self killLastCharacter:sender];
[self performAutorepeat:sender];
}
});
}
- (void)endButtonTouch:(id)sender {
_pressStartedAt = 0;
}
#end
-------- sample usage -------
- (IBAction)killLastDigit:(id)sender {
.....
- (void)viewDidLoad
{
assert(self.backSpace);
[YOIDCAutorepeatingButton class]; // if xib is in a bundle other than main gottal load the class
// otherwise you'd get -[UIButton setRecipient:]: unrecognized selector sent to instance
// on setRecipient:
self.backSpace.recipient = self;
self.backSpace.touchActionOnRecipient = #selector(killLastDigit:);

Resources