iPhone applicationWillResignActive - how to notify current UIView - iphone

I want to pause a timer on my game screen when the iPhone is locked etc. My question is what is the best method to notify the current UIView, which the AppDelegate has no direct access to?

1) Your timer should probably not be managed by the view but by the view's controller. The timer itself is not an inherent part of your UI, only the timer's display is. (What happens if you want to have the timer continue after a view is removed, for example?)
2) Any object (view or controller included) can independently listen for the appropriate notification. For example, in your view controller (or view code, if you choose to go that route):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pauseTheTimer:)
name:UIApplicationWillResignActiveNotification
object:nil];
Then implement a pauseTheTimer: method that will handle the notification. (Since there is only one UIApplication object, you can use nil for the object, as shown.)
This approach nicely decouples your app delegate from the views and view controllers.
(Oh, don't forget to stop observing when your view is unloaded or deallocated. Failure to do so can and will lead to crashes.)

Related

How to deal with ViewController being released, after firing off background thread which referes to now released ViewController?

I have a ViewController which is pushed onto the NavigationController stack. As soon as it's pushed onto the stack it starts to download some images, by means of a downloader object, which is responsible for downlading the images in a background thread. The images can take several seconds, even over WiFi to download. When an image has finished being downloaded the downloader object instructs the ViewController to layout its images, putting the newly downloaded image(s) to the back of a paged UIScrollView. However during this time the user could have pressed the Back button and the ViewController could now have been released / deallocated and so the downloader object will cause a SIGABRT error message and the app will crash.
How should I deal with this situation? Is there some way to check for released / deallocated instances? Or some way to catch the error and log, then ignore, it?
Your best bet is to use a zeroing weak reference. Of course, with iOS 5.0, this is simply a "weak" reference. But, if you are targeting below iOS 5.0 though, then you need a custom solution for it. There is a nice one described by Mike Ash in this article.
Maybe you could avoid the problem by using notifications? Instead of your downloader object referencing the viewController, it posts a notification, which the viewController (if it's loaded) responds to.
in your data loading object:
[[NSNotificationCenter defaultCenter] postNotificationName:#"allDataLoaded" object:nil];
in your view controller viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshMyLayout) name:#"allDataLoaded" object:nil];
in your view controller dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self];
You could even put a reverse notification from your viewController in it's viewDidUnload to tell the data downloader to cancel the downloads (if you want).
The best would be to cancel the download once the result is no longer needed. This prevents unnecessary data traffic and memory consumption.
If you can't do that, the downloader object should have some sort of delegate (your view controller probably) that you can set to nil when you're no longer interested in the results (e.g. in your view controller's dealloc method). Zeroing weak references are also an option, if you target iOS 5, but again, it would be much better to cancel the downloading NSURLConnection.

What gets called after UIApplicationDidBecomeActiveNotification gets triggered?

In my app I'm trying to make my navigation bar not shrink from 44px to 32px when the phone is rotated to horizontal orientation. I've been able to accomplish this by setting the navigationBars frame when the view is rotated and also in viewDidAppear. However, when I push the home button to exit the app and then I reenter the app, the navigation bar still shrinks. So I implemented a notification to detect UIApplicationDidBecomeActiveNotification, and in that method I reset the navigationBar frame height to 44px. However, it doesn't work because something is getting called which is resetting my views frame. Does anyone know what gets called after a UIApplicationDidBecomeActiveNotification gets triggered that resets the viewcontrollers frame?
In Your application any class can be an "observer" for different notifications. When you create view controller, you can register it as an observer for the UIApplicationDidBecomeActiveNotification and specify which method that you want call when that notification gets sent your your application.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name: UIApplicationDidBecomeActiveNotification object:nil];
remove observer in ViewWillDisappear/viewDidDisAppear/Dealloc as per your need:
[[NSNotificationCenter defaultCenter] removeObserver:self];
I'm having the same problem, if you attach an observer via Key Value Observers you can see that something is called after the UIApplicationDidBecomeActiveNotification.

How does iOS decide which objects get sent didReceiveMemoryWarning message?

I am working on an iPhone application in which a number of UIViews are dynamically added to and removed from the main UIWindow.
When simulating low memory errors in the simulator, I have discovered that not all view controllers receive the didReceiveMemoryWarning notification. Unfortunately, these are the controllers that would most benefit from implementing this method.
I cannot seem to find good information about where and how the method gets called. I have read mentions that it gets sent to "all UIViewControllers", but this is evidently not the case. Adding a breakpoint in one of the classes that do receive the notification wasn't particularly enlightening either.
This is a complex project but one way these views get added is:
- (void) showMyView
{
if(!myViewController){
myViewController = [[MyViewController alloc]init];
[window addSubview:myViewController.view];
}
}
MyViewController is a subclass of another class, MySuperViewController, which is itself a subclass of UIViewController. None of those classes have corresponding NIBs; view hierarchies are created programatically.
I am looking for pointers to how I can go about diagnosing the problem.
When you are using .view of the view controller directly, there's a high chance that your view controller won't receive many notifications because it's not the correct way of using view controller. The UIWindow is special case, because the window can automagically know the controller of the view and direct the message to the controller correctly.
However, when you detach your view from UIWindow, the view controller is also detached and not managed by UIWindow any more. I think this is the source of the problem.
I would suggest that you add a navigation controller or tab bar controller as your root view controller, and use that view controller functionality to switch between your child controllers. Note that you should not remove your view controllers when switching so they will be able to receive the messages appropriately.
You might also considering releasing your view controller when not used if initialization of your view controller is trivial and not consuming too much time.
Somewhere in your code you are probably doing something like this:
[[NSNotificationCenter defaultCenter] removeObserver:self];
The only safe place to do this is in -dealloc.
Everywhere else, you should specify the notification that you want to unregister for (this will still potentially break if you register for the same notification as a superlcass).
From the documentation
The default implementation of
[didReceiveMemoryWarning] checks to
see if the view controller can safely
release its view. This is possible if
the view itself does not have a
superview and can be reloaded either
from a nib file or using a custom
loadView method.
This method gets called when a Memory Warning "happens"/is simulated. When the memory is low, the system probably posts a notification and a view controller responds to the notification by calling didReceiveMemoryWarning.
If you do not override the method, the default implementation (described above) is called. All view controllers in memory receive the Memory Warning and call this method. They just don't do anything if it is not "safe" to release the view.
In a simple test application with a navigation controller, in both the current view controller and the one previously displayed, didReceiveMemoryWarning is called. I don't know how the NSNotificationCenter works exactly, but it knows who registered for the UIApplicationDidReceiveMemoryWarningNotification. It is probably set up something like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didReceiveMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
For more information, you can look at the Memory Management section in the UIViewController Class Reference.
I entered this question, searching for the right observer dealing with memory warnings. For those using swift, you can register as followed:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveMemoryWarning:", name:UIApplicationDidReceiveMemoryWarningNotification, object: nil)
With callback method:
func didReceiveMemoryWarning(notification: NSNotification){
//Action take on Notification
}
Also, make sure your custom class inherits from NSObject, or you'll be getting this error:
… does not implement methodSignatureForSelector: — trouble ahead

App Delegate - Unload View Controller

I'm trying to unload a view controller from view when the iPhone goes to sleep. My app has a stopwatch that has to keep counting when the phone goes to sleep or call comes in or the user closes the app without logging out.
I have all this functionality in place, I'm capturing all start times and stop times and upon re-entering the stopwatch view controller, I calculate the difference. It all works beautifully. When I was doing some additional testing I realised I hadn't catered for the iPhone going into sleep mode.
So all I need to do to make sure my stopwatch is correct bring the user back to the app home screen. I know the following method is called when the app goes to sleep:
-(void)applicationWillResignActive:(UIApplication *)application
How do I unload the stopwatch view controller from my app delegate ?
---- UPDATE ----
kpower, thanks for your feedback. I've implemented the following code:
In my App Delegate:
- (void)applicationWillResignActive:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"AppIsAsleep" object:nil];
}
In my view controller, I have the following:
-(void)viewDidLoad
{
// Add Observer.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidUnload:) name:#"AppIsAsleep" object:nil];
}
- (void)viewDidUnload {
//Remove the Observer.
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"AppIsAsleep" object:nil];
}
When the phone goes to sleep, it actually closes the app, am I doing something wrong ?
Regards,
Stephen
You can use the Notifications mechanism. It allows you to unload view controller from different place (not the AppDelegate) this case.
For example, in your view controller's viewDidLoad method you add an observer (don't forget to remove it in viewDidUnload) and in applicationWillResignActive: method of AppDelegate you just simply post notification. That's all.
↓ Update here ↓
When you get a notification - you should manage view controller's removing by yourself. And calling viewDidUnload here is not the solution, cause this method is called after view controller was already unloaded and doesn't cause removing.
How to remove? Depends on how the view controller was added (for example, popViewControllerAnimated for UINavigationController). The main idea here is to make object's retain count equal to 0 (as you know this case an object will be destroyed) - so you should sent release message necessary amount of times.

sending data to previous view in iphone

What are the possible ways to send data to previous view in iphone. Without using Appdelegate. Because there are chances for my view class to be instantiated again.
I believe the best approach is using the NSNotificationCenter class.
Basically what you do is register an object (as an observer) with a notification center.
So for example if you have objects A and B. A registers as an observer. Now lets say A is the "previous" object you are talking about, you can have B send a notification (data or message) to the notification center which then notifies object A (and any other registered observers).
Example:
In file ClassA.m register as shown below:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSomething:) name:#"SomethingHappened" object:nil];
didSomething is the method which receives the notification sent by object B. This will look something like
- (void) didSomething: (NSNotification *) notify {
...
}
Finally you send the message below from whatever method in ClassB.m to notify/send data to object A
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomethingHappened" object:self userInfo:your_data];
Seems convoluted but it's the best approach in my opinion (and quite simple once you understand it :)).
There are several ways to achieve data sharing, with Singleton Objetcs being one of the most popular:
Objective C Singleton
If the view you want to communicate with is a parent view (e.g. the previous view's view controller is where you created this view) then you probably want to handle dismissing the view in the previous view controller. When you do that, you can read the data that has changed and update the previous view controller with the new data.
Then in the viewWillAppear: method of the previous view controller, update the actual views to reflect the current state of the view controller.
Edit: I've just noticed that your newView is transparent. If this is the case, then you certainly want to route all logic through your view controller. You should only have one view controller with visible views at a time.