I'm writing an app that let's the user know when they have a new tip waiting at the same time everyday. I've been able to schedule the notification and write the code for it to fire if the app is running in the foreground. What I'm having trouble with is how to get the same thing to happen when the app is in the background. The complication is that I'm having the view be set up (which buttons are visible etc.) based on the notification itself, as the next step is to have a second notification which would have the view set itself up differently. At the moment, my code in the method looks like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UILocalNotification *note = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
NSString *val = [note.userInfo objectForKey:1];
if ([val isEqual: #"first"]) {
}
return YES;
}
My issue is that I have no idea how to set up the view (i.e. how to make two of the buttons [call them button1 and button2] visible within the view) from within that if statement. Any answers would be greatly appreciated, and if this is something unbelievably obvious, then I do apologize and ask only that someone at least point me in the right direction. Thanks
Not 100% on this but believe that
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
Finishes before starting to load the views, well it makes more sense this way.
Having this in mind, and been used with success by me, i advise you to set up a public var on app delegate that stores the information you need.
After that is just checking in the
- (void)viewDidLoad
Of your viewController if the var as anything and treat the information as you would when the app is in Foreground.
I will try to give you a more general answer to your problem: iOS programming is based on the MVC (Model-View-Controller) software architecture pattern so you must make your application to be as much as possible compliant with MVC. This means, in your case, that as soon as you receive a local notification, whatever your app is running in the foreground or sleeping in the background or simply ready to startup from scratch, the app will behave in the same way if the MVC bricks are properly setup.
So:
the local notification provides some new data to the app, so you must update the model
once you update the model you notify the app view controller of the model change
the view controller if visible will reflect the change in the view that represents the model, if not visible your app must show the view controller and then refresh the view based on the model status.
Clearly how to do this depends on how and where your app shows the different views, but to keep things simple you can:
create the model that reflects the local notification at startup (application:didFinishLaunchingWithOptions:)
create the view controller that show the local notification data at startup (note: you "init" the view controller by providing it the model, you don't care the view yet)
then handle the local notification after the model and view controller have been initialized
So when you receive the local notification the app:
updates the model (which exists in all three cases when the app is in foreground or resuming from sleep or starting from scratch)
at this point if the view controller view is already visibile then you simply refresh it with the new model data, if not visible you add the view controller view in your view hierarchy (e.g. by pushing it in your main navigation controller stack); the view refresh will be done by viewDidLoad:
As you can see you don't need any special code to manage this, apart the need to show the local notification related view when the app gets the notification. Clearly things are much more complex if your local notification view is deep in your view hierarchy and you must maintain some consistency: in such case you must rebuild the whole hierarchy and the possibility to do this depends on the information provided by the local notification.
In the simple case, that is when you can place your local notification view controller just inside the main window hierarchy, after you instantiate your main navigation controller:
[applicationWindow setRootController:mainNavigationController]
you will push your local notification controller in the nav controller stack:
[mainNavigationController pushViewController:myLocalNotificationViewController animated:YES]
Sorry if I'm not entering in the detail of your specific problem (I cannot) but probably my explanation will help you in letting you understand that your case is not a special case but can be easily managed by carefully following the main iOS architectural pattern.
Related
I am building a music player app -- in this app, the music player view controller will always sit on top of any other sub-viewcontroller (navigation view, table views, etc) I need actions taken in any potential subview to be sent back up to the player view controller (for example, user selects "play" on a profile page, and I send that event back up) My question is what is the best way to do this? I apologize in advance for being a bit nebulous, but I already know of three ways I can implement it. I just want to know what the "right" way is.
These are the three ways I've thought of:
1.Delegate pattern -- pass the Music Player Controller off to it's children controllers and set itself as the delegate for whenever that event is passed (messy because the first view controller is a navigation view controller, so I think I'd have to pass it down several levels meaning several delegates (correct me if I'm wrong))
2.Notification Center -- register the player view controller for a particular notification, encapsulate the data that's sent from the other viewcontrollers so that I can perform my actions.
3.Singleton-like access of the player view controller - basically allow access to the player view controller from any view controller.
Any help is appreciate to steer me in the right direction. I can do it either of these ways, but as this is a "learning" app, would love to do it right.
IMHO there is no "right way". Frankly I was thinking of all three of them when I read the subject line only.
As you are asing for opinions... I would not recommend the singleton pattern here. This is just because view controllers could stack and by their nature be instanciated multiple times. In terms of maintainable code (readability by others) I'd say no to that approach.
The delegate pattern is fine. But as you say you would have to pass a reference to this view controller from one view controller to the next. Yes, a bit messy.
Well, you could store a reference to the delegate in some singleton. That is not the same as designing a view controller as singleton.
I'd say that the notification center is most appropriate for this type of data flow. Sender and receiver of the message are totally detatched and don't need to 'know each other'. It is a perfect pattern for all kinds of multiple senders and/or mulitple receipient type of messages.
Well, as I said, this is just an opinion.
I would not recommend broadcast notifications (via NSNotificationCenter), they are hard to debug, other developers will have problems to understand, whats going on in your application (application flow), every developer must maintain a global list with all notification names (notificationSender and observer must use the same notification name and they are usually constant string variables), observer can not send back any data after receiving a notification, etc. Broadcasting is really helpful, if all controllers should be notified of the same event (for example Login/Logout events).
If possible, i think one should allways try to use a Delegate Pattern (with clearly defined protocol). Maybe something like:
id <SubViewControllerEvents>musicPlayerVC= [MyMusicAppManager delegate];
if ( [musicPlayerVC respondsToSelector:#selector(userDidSelectPlay)] ) {
[musicPlayerVC userDidSelectPlay];
}
This question already has answers here:
How can I wait for a NSURLConnection delegate to finish before executing the next statement?
(3 answers)
Closed 8 years ago.
I am running on Xcode 5.1. I am working on a school project where we need to load tweets in application:didFinishLaunchingWithOptions:, and after that we need to display the fetched data on a view controller.
I am using interface builder because I think it is simpler, so to answer my question please keep in mind that I am looking for solution with interface builder.
My problem is:
The tweets loading function in application:didFinishLaunchingWithOptions: in AppDelegate runs in a separate thread. I know if you are programmatically pushing the view controller in AppDelegate then you can wait for the tweets to be loaded before you initialize the view controller. But since I am using the interface builder the view controller is always initialized at start, even before the tweets loading function is finished. Therefore I am looking for a way for function in AppDelegate to notify the instance of the view controller that story board created, to reload its data once all the required tweets are downloaded.
Simply put:
How do I access the instance of any view controller that's created by the interface builder from AppDelegate? Is this a right practice?
I've googled a lot of solutions but all of them involved creating a new instance, which is different than the one that is already created at run time.
I think what you are looking for is something along the lines of the following:
You want asynchronous data loading when your application begins
You don't want the user to be able to interact with the app until there is data to display.
What I would do for this is:
Dont fetch the data in did-finish-launching
Get the instance of your viewcontroller by using the 'rootViewController` property :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
MYVIEWCONTROLLER *controller = (MY_ROOT_VIEW_CONTROLLER *)self.window.rootViewController;
[controller triggerFetch];
...
return YES;
}
in triggerFetch you would create add a UIActivityIndicator or progress bar to the main view and add a callback for the fetch function. After the fetch is complete you would remove the loading view and display the fetched content.
If your UI elements are bound to the model properly, changes to your model should reflect in the view automatically.
I'm not sure exactly what UI interface elements you are using. I'm assuming you have a UITableViewController displaying your tweets, and you are using your appDelegate as the UITVCDelegate as well to give it info on how many cells, dimensions, and the data for each cell inside its UITableView. And in the end you want it to reload data after you are done with fetching the tweets.
The simplest way would be to call [tableView reloadData] on the tableView (You can access it from the UITVC). But, that's just based on my assumptions being true about you using a UITableView to display the tweets.
Cheers!
I have several view controller objects each can take in some user inputs from UITextField, save inputs to mutable arrays and display in a UITableView.
I also want these mutable arrays to be saved in files when home button is pressed by the user, so I found the code below in AppDelegate.m:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
It seems this method is perfect for saving user data whenever the user presses home button, but my question is how do I access these mutable arrays declared in different view controllers in order to save them into files? I can certainly make pointers in AppDelegate and make them point to each view controller object, but I know views can be unloaded when the program is running low on memory; therefore, if I make these pointers in AppDelegate then these view objects can never be unloaded when the memory is running low(because of strong references). What should I do? Thanks!
The general design pattern for user interfaces is the model view controller. The data storage ('model') is held in a separate object to the view controller.
For example, you can create an object that stores all of your applications data, instantiate it in the AppDelegate didFinishLaunchingWithOptions: method and store the reference in a property of your AppDelegate. Then each view controller can use [UIApplication sharedApplication].delegate.myData to retrieve the reference.
You need to get the MVC (model/view/controller) religion. Don't mix model (the NSMutableArrays) with controller (UIViewController). If you keep all the model data in separate classes then you don't have to worry about whether the view controllers exist or not. Also, it becomes super easy to keep your program logic clean. Put all the saving/loading stuff in the model classes. You can have your model classes listen for the UIApplicationWillResignActiveNotification and save when they receive it.
Unfortunately Apple's templates tend to push people toward putting a lot of stuff in app delegates or view controllers. Only really app global stuff should go in app delegate. Only controller code (that mediates between model and view) should go in a view controller.
Inside other view controllers you do this
id myid = [[UIApplication sharedApplication] delegate];
Please replace the "id" with the name of your AppDelegate Class (usually AppDeletegate).
You of course have to #import the AppDelegate in the top of your implementation file.
I suggest to listen for UIApplicationWillResignActiveNotification.
Also in iOS6 and later views are not unloaded when the memory is running low
(void)viewWillUnload Discussion In iOS 5 and earlier, when a low-memory condition occurred and the current view controller’s views
were not needed, the system could opt to remove those views from
memory. This method was called prior to releasing the actual views so
you could perform any cleanup prior to the view being deallocated. For
example, you could use this method to remove views as observers of
notifications or record the state of the views so it can be
reestablished when the views are reloaded.
In iOS 6 and later, clearing references to views is no longer
necessary. As a result, any other cleanup related to those views, such
as removing them as observers, is also not necessary.
What ways are there to change views other than using a navigation-based app? I want my first view to be basically just a simple form and when the user hits a "submit" button I want to send him over to the main view, without being able to return. And I don't want the bar on the top of the view either.
How can I achieve this, and if possible without losing the animations that come with a navigation-based app.
If you wanted your app to be entirely navigation controller free, you can use one of the signatures of presentModalViewController:animated: from whichever UIViewController you deem best fit to be the parent. Call [self dismissModalViewControllerAnimated:YES] on the child view (form you want submitted) after you've handled state change on submit. One thing to watch out with this, is as of iOS 5, Apple now prefers that you use presentViewController: instead, and presentModalViewController: is marked for deprecation at a future date.
In terms of "how you would know the user submitted the form, so they may now proceed in your application" - one way you could do that is to use delegation / notifications to maintain awareness of the state of the form. When the child form is submitted, you can call the parentViewController's delegate callback to set flags - or return authentication data, for example - in your AppDelegate or some high-level class. Delegation and Notifications are useful tools when using the iOS SDK.
Option two could be using a completion handler in with your call to present the child, such as:
ChildForm *childFormWithSubmit = [[ChildForm alloc] init];
[self presentModalViewController:childFormWithSubmit animated:YES
completion:^(/*inlineFunctionParams*/)
{ /*inlineFunctionBodyToRunOnCompletion*/ }];
Lots of possibilities ~
Did you look at 'presentViewController:animated:completion:' in the UIViewController class description? There are lots of options for how you animate in another viewController.
Sled, you can simply just hide the UINavigationBar for your UINavigationController.
That way you won't see the UINavigationBar and the user will not be able to return back to that page.
You'll need to set a permanent flag in your app either writing to text file or using NSUserDefaults.
This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.