I am new to iOS development and I am writing a 3-View Location Application.
The first view is the main view of the application, the second view is a table view with several locations and the third view is a detail view, where the user is able to edit or add new Locations to the table view.
I'm using the CLLocationManager in the first and in the third view but every view of both has got his own CLLocationManager instances, because for the detail view I need the best accuracy whereas in the MainView I dont need the best accuracy.
So here is the problem:
In my AppDelegate.m I have got a notification, which fires when the Applications enters foreground:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName: #"didEnterForeground" object: nil userInfo: nil];
}
In my third view, the DetailViewController.m, I register in the viewDidLoad for this notification:
- (void) viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(enteredBackground:) name: #"didEnterBackground" object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(enteredForeground:) name: #"didEnterForeground" object: nil];
}
The enteredForeground Method in DetailViewController.m just needs to start the Location Manager again (the didEnterBackground Method stopped him)
- (void) enteredForeground: (NSNotification*) notification {
[self.locationManager startUpdatingLocation];
}
I am using XCode 4.2 with ARC.
The problem is, that if I visited the DetailView for about 10 times, go to background (f.e. from the MainView), then I enter foreground again then 10 LocationManagers will be started immediately (this is what my NSLog says).
It seems that for the DetailView (and for the other Views) the same number of instances like the number of visits for these views exist.
Maybe the views dont get released properly if they disappeared, perhaps because of the NSNotification?!
I would appreciate if someone could help me with this matter, cause so many LocationManagers will stress the battery pretty hard.
Thanks in advance!
I believe you need to stopUpdatingLocation when it goes to the background. Otherwise, it will spawn multiple instances.
Related
I bult an iOS app and in -(void)viewDidLoad I parse data from web, and display it on load. But often when I open my app it displays old data(app loads fast) and I need to kill it and open it again, after that it parses data and shows new. Why is that happening?
It doesn't work that way because viewDidLoad is only called once, when the view is created. After backgrounding and returning, your view still exists.
If you want to reload your data whenever the app returns from the background, you need to either override applicationDidBecomeActive: in your UIApplicationDelegate implementation, or you need to listen for the appropriate notification:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
// reload your data here.
}
Don't forget to remove yourself as an observer when you no longer need the notification.
I am fairly new to iOS dev. I have been trying to find an answer to this but don't have a definite solution..
I have a view called FirstView.m
And there's AppDelegate.m
A background task is running in AppDelegate.m which updates a variable called `text' depending upon the closest beacon to the phone.
When the app is in FirstView, I want to update a UILabel inside FirstView as per the variable text of AppDelegate.
I know this can be done by running a background thread in FirstView which every 1 second checks whether the variable in AppDelegate was changed or not, but this doesn't seem efficient to me at all, there is no point running two background threads for the same task.
My questions is, is there a way to update the label from AppDelegate itself ? Something on the lines of performSelectorOnMainThread ?
Thanks!
You could post a notification with the text in the userInfo dictionary in the AppDelegate anytime that value changes:
text = [iBeacon updateText]; // just a random method name I made up
[[NSNotificationCenter defaultCenter] postNotificationName:#"textChanged"
object:self
userInfo:#{#"text":text}];
Then in the FirstView you can listen for that notification to update the UI (normally in viewDidLoad or in viewDidAppear:). Be sure to unregister before the view controller is dealloced somewhere in FirstView so there are no memory leaks. Example for registering and updating UI
You can register like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleTextChanged:)
name:#"textChanged"
object:nil];
Then update the UI:
- (void)handleTextChanged:(NSNotification *)notification
{
// Be sure to update the UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
self.label.text = notification.userInfo[#"text"];
});
}
And finally, unregister for notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self];
One more way you can try the same using keyValue Observing. Refer this Apple Documentation and how to implement the same.
I have an application that stores values in the viewWillDisappear method in one of my classes. For example, I store the data and time in the viewWillAppear method and the same again when in the viewWillDisappear method so I'm able to compare the time difference and log this out.
However, if the user presses the home button on the device, then the viewWillDisappear code is not run. I've tried to call this particular method in the AppDelegate's applicationDidEnterBackground, but this stores the wrong information as the viewWillDisappear method isn't actually run. I've also tried to store them in NSUserDefaults, but again, as the viewWillDisappear method isn't run, then the wrong values are stored.
Is there a way to make the viewWillDisappear method run for that view as soon as the user presses their home button?
In the viewWillAppear register for this notification...
-(void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(appEnteredBackground:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
}
-(void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)appEnteredBackground:(NSNotification *)appEnteredBackgroundNotification {
//do your thing
}
Within an App I make use of several viewcontrollers. On one viewcontroller an observer is initialized as follows:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
Even when removing the NSNotification before initializing the number of executions of myMethod: is being summed up by the amount of repeated views on the respective viewcontroller.
Why does this happen and how can I avoid myMethod: being called more then once.
Note: I made sure by using breakpoints that I did not made mistakes on calling postNotification multiple times.
Edit: This is how my postNotification looks like
NSArray * objects = [NSArray arrayWithObjects:[NSNumber numberWithInt:number],someText, nil];
NSArray * keys = [NSArray arrayWithObjects:#"Number",#"Text", nil];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
edit: even after moving my subscribing to viewwillappear: I get the same result. myMethod: is called multiple times. (number of times i reload the viewcontroller).
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
}
edit: something seems wrong with my lifecycle. ViewDidUnload and dealloc are not getting called, however viewdiddisappear is getting called.
The way I push my Viewcontroller to the stack is as follows where parent is a tableview subclass (on clicking the row this viewcontroller is initiated:
detailScreen * screen = [[detailScreen alloc] initWithContentID:ID andFullContentArray:fullContentIndex andParent:parent];
[self.navigationController pushViewController:screen animated:YES];
Solution:
Moving removal of nsnotification to viewdiddisappear did the trick. Thanks for guidance!
Based on this description, a likely cause is that your viewcontrollers are over-retained and not released when you think they are. This is quite common even with ARC if things are over-retained. So, you think that you have only one instance of a given viewcontroller active, whereas you actually have several live instances, and they all listen to the notifications.
If I was in this situation, I would put a breakpoint in the viewcontroller’s dealloc method and make sure it is deallocated correctly, if that’s the intended design of your app.
In which methods did you register the observers?
Apple recommends that observers should be registered in viewWillAppear: and unregistered in viewWillDissapear:
Are you sure that you don't register the observer twice?
Ran into this issue in an application running swift. The application got the notification once when first launched. the notification increases the number of times you go into the background and come back. i.e
app launches one - add observer gets gets called once in view will appear or view did load - notification is called once
app goes into background and comes back, add observer gets called again in view will appear or view did load. notification gets called twice.
the number increases the number of times you go into background and come back.
code in view will disappear will make no difference as the view is still in the window stack and has not been removed from it.
solution:
observe application will resign active in your view controller:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillResign:", name: UIApplicationWillResignActiveNotification, object: nil)
func applicationWillResign(notification : NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
this will make sure that your view controller will remove the observer for the notification when the view goes into background.
it is quite possible you are subscribing to the notifications
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
before self gets initialized. And trying to unsubscribe 'self' which isn't really subscribed to, and you will get all global myNotification notifications.
If your view was hooked up in IB, use -awakeFromNib: as the starting point to register for notifications
It is possible that the class with the observer is, quite appropriately, instantiated multiple times. When you are debugging it will kinda look like the notification is being posted multiple times. But if you inspect self you might see that each time is for a different instance.
This could easily be the case if your app uses a tab bar and the observer is in a base class of which your view controllers are subclasses.
I add this function to post a notification when the app enter foreground:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName: #"UIApplicationWillEnterForegroundNotification" object: nil];
}
In my own class:
- (void) handleEnterForeground: (NSNotification*) sender
{
[self reloadTableData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(handleEnterForeground:)
name: #"UIApplicationWillEnterForegroundNotification"
object: nil];
}
but the handleEnterForeground: function will called twice, I don't know why. The
reloadTableData: function will call remote webService , so when the app enter
foreground, it will stuck for a while.
The system will call that event automatically. The reason it fires twice is because you manually fire it again.
P.S. It's better to use the variable name UIApplicationWillEnterForeground, instead of a NSString literal.
EDIT: I realize now the confusion is coming from the fact that you didn't know that this even name was already taken. As a note to other people who run into this kind of problem, it is a good practice to prefix your event names with your project prefix (i.e. XYZEventNotification) to avoid collisions.