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
Related
Hi im developing an app that has a parent view that then used containers to embed other views as seen below.
For now im only working with the left and centre container which are both table views. The main view or the Project screen view is my parent controller and i want it to pass data to and from the two child controller and i know for this the best option is to use delegates. However each example i have looked at that uses delegates, created and initialises a new view controller so for example lets say the left container embeds a view using the leftviewcontroller. Each example has this line of code.
LeftViewController *sampleProtocol = [[LeftViewController alloc]init];
LeftViewController.delegate = self;
Im thinking i dont need to create a new LeftViewController since it is embeded it is already in my list of child controllers. So my queston is how would i get the controller from the list of child controllers and set the parent as the delegate. I know i it is an array and i can use objectAtIndex but how do i know the order of items in the array will not change can i not call it but a tag or identifier? Thank you for any help sorry if the question is not that clear its my first time setting up delegates.
i know for this the best option is to use delegates.
In this case, I wouldn't be so sure. I think the best option would be to have a robust model and use KVO and notifications to signal updates between view controllers.
The direct answer to your question is not too bad.
for (UIViewController *viewController in self.childViewControllers) {
if ([viewController isKindOfClass:[LeftViewController class]]) {
LeftViewController *leftViewController = (id)viewController;
leftViewController.delegate = self;
break;
}
}
I think a minor improvement on this would be to use the segue. Make sure each of the containers have a named segue. In this example, the left view controller has a segue with the identifier "Load Child LeftViewController".
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Load Child LeftViewController"]) {
LeftViewController *leftViewController = segue.destinationViewController;
leftViewController.delefate = self;
}
}
Its always better to use NSNotificationCenter for such complex mechanism.
*** put following code in LeftController.m ***
// *** Register a Notification to recieve a Data when something happens in Center controller ***
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedNotification:)
name:#"hasSomeData"
object:nil];
// *** create a method to receive Notification data ***
- (void)receivedNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"hasSomeData"])
{
// do your stuff here with data
NSLog(#"data %#",[notification object]);
}
}
*** when something happen in center controller post a notification to inform Left Controller ***
[[NSNotificationCenter defaultCenter] postNotificationName:#"hasSomeData" object:self];
//Secondvc.h
#protocol Sendmessage<NSObject>
#required
-(void)Object:(NSArray *)tosend;
#end
#interface Secondvc:UIViewcontroller{
id <Sendmessage> delegate;
}
#property(strong,nonatomic) id <Sendmessage> delegate;
#end
//Secondvc.m
#implementation Secondvc
#synthesize delegate;
-(void)viewDidLoad{
//Do Something here!
}
//Pass Some Value When a button event occured in Second vc
-(IBAction)Send_Data{
[self dismissViewControllerAnimated:Yes completion:nil];
[self.delegate Object:[NSArray Arraywithobjects:#"Hello",nil]];
}
#end
//FirstVc.h
#import "Secondvc.h"
#interface FirstVc<Sendmessage>
#end
//FirstVc.m
#implementation FirstVc
-(void)viewDidLoad{
Secondvc* Object=[[Secondvc alloc]init];
Object.delegate=self;
}
#pragma mark Secondvc Deklegate method implementation
-(void)Object:(NSArray *)tosend{
NSLog(#"Recieved data Form Second VC Is:\n%#",tosend);
}
#end
HTH!Enjoy Coding.
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.
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!
I have an NSTimer that runs every 10 seconds and is kicked off from LandingController.m. It continues to run as you go to other views in the application. I want to be able to (when a certain condition is met within that timer) update a label field from another view GuardMenu.m The label I want to update is called CurrentZone.text and I want to update it from value "N" to value "Y."
Here's my timer on LandingController.m
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkForMessages)
userInfo:nil
repeats:YES];
Which calls this on LandingController.m
- (void)checkForMessages
{
if ( //some condition here ){
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
}
}
First create a NSNotification in your init method of GuardMenu class
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"TextChangeNotification" object:nil];
}
return self;
}
Then implement the notification's selector, this is where you will be changing your CurrentZone label text.
- (void)receiveNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"TextChangeNotification"]) {
NSLog (#"Change you label here");
self.lblCurrentZone.text = #"Y";
}
}
Now in your LandingViewController.m -viewDidLoad Method
Start the timer.
self->msgTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(checkForMessages) userInfo:nil repeats:YES];
Now implement the #selector for the NSTimer, this is where you will be sending the notification back to the GuardMenu class
- (void)checkForMessages {
NSString *strText = #"test";
if ([strText isEqualToString:#"test"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"TextChangeNotification" object:nil];
}
else {
}
}
NOTE: the NotificationName should be the same.
Sample Project Code Dropbox Link
You can use the prepareForSegue method to pass objects between view controllers in the storyboard. For example, to pass a string from the GreyViewController to the OrangeViewController, in GreyViewController.m you have:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
OrangeViewController *orange = [segue destinationViewController];
orange.self.orangeString = #"text for orangeView";
}
Then in the viewDidLoad of the other view controller, in the OrangeViewController.m, you can set the text of the label by doing the following:
self.orangeLabel.text = self.orangeString;
Maybe you should describe which error you're getting. Is your checkForMessages method (not) firing? Use an NSLog() message to check. Otherwise, check if the UILabel you want to change is actually loaded into memory (i.e. is not nil). Please also let us know if the currentZone.text is part of the view hierarchy of the LandingController or of another view controller.
You can make use of notifications.
In GuardMenu class init register for custom notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"MessageChangeNotification"
object:nil];
In LandingController->checkForMessages method post the notification when condition is satisfied.
[[NSNotificationCenter defaultCenter] postNotificationName:#"MessageChangeNotification"
object:nil];
In GuardMenu class implement the notification callback selector
- (void) receiveNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"MessageChangeNotification"]) {
NSLog (#"Successfully received the notification");
//Change the label text here..
}
}
Hope it helps!
Amar.
Make sure that the label you are trying to edit is declared as a property in the appropriate view and properly synthesised. Also make sure it is connected in Interface Builder.
In GuardMenu.h
#property (strong, nonatomic) IBOutlet UILabel *CurrentZone;
Also, in LandingController.h, import GuardMenu.h:
#import "GuardMenu.h"
You will now be able to access the label and its text property from LandingController.h using
-(void)checkForMessages
{
GuardMenu *guardMenu = [[GuardMenu alloc]init];
if (/* some condition here */) {
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
guardMenu.CurrentZone.text = #"Y";
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
}
}
For this you should use KVO(Key Value Observing). There are lot of ways to pass notifications, but KVO is potentially much simpler. I suspect that Notification is used more often because you can do a ‘chain of responsibility’ for an event as opposed to just assigning an observer. However, just having an observer in a controller that can watch a particular property in another object and get notified of changes is a powerful and simple way to solve a whole class of problems.
Firstly set a public property in LandingController like "lablelText" .
Then add the observer once, when you create the LandingController view. Once you've added the observer, the observeValueForKeyPath:ofObject:change:context: method will be executed in GuardMenu, so you can do the update to the GuardMenu UI from there. You shouldn't need to do anything every time GuardMenu is about to appear.
In GuardMenu, you should probably create LandingController just before you are going to push LandingController onto the controller stack, presumably in the event handler for some action the user took. Immediately after you create LandingController, add the observer in GuardMenu with the correct NSKeyValueObservingOption value.
If you just want to be notified whenever the public property "lablelText" in LandingController is changed, then try this:
LandingController
#interface LandingController : UIViewController {
}
#property (strong, nonatomic) NSString* lablelText;
- (void)checkForMessages;
#end
#implementation LandingController
#synthesize lablelText;
- (void)checkForMessages
{
if ( //some condition here ){
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
self.lablelText = #"Y";
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
self.lablelText = #"N";
}
}
#end
GuardMenu
#interface GuardMenu : UIViewController {
}
#property (strong, nonatomic) IBOutlet UILabel* nameLabel;
- (IBAction) methodToHandleEvent:(id)sender;
#end
#implementation GuardMenu
- (IBAction) methodToHandleEvent:(id)sender{
LandingController* tempVC = [[LandingController alloc]init];
[tempVC addObserver:self forKeyPath:#"lablelText" options:NSKeyValueObservingOptionNew context:NULL];
[self.navigationController pushViewController:tempVC animated:YES];
}
- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
// Here you will be notified everytime lablelText changes
if ([keyPath isEqual:#"lablelText"]) {
NSString* changedName = [change objectForKey:NSKeyValueChangeNewKey];
// do something with the changedName - call a method or update the UI here
self.nameLabel.text = changedName;
}
}
#end
As an alternative for this you can use NSNotificationCeneter to pass notifications from one class to another for some event.
For this you can check my detailed answer How to pass Notifications from one class to another for some event.
Hope it helps you.
Create a notification in the init of GuardMenu class
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"UpdateCurrentZoneNotification"
object:nil];
In the LandingController,
(void)checkForMessages
{
if ( //some condition here ){
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateCurrentZoneNotification"
object:nil];
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
}
}
i have a question. How can i pass and object to the previous view when i press the back button in UINavigationBar?
If you have a link to your view in your current VC like this :
previousVC.objectToPass = objectToPass;
Or with a notification like this :
1 - in your back method :
[[NSNotificationCenter defaultCenter] postNotificationName:#"PassObject" withObject:objectToPass];
2 - In your previousVC :
- (void) didReceiveNotificationPassObject:(NSNotification*)notification
{
YourObjectClass *theObject = (YourObjectClass*)notification.object;
}
3 - In the init of your previousVC :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didReceiveNotificationPassObject:) name:#"PassObject" object:nil];
I am explaining with an example
there are two screens first and second.
Currently i m on second view.
so make a property of that object which you want to pass
MyObject *obj and make it property
#property(nonatomic,retain) MyObject *obj; in second.h
then set this in viewWillDisappear or wherever you want
make object of Second class
Second *objSecond=......;
then objSecond.obj is that object which you need on this page.now you can do whatever you want.