This might sound like duplicate, but I need farther understanding and explanation about my Scenario please.
In my app, I have one InfoViewController, which represents information when called (pushed)from other Controller like, 1)HomeViewController, 2)FavouriteViewController and 3)DownloadViewController. All 3 held by UITabViewController
InfoViewController had about 10 buttons and corresponding actions. I use separate singleton to hold all info objects.
All 3 ViewController(Held by TabVC)--> Loads object to Singleton --> InfoViewController uses that to Present Detail Info
HomeViewController - Its for new 'Info' to present,which changes everyday
When Information is present,
user can mark it as a Favorite, which then Listed(In UITableView) on FavoriteViewController.
User can save it in phone for future reference, which will be listed(In UITableView) on DownloadViewController.
On selection of cell all 3 represents details using InfoViewController.
Now I want to present only one instance on InfoViewController to be visible to user. Not all in all 3 tab. Currently I am switching it back to Main screen with ViewDidDisappear method. Which works only when I add InfoViewController as child to main 3 VC. Not by push.
Now my problem is, i tried to use Appdelegate to initialized SharedInfo Object
sharedInfo = [InfoViewController alloc]init], but it goes to black screen. I have to initialize it as
[self.storyboard instantiateViewControllerWithIdentifier:#"InfoViewController"];
but this is not allowed in AppDelegate or making shared instance on InfoViewController itself.
How do I achieve Only one Instance present to user at a time??
I think rephrasing what you need might help. I don’t think you really want a shared instance of the UIViewController. What you want is a single source of the data which is displayed in one or more views and a method to update that data in all the views when the data changes.
Two possible solutions are:
NSNotification
Any view which contains the shared data subscribes to notifications when the data is changed. When data is updated via the singleton it sends a broadcast to anyone listening to update the view data.
View Lifecycle
In this second method the data in a particular view is only updated when the user brings up that view. Note in a tab bar interface that the viewDidLoad is called when the view is initially loaded. DO NOT update your data here if it changes. Instead you want to update the data in viewWillAppear as this is called every time you navigate to that view. In viewWillAppear you have code that checks the data and updates it if needed.
There are other ways to skin this cat, but either of the two above should work for you.
Related
I have a navigation controller with 4 view controllers on the stack. I need to access a function in ViewController1 from ViewController4. What is the proper way to do this?
Do I pass the reference to ViewController1 through ViewController2 & 3 then access the function using a protocol delegate?
Do I store a reference to ViewController1 in a struct then access it from there?
Number 2 is what I am currently doing. I set the reference when I leave ViewController1 then set that reference back to nil when I'm done with it.
Why I need to do this:
VC1 has a tableview with a bunch of items. The data in these items is edited in VC4. Once editing is done in VC4 I want to save, pop to root, and reload the tableview with the new data.
Your plan is all wrong. What you should be doing is have a data model that can post notifications about changes in its data. There should be no link whatsoever between the view controllers. VC1 should be prepared to listen for notifications from the data model. When VC4 updates the data model, the data model will tell anyone that is listening that it has been updated.
With this setup, any number of view controller can all be listening to the same instance of the data model being passed around. Any part of your app can respond as needed to these notifications. The best part of this design is that no class has any knowledge of any other specific class except everyone knows about the data model.
The data model has no knowledge of any controllers or views.
No controllers have any direct link to other controllers except for one that needs to present another.
I think you can use notification. Make VC1 to subscribe the notification. When need, in VC4, send the notification.
I'm looking into the viewDidLoad and viewDidAppear methods to better understand what they both do and I came across an article which uses the example of a banking application to explain how these methods work:
Consider a banking application that shows your current balance. A user
can tap on a button, which presents a list of nearby ATMs in a modal
view controller. In order to get the list of nearby ATMs, the
application must make a core location and web service request.
In this situation, a programmer could get away with requesting the
list of nearby ATMs from the server in viewDidLoad. The view
controller is only presented once, both viewDidLoad and
viewWillAppear: will be called back to back and only once for that
particular view controller instance. The net effect of the code in
either of these two methods will be the same.
But this is a bad idea. Consider what would happen if you wanted to
move the ATM view controller into a tab bar controller. Now, the ATM
view controller – with its ATM fetching code in viewDidLoad only
fetches the list of ATMs once. So you are in Atlanta on Tuesday, open
up your application to look for an ATM, then check your balance. Then
you travel to New York on Wednesday, re-open the banking application,
and you only see ATMs in Atlanta. The view was already loaded, no need
to call viewDidLoad and now you’re looking at stale data.
Sadly, I still don't fully understand how/why both viewDidLoad and viewWillAppear will be called 'back to back', or what adding the ATM view controller to a tab bar controller means in terms of these methods.
viewDidLoad method will call only once a life time of viewController and that is when viewController object will first load in memory.
where as viewWillAppear method will call every time when a view will appear to screen or you can say will be topViewController...
Explanation:
Consider you have tab based app with two tabs. Tab1 associated with viewController1 and tab2 is associated with viewController2. Now when you will run your app and you will see tab one is selected and viewController1 is on view and you want to change to tab2, when you will tap on tab2 then tabVieController2's object will create and load to memory first time hence its viewDidLoad method will call, and soon after that it will appear to window and viewWillAppear will also get call.
Now if you you try changing tabs by click on them only viewWillAppear methods will get called for both, as they are in memory already.
It simple, viewDidLoad get called when the view is load in, either via NIB, storyboard or with the loadView method. The viewWillAppear: is called when the view is presented.
When a view is added to a tab bar it only gets load once, thus the viewDidLoad will only be called once. But if the user switch to an other tab and back to the same view the viewDidLoad will not be called. This is because the view is already loaded.
However the viewWillAppear: is called in both cases just before the view is shown. Thus this will be called when the user first opens the tab and when it switches back to that tab.
I think they are referring to the fact that the view is loaded every time the modal view controller appears (thus the data is constantly refreshed) but only once when it is part of tab bar (only loaded on app launch). Kind of a whacky example to explain the methods though.
You might want to read up on the view controller lifecycle to know when to implement what in which method:
Responding to Display-Related Notifications
View Loading and Unloading
I'm using SWRevealViewController in my app (to get the slide out side panel) however whenever the user navigates to another viewController like 'settings' and comes back, everything gets reset. I understand this is normal behaviour for storyboards since a new VC is instantiated and viewDidLoad is called each time. I tried to get around this by storing the VC in an array in the AppDelegate and then going back to the original viewController, this prevented viewDidLoad being called when the original VC is initially re-presented but I still found it get's called randomly when moving between veiwcontroller's, resetting all my properties etc.. On researching, the Apple documentation does say not to assume viewDidLoad will only be called once.
Is this behaviour apparent in UITabBarController when switching tabs as I'm thinking of ditching the SWRevealViewController and using that instead if it's going to be less headache.
Should I be handling this differently, ie. storing the 'state' in NSUserDefaults and restoring on viewDidLoad?
Thanks in advance.
viewDidLoad is called exactly once, when the UIViewController is first loaded into memory. This is where you want to instantiate any instance variables and build any UIViews that live for the entire lifecycle of this UIViewController.
In UITabBarController also the viewdidLoad for UIViewController is called once, when you are switching tabs.
viewDidLoad() method is called only once. Its an integral part of the cycle.
It is called then the respective UIViewController class is loaded into memory.
And yes, if you want to initialise any properties or access and modify the NSUserDefaults, it can and should be done in the viewDidLoad method.
As for your app, whenever the user will switch between different UIViewControllers, the viewDidLoad method will be called for every destination UIViewController.
Also, as correctly pointed out, it'll also be called in the case of a memory warning.
Very simple use case: Let's say an iOS app displays a MovieListController view (inside of a UINavigationController) with a list of movies. When the user touches on one, the app pushes a MovieDetailController onto the navigation stack (i.e. [[MovieDetailController alloc] initWithMovieId:(NSString *). In the MovieDetailController's viewDidAppear: method, it makes an HTTP call to retrieve details based on the movie ID passed into it.
The challenge is that the MovieDetailController gets pushed onto the navigation stack right away, and for a second or two while the details haven't been retrieved, the view shows a bunch of blank fields, which is undesirable.
To get around this, I'm thinking of having the MovieListController not push the MovieDetailController onto the stack right away. Instead, it would put up a progress indicator (I'm using SVProgressHUD), then call MovieDetailController's initWithMovieId: method which would kick off the HTTP call. Then when the data is received, the MovieDetailController would make a callback back to MovieListController to remove the progress indicator and then push the MovieDetailController onto the navigation stack.
Is there a better pattern for this type of scenario? Should I be considering having the MovieDetailController push itself onto the navigation stack when it's ready?
Note: I have considered loading the detail view and putting up an activity indicator, but you'll still be able to see an 'empty view' behind it which looks a bit weird. I have also considered just having the MovieListController retrieve the details itself but this seems to break the encapsulation model - the MovieListController should just be concerned about listing movies, not about their details.
Any thoughts? This Movie stuff is just an example - looking for a general pattern here.
Personally I would take the following approach.
User selects the movie they want details for
Push to the detail view and rather than showing your skeleton view with empty fields, overlay a loading view, you could continue using your progress HUD on top of this for any animations you gain with that.
Once the results come down, remove your HUD and the loading overlay view that is hiding all the data/fields
The reason I would go this route rather than showing the HUD before pushing the view controller is that you can give the user the opportunity to cancel their selection. I am not familiar with SVProgressHUD but hopefully when a HUD is displayed you can enable touches, specifically the user touching Back on your UINavigationController in the event they accidentally selected the movie or the request is just taking longer than they are willing to wait for.
It also separates the logic form your list view, your detail view is standalone and could be initialized anywhere in your app (maybe you want to cross link similar movies within a movie detail view) and you do not need to rewrite the logic of the presenting view waiting on the results to come back.
In this situation, I personally would return to the model-view-controller pattern.
There are several system apps that display a detail view from a list of objects, e.g. Calendar, Contacts, etc. Presumably, as with EKEvent and ABPerson in their respective apps, the main view controller maintains a list of the model objects. When the user selects one of the items, the main view controller passes the selected model object to the detail view controller. The detail view controller itself doesn't have to do any data loading. So, like #ChrisWagner said, we want to separate the logic from the view controller.
Method
Similarly, you might want to use a MovieList class that stores an array of Movie objects. Each Movie stores the values for all the fields in the detail view controller - essentially, all the information the app needs about the movie. For example, you might have a property NSString *movieTitle, or NSDate *premiereDate. The movieTitle would be set by the MovieList at initialization because it's just metadata; on the other hand, the premiereDate might be nil if the data hasn't loaded, so you would have a property BOOL isLoaded to check for this condition.
You could then proceed in one of two ways:
1) Say the main view controller wants to push a detail view controller for a movie. Then the main view controller would dig the appropriate Movie out of the MovieList and check if it's loaded. If not, it would call something like -(void)loadContents on the Movie. When the model object is finished loading, it will post a notification that it's finished loading. At this point the main view controller will dismiss its progress view and push the detail view. If you use (1), it's not as important to use a MovieList coordinator.
2) If you want to be more aggressive about loading the movie information, you could implement a method on MovieList that calls loadContents on its Movies in the background. Then there's a higher chance that a movie will already be loaded when the user selects it.
Edit: Note that if you decide to use a MovieList type object, only the main view controller should be allowed to access this list. In a way, the model structure I've described parallels the view controller structure. The detail view controller doesn't know about the list view controller, so it shouldn't know about the MovieList either.
Benefit
The benefit of using this Movie and MovieList structure is that the data model is completely separated from the view controllers. That way, the user is free to cancel, select, present and dismiss view controllers while the data is loading. (Imagine if the user pressed Back to dismiss the progress HUD, canceling any HTTP data it would have gathered. Then, if he decides to come back to that same movie, the new detail view controller has to start loading afresh.) Also, if you decide to replace view controller classes in the development process, you won't have to copy and paste a bunch of data-handling code.
I think you'll find that structuring your app in this way not only gives you and the user more freedom, it also makes your app efficient and open to later extensions. (Sorry to post so late, but I wanted to include MVC in this pattern-related discussion!)
I think a more advanced route would be to start the detail requests as the cells are being being shown on in the tableview/collection view. Then as the cells move offscreen, cancel the requests. You might be loading movie details unnecessarily, but I don't think this is a big deal, because you'd only be filling your db with movie details you might not need and coredata can handle that. This is assuming that your api requests are happening on a bg thread and they don't affect the main(UI) thread.
So, I followed this tutorial: http://enroyed.com/ios/how-to-pass-data-between-ios-tab-bars-using-delegate/
And the most important part of the tutorial:
- (void)viewDidLoad
{
[super viewDidLoad];
SecondViewController *svc = [self.tabBarController.viewControllers objectAtIndex:1];
svc.delegate = self; //important !! otherwise delegation will not work !
}
The problem is that even if I put it in "viewWillLoad", it still forces me to click on my tab before it initializes. How can I specify this before I click on the tab?
Edit
I have a three tab project. I used that tutorial in the link pass data from tab 1 to tab 2. The data passed is a url from a webview on tab 1 to a url on tab 2. The url gets pass when I click a link on the 1st tab.
The data does get passed, but only if I physically click on the 2nd tab first and then click back to the 1st and click on the link.
So, it appears to me that my code above only runs if I physically click on the 2nd tab.
Your problem is that - until you actually go to tab item 2, secondViewController is not fully initialised, and so there is no data to transfer from vc2 to vc1. In particular, secondViewController's view has not yet loaded, so there is no value to be had from it's slider yet, and no slider, so also no IBAction method to call to trigger the delegate method. Indeed, as the data transfer is only triggered on moving the slider in VC2, it should be fairly obvious that until you go to vc2 and move the slider, nothing is going to happen.
The example you link to uses the delegation pattern, which seems a fairly poor way to deal with your problem. The delegation pattern most comfortably fits with the scenario where there is a hierarchical relationship between delegator (owned) and delegatee (owner) ... not always, but most commonly. In a tab bar controller, the relationships are more like kindred child relationships to the tab bar controller itself.
You haven't offered enough detail in your question as to what you want to achieve, but you need to consider this:
When a tabBarController loads, all of it's child viewControllers are initialised but their views are not loaded.
This means that these methods do get called:
//if loading from a xib or in code
- (id) initWithNibName:bundle
//if loading from a storyboard
- (id) initWithCoder:
- (void) awakeFromNib
But the view loading methods (viewDidLoad, viewWillAppear etc) do not get called as the view does not get loaded unless you actually open the relevant tab.
You could solve this by putting an initialised variable into your viewController2 (in one of the init methods) and accessing that variable via property syntax from vc1. But then, you might as well just put an initialised value directly into vc1. You need to think closely about how each vc is dependent on the other, how you can decouple that dependency, and perhaps how to set up an independent data source that both vcs can access as needed. This could be a model class, or NSUserDefaults, or a property in your appDelegate... just a few of the many possible solutions.