View.UILabel.text not changing after switch between viewControllers - ios

I have used NSTimer & at the end of countDown want to change the value of View.UILabel.text=#"Time Up". This is working perfectly until I switch to some other ViewController, after switching between viewController(& coming back to this viewController) when countDown is complete I am getting perfect values & their is no nil but View.UILabel.text value is not changing, I have checked exceptions, no exception get raised.
My Code:
-(void) timeInterval
{
appdelegate._UserProfile.totalTime = 30;
UIApplication *app = [UIApplication sharedApplication];
//will allow NSTimer to continue running in background..
UIBackgroundTaskIdentifier bgTask = 0;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
appdelegate._UserProfile.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(countDown) userInfo:nil repeats:YES];
}
-(void) countDown
{
appdelegate._UserProfile.totalTime -= 1;
if(appdelegate._UserProfile.totalTime == 0)
{
[profileView.userPhoneNo setText:#""];
profileView.timeUp.text= #"* Time Up";
}
}
Please any help will be very appreciated. (I have seen many questions but my issue is not solved)
*Note: After switch if i change the value of label in ViewWillAppear() it is working perfect, but i need to change text when countDown is completed. I am using UINavigationController & MFSideMenuManager to switch between ViewControllers.
My Navigation Code:
-(void) initMyProfile
{
UINavigationController *detailNavigationController = [MFSideMenuManager sharedManager].navigationController;
detailNavigationController.menuState = MFSideMenuStateHidden;
MyProfileViewController_iPhone* detailViewController = [[MyProfileViewController_iPhone alloc] initWithNibName:nil bundle:nil];
detailViewController.title = #"My Profile";
detailNavigationController.viewControllers = [NSArray arrayWithObject:detailViewController];
}

One possible explanation is that you are not coming back to the original view controller, but to a fresh new instance. This can easily happen when using a standard segue instead of an unwind segue.
See this other question where a similar problem occurred: Changing label text inside method

If you are returning to the view by clicking on the back navigation button, and calling this methods on viewDidLoad that is the problem.
The viewDidLoad are only called once the view controller is created, to call those methods every time when you view controller opens, call those methods on viewWillAppear.

Related

Dismiss a VC and return to a countdown on previous VC

How can I get a VC to dismiss while returning to the previous VC which still displays the state after the START button was pressed (ie buttons still visible and countdown still going on etc.) as in this snippet.
-(IBAction)startPressed:(id)sender
{
// hide start UI
self.startButton.hidden = YES;
self.promptLabel.hidden = YES;
// show running UI
self.timeLabel.hidden = NO;
self.distLabel.hidden = NO;
self.paceLabel.hidden = NO;
self.stopButton.hidden = NO;
self.progressImageView.hidden = NO;
self.mapView.hidden = NO;
self.cameraButton.hidden = NO;
self.seconds = 0;
// initialize the timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:(1.0) target:self selector:#selector(eachSecond) userInfo:nil repeats:YES];
self.distance = 0;
self.locations = [NSMutableArray array];
[self startLocationUpdates];
}
Currently the previous VC returns to the initial state with all buttons hidden and the START button ready to press again.
What kind of segue to the second VC is necessary so the countdown on the first VC continues and the second simply appears over it and is then dismissed without interfering with the stuff going on on the first one?

How to release my NSTimer object in ios?

In My Application i need to release my NSTimer , when i am moving from one view controller to another view controller . How to release this type of objects in ARC ? i am using below code for creation and releasing NSTimer but Where i have to write this releasing code in view controller?
For Creation.
- (void)viewDidLoad{
[super viewDidLoad];
updateBCK = [NSTimer scheduledTimerWithTimeInterval:(5.0) target:self selector:#selector(changeImage) userInfo:nil repeats:YES];
[updateBCK fire];
}
-(void)changeImage{
static int i=0;
if (i == [myImages count]){
i=0;
}
[UIImageView beginAnimations:nil context:NULL];
[UIImageView setAnimationDuration:0.6];
mainBackgroundImageView.alpha=1;
mainBackgroundImageView.image =[myImages objectAtIndex:i];
NSLog(#"\n The main screen image is %#",[myImages objectAtIndex:i]);
[UIImageView commitAnimations];
i++;
}
For Release.
[updateBCK invalidate];//
updateBCK = nil;
Thanks in Advance.
You should call
[timer invalidate];
timer = nil;
where you push your view controller. If this is an issue, you can still call it in
- (void) viewWillDisappear:(BOOL)animated;
Also, you should initialize it in
- (void) viewDidAppear:(BOOL)animated;
That makes more sense.
When you want to stop time
use below code
[timer invalidate]; // timer is the name of timer object
- (IBAction)button:(id)sender {
SecondViewController *second = [[SecondViewController alloc]
initWithNibName:#"secondViewController"
bundle:nil];
[self presentViewController:second animated:NO completion:nil];
[self.timer invalidate]; // timer is the name of timer object
timer=nil;//it may work without this line too ;not sure
}
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Timers/Articles/usingTimers.html
A timer maintains a strong reference to its target. This means that as long as a timer remains valid, its target will not be deallocated. As a corollary, this means that it does not make sense for a timer’s target to try to invalidate the timer in its dealloc method—the dealloc method will not be invoked as long as the timer is valid.
when i am moving from one view controller to another view controller
Then you should do this in delloc, if you want to do some cleanup tasks when your view is dismissing or released. It's the best place, In such case you can implement it.
-(void)dealloc{
[updateBCK invalidate];//
updateBCK = nil;
}
Hope this helps

Crash while popping view controller

I have one view controller (let's call it ViewController) with timer within its code, and have another view controller (let's call it ContentViewController) with webView within it which shows content depending on which button is clicked.
I have buttons on ViewController which, when clicked, pushes ContentViewController which loads a html file into its webView.
Timers are used to close ContentViewController if it is pushed.
Here's how I create timers:
-(void)createTimer :(NSNumber*)index
{
if (_show)
{
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:index, #"parameter1", nil];
switch ([index intValue])
{
case 1001:
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1002:
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1003:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1004:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
}
}
}
Here's the code in ViewController which closes ContentViewController when timer fired:
-(void)updateUi :(NSTimer *)timer
{
int index = [[timer.userInfo objectForKey:#"parameter1"] intValue];
if([self.navigationController.visibleViewController isKindOfClass:[ContentViewController class]])
{
if ([[[NSUserDefaults standardUserDefaults]valueForKey:#"CurrentString"] intValue]==index )
{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
And on web page which is shown on ContentViewController there's a button,when user clicks it ContentView must go back to ViewController. Here's how I do this:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"coffee-drink"])
{
NSString *urlString = [[request URL] absoluteString];
NSArray *urlParts = [urlString componentsSeparatedByString:#":"];
if (urlParts.count > 1) ///------------поменял
{
[self.navigationController popViewControllerAnimated:YES];
}
}
return YES;
}
All of above works fine but sometimes it crashes with the following error:
[ContentViewController respondsToSelector:]: message sent to deallocated instance
And also, I have other errors like these:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
and
delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds
The last one appears very rarely.
I don't know what am I doing wrong.Can someone help me)?If needed any more information I will provide it!Thanks in advance!
Issue
You are popping your view without leaving ARC to deallocate all objects and delegates, by keeping them alive.
My Solution
For the NSTimer
At #implementation create NSTimer *timer and use it when you want to initialize it. To dealloc it correctly when you pop back, at viewWillDisappear set [timer invalidate] and timer = nil.
For delegates
Set you specific delegates to nil. For example self.delegate = nil
#Yuandra Ismiraldi is right. Your NSTimer will call your updateUI method every 20/10 seconds repeatedly untill it receives [timer invalidate]; instruction. You are getting this error messages as you navigate through your view controller tree. Sometimes your NavigationViewController will need more memory and will release some of un-used objects. In your case some of the previously shown view controllers that still runs the updateUI method every 20/10 seconds. Once this view controller has been released and your selector is called you will receive this crash.
I hope this explains why sometimes you get this crash, and sometimes not.
Your timer code
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES]
the repeat parameter is set to YES, this make your timer always repeat, thus repeating the sending of the message updateUI after the timer has run for the first time and the view has popped, thus resulting in your error. To make sure your timer only run once, set the repeat to NO
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:NO]

Xcode IOS - How to get the scene/view that is currently being viewed in appdelegate

Okay I am kind of new to IOS development, but I am writing an application where I am using a timer class to time out the user if they idle too long on any particular scene in my storyboard and it bumps the user back to the original scene/view. I have a single story board that is made up of several scenes/views(not sure what the correct word here is), and each scene has its own view controller.
I accomplish the timeout via the appdelegate class. See code below.
So I have the code working and it works great, but I am trying to make it so that it will ignore the timer if we are on the main scene.
I have googled this, read copious amounts of documentation, and have tried many things but so far I haven't been able to figure out how to get the currently viewed scene in the applicationDidTimeout method.
If I can get the name of the currently viewed scene/view, then I can choose to ignore the timer or not.
Does anyone know how to do this?
Thank you for your time.
#import "StoryboardAppDelegate.h"
#import "TIMERUIApplication.h"
#implementation StoryboardAppDelegate
#synthesize window = _window;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// applicaiton has timed out
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];
return YES;
}
-(void)applicationDidTimeout:(NSNotification *) notif
{
NSLog (#"time exceeded!!");
UIViewController *controller = [[UIStoryboard storyboardWithName:#"Main" bundle:NULL] instantiateViewControllerWithIdentifier:#"StoryboardViewController"];
UINavigationController * navigation = [[UINavigationController alloc]initWithRootViewController:controller];
[self.window setRootViewController:navigation];
navigation.delegate = self;
navigation.navigationBarHidden = YES;
if (controller) {
#try {
[navigation pushViewController:controller animated:NO];
} #catch (NSException * ex) {
//“Pushing the same view controller instance more than once is not supported”
//NSInvalidArgumentException
NSLog(#"Exception: [%#]:%#",[ex class], ex );
NSLog(#"ex.name:'%#'", ex.name);
NSLog(#"ex.reason:'%#'", ex.reason);
//Full error includes class pointer address so only care if it starts with this error
NSRange range = [ex.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
if ([ex.name isEqualToString:#"NSInvalidArgumentException"] &&
range.location != NSNotFound) {
//view controller already exists in the stack - just pop back to it
[navigation popToViewController:controller animated:NO];
} else {
NSLog(#"ERROR:UNHANDLED EXCEPTION TYPE:%#", ex);
}
} #finally {
//NSLog(#"finally");
}
} else {
NSLog(#"ERROR:pushViewController: viewController is nil");
}
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
}
#end
I'm assuming you've written the logic for the timer somewhere. Can you just invalidate the timer when you've popped back to the rootViewController?
Also instead of pushing a viewController onto the navigationViewController and handling the errors, you should check to see if the controller you're pushing is already in the stack like so:
if (![navigation.viewControllers containsObject:viewController] {
// push onto the stack
}
You could also check to see how many levels are currently in the navigationController by checking the count of the viewControllers array like so:
if ([navigation.viewControllers count] == 0) {
// I know we're on the main screen because no additional viewControllers have been added to the stack.
}
If you are not using modal controllers anywhere then the simplest solution would be
UINavigationController* nav = (UINavigationController*)self.window.rootViewController; // You could just save the nav as part of your app delegate
if (nav.viewControllers.count > 1){
[nav popToRootViewControllerAnimated:YES];
}
This is different then your current code because your main page will not be deleted and recreated every time the timer goes off
Okay I figured out how to do this. I was making this way too complicated.
To solve this I simply made a property and method in the app delegate class where I could set a scene name.
Then in each view controller header file I import the header file for the app delegate class and define a reference to it. Then in the load event for each view I simply set the scene name in the app delegate class using this line of code.
[myAppDelegate setSceneName:self.title];
Easy peasy!

UILabel text is not updating NSTimer objective c?

Hi I have Navigation based application in which there is a timer in one view. (View like : A, B & C)
I have timer in C when I start timer it's working fine but when I push back to any view and again come to View C it's not showing updated values.
here is my code .
App Delegate
-(int)updateTimer
{
timer_value--;
return timer_value;
}
View "C" Code
- (IBAction)buttonClick:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(update) userInfo:nil repeats:YES];
}
-(void)update
{
MFAppDelegate *appDelegate = (MFAppDelegate *) [[UIApplication sharedApplication]delegate];
int Time=[appDelegate updateTimer];
int minute=Time/60;
int second=Time-(minute*60);
NSString *strValue=[NSString stringWithFormat:#"%d:%d",minute,second];
NSLog(#"%#",self.lbl_timer.text);
[self.lbl_timer setText:strValue];
}
update function is calling every time and NSlog of label text is showing correct.
Anything I am doing wrong ? please help me out.
In your class
-(void)update
{
MFAppDelegate *appDelegate = (MFAppDelegate *) [[UIApplication sharedApplication]delegate];
int Time=[appDelegate updateTimer];
int minute=Time/60;
int second=Time-(minute*60);
NSString *strValue=[NSString stringWithFormat:#"%d:%d",minute,second];
NSLog(#"%#",self.lbl_timer.text);
[[NSNotificationCenter defaultCenter] postNotificationName:#"DoUpdateLabel" object:strValue userInfo:nil];
}
- (void) updateLabel:(NSString *)string
{
yourLabel.text = string;
}
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel:) name:#"DoUpdateLabel" object:nil];
}
I think the views you are referring to are actually view controllers. When you go back from a view controller to previous view controller in the navigation controller stack, depending on your code, it will be released if you do not retain it, which I assume happened in your case.
Because of that, everytime you push a new view controller, it is actually a newly allocated instance of the view controller, and it is not the same instance as the previously viewed view controller.
Try to make the view controller, e.g. A, which has the timer label as a strong property of the view controller that push A.
#property (nonatomic, strong) UIViewController *viewControllerA;
And push the property instead of allocating a new instance everytime you push A.
[self.navigationController pushViewController:self.viewControllerA animated:YES];
you need to tell the UILabel to redraw itself after its text property has changed. I don't know about the method Ahmed Z. describes, but in my projects something like
[[self lbl_timer] setNeedsDisplay];
does the trick.
cheers

Resources