UISlider to modify NSTimer interval crashing application when moved - iphone

I have been having problems creating a UISlider that can be used to modify a NSTimer I created. Essentially the slider is ment to modify the integer that the NSTimer is counting down from, but when I try to move the UISlider, the application crashes, I'm assuming this is because it is interfering with the countdown that is occurring, but I don't know what to do to fix this.
Here is the relevant code
- (void)viewDidLoad {
[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text = #"I0A0IN6";
mainInt = mySlider.value;
timer = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0) target:self selector:#selector (timerVoid) userInfo:nil repeats:YES];
[super viewDidLoad];
}
- (void)timerVoid {
mainInt += -1;
if (mainInt == 0) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Break Time!"
message:#"Time to take a break, please go to the exorcises page during your break inorder to maximise it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
[mainInt invalidate];
}
[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text=[NSString stringWithFormat:#"%d" , mainInt];
}
The slider is called mySlider, and it is modifying the interger "mainInt" (line 5).

Few things:
[super viewDidLoad]; line is better be first in viewDidLoad.
There is no need to set the font each time timerVoid is executed.
As I have mentioned in the comment, you should call invalidate on timer and not on mainInt.
The slider does not modify mainInt - you have set the value of mainInt to hold the initial value of your slider and it is not changed by itself when you change the value of the slider. In order to do that you should create an IBAction and connect it to slider's valueChanged event. Inside that IBAction you may do what ever you want with the new value of the slider - for example set the value of mainInt or reschedule your timer.
You may also avoid using the IBAction by using the mySlider.value directly everywhere you need.
I don't see any reason for crashing the app though...
Possible code:
- (void)viewDidLoad {
// This line should be first
[super viewDidLoad];
[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text = #"I0A0IN6";
// There is no need in mainInt
//mainInt = mySlider.value;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerVoid) userInfo:nil repeats:YES];
}
- (void)timerVoid {
// This line is much more readable like this ("-=" instead of "+= -")
mySlider.value -= 1;
// Much better to use "<=" in this case to avoid bugs
if (mySlider.value <= 0) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Break Time!"
message:#"Time to take a break, please go to the exorcises page during your break inorder to maximise it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
[alert release]; // Haven't noticed the lack of release here earlier...
// timer and not mainInt
[timer invalidate];
}
// The next line is unnecessary
//[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text = [NSString stringWithFormat:#"%d", mySlider.value];
}
EDIT:
Added the [alert release]; line

Your app is crashing because you haven't linked the valueChanged event from your slider to any proper IBAction method.

Related

UIAlertView displays textboxes and buttons

I used the following code to create a UIAlertView and add some components on it but the result is at the image :(( (image is here : http://i.stack.imgur.com/DTg02.png)
-(IBAction)login:(id)sender
{
UIAlertView *login = [[UIAlertView alloc]initWithTitle: #"Login first"
message:#"enter username and password please first" delegate:self cancelButtonTitle:#"cancel"otherButtonTitles:#"OK", nil];
k = [[UITextField alloc] initWithFrame:CGRectMake(20.0, 70.0, 200.0, 25.0)]; //<< it also displays wrong without this line
k.text = #"";
k.backgroundColor = [UIColor whiteColor];
k.clearButtonMode= UITextFieldViewModeWhileEditing;
k.keyboardType = UIKeyboardTypeAlphabet;
k.keyboardAppearance = UIKeyboardAppearanceAlert;
p = [[UITextField alloc] initWithFrame:CGRectMake(20.0, 100.0, 200.0, 25.0)];
p.text = #"";
p.backgroundColor = [UIColor whiteColor];
p.clearButtonMode = UITextFieldViewModeWhileEditing;
p.keyboardType = UIKeyboardTypeAlphabet;
p.keyboardAppearance = UIKeyboardAppearanceAlert;
[login addSubview:k];
[login addSubview:p];
[login show];
}
if you want to add multiple textField and Multiple Button on UIAlertView then follow the step :
step 1 :
#interface ViewController ()<UIAlertViewDelegate>
{
UITextField *textFieldOne,*textFieldTwo,*textFieldThird;
}
step 2 :
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"Multiple TextField" delegate:nil cancelButtonTitle:#"NO" otherButtonTitles:#"YES", nil];
[alert addButtonWithTitle:#"CANCEL"];
alert.delegate=self;
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
textFieldOne=[[UITextField alloc]initWithFrame:CGRectMake(5, 0, 150, 20)];
[textFieldOne setPlaceholder:#"First Name"];
[myView addSubview:textFieldOne];
textFieldTwo=[[UITextField alloc]initWithFrame:CGRectMake(5,25, 150, 20)];
[textFieldTwo setPlaceholder:#"Middle Name"];
[myView addSubview:textFieldTwo];
textFieldThird=[[UITextField alloc]initWithFrame:CGRectMake(5,50, 150, 20)];
[textFieldThird setPlaceholder:#"Last Name"];
[myView addSubview:textFieldThird];
[alert setValue:myView forKey:#"accessoryView"];
[alert show];
step 3 :
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"YES"])
{
NSLog(#"Button YES");
NSLog(#"TextFiledOne Text = %#",textFieldOne.text);
NSLog(#"TextFiledTwo Text = %#",textFieldTwo.text);
NSLog(#"TextFiledThree Text = %#",textFieldThird.text);
}
else if([title isEqualToString:#"NO"])
{
NSLog(#"Button NO");
}
else
{
NSLog(#"Cancel is click");
}
}
A UIAlertView is no thought for additional text input. You could use instead a UIActionSheet as it is explained here.
There is no point adding to the code that has already been added on here as it is good enough already and there are some good alternatives given. What I am going to do is tell you why you shouldn't be adding your own UITextFields to a UIAlertView using addSubview:.
Basically Apple have made the UIAlertView class to be used as is and the view hierarchy for this class is private.
Subclassing Notes
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
Subclassing note taken from UIAlertView Apple documentation.
In essence what this means is that if you use the addSubview: method on a UIAlertView then you are in essence altering something that Apple as indicated as being private and your app would get rejected from the Apple App Review Process for rule 2.5.
2.5 Apps that use non-public APIs will be rejected
You might be asking yourself why does that method even exist then for UIAlertView surely because its there we can use it. Well NO the reason it is there is because the UIAlertView class is itself is a subclass of UIView which is where the addSubview: method is declared. Unfortunately there is no why to stop a instance of UIAlertView from actually calling that method. So what Apple have done is they have overridden the addSubview: method and all it does is it returns. So this method makes it do nothing and any views you pass to this method will never get added, as it never calls [super addSubview:view];.
So when it comes to UIAlertViews there are two things that you shouldn't be doing and these are:
Subclass UIAlertView like #interface MYAlertView : UIAlertView this is subclassing and is not allowed.
Alter View Hierarchy like [alertView addSubview:view];
However there is one point I should make, whilst we aren't allowed to subclass UIAlertView we are still allowed to make categories for it like #interface UIAlertView (MyCategory) as this is not classed as subclassing it is known as a class category or class extension (I've also heard it called a class provider before).
It should also be noted that if you are developing for iOS8 and above you should be using UIAlertController as the UIAlertView has been deprecated.
Important:
UIAlertView is deprecated in iOS 8. (Note that UIAlertViewDelegate is also deprecated.) To create and manage alerts in iOS 8 and later, instead use UIAlertController with a preferredStyle of UIAlertControllerStyleAlert.
So I'd either use one of the custom classes mentioned in the other answers or move to use UIAlertController or if you have the time make your own custom alert view.
I hope this explanation helps you understand UIAlertViews more.
Don't go for all the bluffs. Just use the following code:
[login setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
And to handle the inputs of the user, use this:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if([[alertView textFieldAtIndex:0].text isEqual:#"UserName"]&&[[alertView textFieldAtIndex:1].text isEqual:#"PassWord"])
{
//do your stuff here
[adminLoginButton setTitle:#"Logout" forState:UIControlStateNormal];
[adminLoginButton setImage:[UIImage imageNamed:#"Logout.png"] forState:UIControlStateNormal];
}
else
{
UIAlertView *errorMessage = [[UIAlertView alloc]initWithTitle:#"Authentication Error" message:#"Input is wrong" delegate:self cancelButtonTitle:#"Okay" otherButtonTitles: nil];
[errorMessage show];
}
}

Can't see my alertView unless I comment out automatic dismission

I am implementing a simple alert view with activity indicator to show to the user while the app is performing the restore of the database. Everything is fine except that I can't see the alert view, unless I comment out the automatic dismiss line (last one here).
Ok, the restore of the database is quite quick, but still I would expect to see it even if for an instant, no? I can see the screen get's a little darker during animation but that's all. I also tried putting a for loop to extend the time, the time extends but still no alert view.
There is nothing wrong in the way the alert view is called, since if I comment the dismiss, I can see it...only forever. Anyone has an idea here?
Before anyone says it, I tried changing the delegate of the alertView to self as posted here, but it didn't help.
// First we prepare the activity indicator view to show progress indicator
UIActivityIndicatorView * activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[activityView setFrame:CGRectMake(121.0f, 50.0f, 37.0f, 37.0f)];
[activityView startAnimating];
// Then we put it in an alert view
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Loading" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
[alertView addSubview:activityView];
[alertView show];
// Finally we operate the restore
[[SQLManager sharedSQL] restoreDatabase:[restoreDatabaseURL path]];
// And we can dismiss the alert view with the progress indicator
[alertView dismissWithClickedButtonIndex:0 animated:NO];
Im not sure why this happens, but I encounter it occasionally as well. One way I have gotten something like this to work is by using performSelector:withObject:afterDelay: on the next call, like this:
// First we prepare the activity indicator view to show progress indicator
UIActivityIndicatorView * activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[activityView setFrame:CGRectMake(121.0f, 50.0f, 37.0f, 37.0f)];
[activityView startAnimating];
// Then we put it in an alert view
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Loading" message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
[alertView addSubview:activityView];
[alertView show];
// Finally we operate the restore
// [[SQLManager sharedSQL] restoreDatabase:[restoreDatabaseURL path]];
[[SQLManager sharedSQL] performSelector:#selector(restoreDatabase:) withObject:[restoreDatabaseURL path] afterDelay:0.25];
// And we can dismiss the alert view with the progress indicator
[alertView dismissWithClickedButtonIndex:0 animated:NO];
performSelector Documentation
Sounds like it's dismissing itself almost instantaneously. If you add a for loop, it will likely freeze all other computations which means your activity view won't show up until the loop breaks.
You could try using a NSTimer to call another function to determine if the restore is complete or not;
// Setup our the timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(checkRestore)
userInfo:nil
repeats:YES];
// Timer function checking for completion
-(void)checkRestore {
if (restore is complete) {
[alertView dismissWithClickedButtonIndex:0 animated:NO];
}
}

Creating a timer to timeout and pop up an alert

I'm trying to get a recent location using CLLocationManger. If I cannot get one within 30 seconds, I want an alert to pop up saying that "we're having problems right now" or something along those lines. In my CLLocationManager Delegate, I do this:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSDate *eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
NSTimer *timer;
if (abs(howRecent) < 1.0) { // process time if time is less than 1.0 seconds old.
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateCount:) userInfo:nil repeats:NO];
return;
}
My updateCount method:
- (void)updateCount:(NSTimer *)aTimer {
count++;
NSLog(#"%i", count);
if (count == 30) {
[aTimer invalidate];
NSLog(#"Timer invalidated");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Server Down" message:#"Sorry, but we cannot find your location at this time. Please try again later." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
}
}
But I don't see my alert get fired. I tried with a repeating timer, but after it hits 30 seconds, and the alert is shown, it repeats again. So I'm not quite sure what I'm doing wrong here. Thanks!
just a small update to updateCount method :
- (void)updateCount:(NSTimer *)aTimer {
count++;
NSLog(#"%i", count);
if (count == 30) {
[aTimer invalidate];
NSLog(#"Timer invalidated");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Server Down" message:#"Sorry, but we cannot find your location at this time. Please try again later." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
[timer invalidate];
timer = nil;
}
}
Don't use NSTimer *timer; in locationManager method. instead directly call timer = [[NSTimer....]] method and I think because there is one argument in updateCount method i.e. NSTimer and we are using different NSTimer instance so here is basically problem. Try without using argument in updateCount Method and invalidate the timer that is declared in .h file.
I checked the documentation and found following paragraph in that.
However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate method. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call the invalidate method from the same thread on which the timer was installed.
So, for invalidating timer try calling performSelector.
I am not sure weather it will help or not but if it works then please post here so that other too can take advantage.

Update text in alert message

Still trying to update the message in an UIAlertview while the alert is active. I removed most of the first part of the question and publish more code in the update below.
UPDATE 3: Adding more code!
In the .h file I declare the following (among others):
#interface WebViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
UIAlertView *alert;
}
I #property and #synthesize the UIAlertview to.
Next I create the alert in an IBAction which is run by a button click:
-(IBAction)convert {
convertButton.enabled = NO;
mailButton.enabled = NO;
backButton.enabled = NO;
//show the alert window
alert = [[UIAlertView alloc] initWithTitle:#"Converting in progress\nPlease Wait..." message:#"\n\n\n" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[alert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50);
[indicator startAnimating];
[alert addSubview:indicator];
[indicator release];
[alert setMessage:#"getting roster"];
}
It then jumps to the following function:
- (void)didPresentAlertView:(UIAlertView *)progressAlert {
//A lot of code
[alert setMessage:#"Checking history"];
//more code
[alert setMessage:#"writing history"];
//even more code
[alert setMessage:#"converting roster"];
}
The didPresentAlertView method ends with an ASIHTTPRequest to post data to a webpage and when this request is finished the code finally jumps to the last method to exit the UIAlertView and closes everything up:
- (void)requestFinished:(ASIHTTPRequest *)request {
[timer invalidate];
[alert dismissWithClickedButtonIndex:0 animated:YES];
backButton.enabled = YES;
[alert release];
}
I also removed the autorelease from my UIAlertView init to make sure it exists during the rest of the process.
As it is now, the code only fires the very first setMessage -> 'getting roster' and the very last -> 'converting roster'. The setMessage requests in the middle do not get fired..
Really hope someone can help me here!
Thanks all!
Now I see the problem.
When you update the message property, it doesn't fire redrawing the view right away. The view is marked as 'needed to be drawn', and the actual drawing happens later, typically at the end of the current or next runloop in the main thread.
Therefore, while your didPresentAlertView method is running on the main thread, the alert view is not redrawn until the method is finished. This is why a computation-intensive job needs to run on a separate thread to increase the responsiveness of the UI, as the UI-related job is done on the main thread.
What you should do is run your //A lot of code //more code and //even more code on a separate thread, and update the message property on the main thread.
For example, your code may look similar to :
// this is inside didPresentAlertView method
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock: ^{
// A lot of code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"Checking history" waitUntilDone:NO];
// more code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"writing history" waitUntilDone:NO];
// it goes on
}];
If you are working with iOS 4.0 and later, and want to use the GDC, be careful because it may detect independency of your computation and message updates and let them happen concurrently (which is not desired here).

NSTimer not invalidating or is refiring

so here is the problem i have, i created two NSTimer objects and they are fired when i button is pressed. then the user has 20 seconds to press another button which forces an alert to popup where they enter a validation code, and when they press the confirm button on the alert it is supposed to stop the timer. what is happening is that everything works until they press confirm but instead of the timer stopping it hangs for a second( which im thinking is a delay caused by the keyboard dismiss animation) and then the timer continues. any help would be greatly appreciated and here is all relevant code:
#import "hackergameViewController.h"
#import <AudioToolbox/AudioToolbox.h>
#implementation hackergameViewController
#synthesize decryptLabel, crackLabel, decryptButton, crackButton, submit, numberToDecrypt, numberToCrack, stopDecryptButton, stopCrackButton, inputCode;
#synthesize soundFileURLRefBeep;
#synthesize soundFileURLRefBuzz;
#synthesize soundFileObjectBeep;
#synthesize soundFileObjectBuzz;
NSTimer *decryptTimer;
NSTimer *crackTimer;
int crackTime;
int decryptTime;
NSString *codeToConfirm;
#pragma mark UIAlertView
- (void)confirm:(UIAlertView *)confirm clickedButtonAtIndex:(NSInteger)buttonIndex {
if(buttonIndex == 0){
[confirm dismissWithClickedButtonIndex:0 animated:YES];
}
else {
if (inputCode.text == codeToConfirm) {
[self stopCrack];
[self stopDecrypt];
}
}
}
-(void) generateDecryptionCode{
codeToConfirm = [NSString stringWithFormat:#"%i%i%i%i%i%i", arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10];
numberToDecrypt.text = codeToConfirm;
}
-(void) generateCrackCode{
codeToConfirm = [NSString stringWithFormat:#"%i%i%i%i%i%i%i%i%i%i", arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10,arc4random() % 10];
numberToCrack.text = codeToConfirm;
}
- (void)dealloc {
[decryptLabel release];
[decryptButton release];
[crackLabel release];
[crackButton release];
[submit release];
[numberToCrack release];
[numberToDecrypt release];
[super dealloc];
}
- (void) confirmCode{
UIAlertView *confirm = [[UIAlertView alloc] initWithTitle:#"Confirm Code" message:#"Please Input The Correct Code:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Confirm Code", nil];
inputCode = [[UITextField alloc] initWithFrame:CGRectMake(12, 45, 260, 25)];
CGAffineTransform myTransform = CGAffineTransformMakeTranslation(0, 60);
[confirm setTransform:myTransform];
[inputCode setBackgroundColor:[UIColor whiteColor]];
[confirm addSubview:inputCode];
[confirm show];
[confirm release];
[inputCode release];
}
- (void) decryptTimerFires{
if(decryptTime > 0){
decryptTime--;
decryptLabel.text = [NSString stringWithFormat:#"%g", (float)decryptTime/10];
if(decryptTime%10 == 0){
AudioServicesPlaySystemSound (self.soundFileObjectBeep);
}
}
else{
[decryptTimer release];
decryptTimer = nil;
}
}
- (void) crackTimerFires{
if(crackTime > 0){
crackTime--;
crackLabel.text = [NSString stringWithFormat:#"%g", (float)crackTime/10];
if(crackTime%10 == 0){
AudioServicesPlaySystemSound (self.soundFileObjectBeep);
}
else if(crackTime == 0){
AudioServicesPlaySystemSound (self.soundFileObjectBuzz);
}
}
else{
[crackTimer release];
crackTimer = nil;
}
}
-(void) stopDecrypt{
[decryptTimer invalidate];
[decryptTimer release];
decryptTimer = nil;
}
-(void) stopCrack{
[crackTimer invalidate];
[crackTimer release];
crackTimer = nil;
}
-(IBAction)decrypt{
[self generateDecryptionCode];
decryptTime = 200;
decryptTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(decryptTimerFires) userInfo:nil repeats:YES];
[decryptTimer fire];
}
-(IBAction)crack{
[self generateCrackCode];
crackTime = 200;
crackTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(crackTimerFires) userInfo:nil repeats:YES];
[crackTimer fire];
}
In your confirm method, are you sure that your inputCode.text == codeToConfirm is returning true? Have you tried using [inputCode.text compare:codeToConfirm]? From what I can see, your timers only get invalidated in stopCrack and stopDecrypt, which only get called if that comparison succeeds.
First, you should not be calling "fire" manually unless you want the timer to fire immediately.
Second, you should call -invalidate rather than -release (as you're doing in your -decryptTimerFires and -crackTimerFires methods) when you want your timer to go away, then nil it out as you're doing. You're using the +scheduled... class method, which schedules the timer on the current run loop (which retains it, so it's not up to you to release it). -invalidate removes it from the run loop properly.
From the docs:
This method is the only way to remove
a timer from an NSRunLoop object.