I have an navigation-based app with three view controllers. The first has categories of information, the second has a list of items from that category and the third has detail on a specific item.
I populate view controllers 1 and 2 using an NSXMLParser which gets called on viewWillAppear. In the forward (VC1 to VC2 to VC3) direction, everything is fine, the parser gets called and the views are populated.
Unfortunately when the user chooses the back button on the navigation bar, the same process happens in reverse (VC3 to VC2 to VC1) as viewWillAppear is called again and so the parser is also called, even though it just fetches the same data.
I want to ensure that the parser is only called in the forward direction. Any ideas how I might structure this?
Thanks, Phil
Could you do something as simple as having a BOOL ivar called goingForward on VC2 that gets set to YES by VC1 prior to -pushViewController:animated: call and set to NO by VC3 prior to -popViewControllerAnimated: ... and then check goingForward in VC2's -viewWillAppear?
I am sure that there are more elegant ways of doing this (and look forward to reading about them) - but this should work, don't you think?
Related
i initialize tables, data etc in my main ViewController. For more settings, i want to call another Viewcontroller with:
DateChangeController *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"dateChangeController"];
[self presentViewController:theController animated:YES completion:^{NSLog(#"View controller presented.");}];
And some clicks later i return with a segue (custom:)
NSLog(#"Scroll to Ticket");
[self.sourceViewController presentModalViewController:self.destinationViewController animated:YES];
My Problem:
After returning to my main ViewController, viewDidLoad is called (everytime).I read, that the ARC releasing my mainView after "going" to the other ViewController and calling the viewDidUnload Method, but i want to keep all my data and tables i initialize at the beginning..
Any solution? Thank you very much!
The problem is that you are doing this:
main view controller ->
date change controller ->
a *different* main view controller
In other words, although in your verbal description you use the words "returning to my main ViewController", you are not in fact returning; you are moving forward to yet another instance of this main view controller every time, piling up all these view controllers on top of one another.
The way to return to an existing view controller is not to make a segue but to return! The return from presentViewController is dismissViewController. You do not use a segue for that; you just call dismissViewController. (Okay, in iOS 6 you can in fact use a segue, but it is a very special and rather complicated kind of segue called an Unwind or Exit segue.)
If you do that, you'll be back at your old view controller, which was sitting there all along waiting for your return, and viewDidLoad will not be called.
So, this was a good question for you to ask, because the double call of viewDidLoad was a sign to you that your architecture was all wrong.
I think you're taking the wrong approach - viewDidLoad is supposed to be called when it is called - it's a notification to you that the view is being refreshed or initially loaded. What you want to do is move that table initialization code somewhere else, or, at least, set a Boolean variable so that it is only called once. Would it work to create an object that has your table data when viewDidLoad is first called, then to check it to see if it's already been called?
Ok I am trying to refresh the tab content of each of my tabs after a web call has been made, and I have tried soo many different methods to do this that I have lost count. Could someone please tell me how this is possible?
The web call just calls JSON from a server and uses it to update the content of the tabs. For testing purposes I have a button set up inside my settings class. Settings class is a view within the home tab which has a button called refresh. When clicked this takes JSON stored on the device which is different to the one called from the web call on application start up. This saves me having to change the JSON on the server.
I will take you through some of the techniques I have tried and would be grateful if someone could tell me what I am doing wrong.
I tried making an instance of the class and calling the refresh method like this
DashboardVC *db = [[DashboardVC alloc] init];
[db refreshMe];
The refresh method in dashboard class is this
-(void) refreshMe
{
[self loadView];
[self viewDidLoad];
}
However no luck. This method will work if I call it inside the Dashboard class, but wont work if I call it from another class. I think it is become I am instantiating a new class and calling refresh on that. So I dropped that technique and moved onto the next method
This loops through all the tabBars and changes the tabTitles without any issues, so it I know it is definitely looping through the ViewControllers properly.
I also tried every varient of the view methods like ViewDidAppear, viewWillAppear etc, no luck.
I also tried accessing the refreshMe method I made in the dashBoard class through the tabController like this
[[[self.tabBarController viewControllers] objectAtIndex:0] refreshMe];
But again no luck, this just causes my application to crash.
I read through this guide
https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewControllerPGforiOSLegacy/TabBarControllers/TabBarControllers.html
on the apple website but it doesn't seem to cover how to refresh individual tab content.
All I want is to have each individual tab refresh its content after the web call is made, and have spent ages trying to figure this out, but nothing is working.
So would be very grateful if someone could show me what I am doing wrong?
Thanx in advance....
EDIT:
Expand on what I have tried
After discussion with Michael I realised you should never call loadView as against Apple guidelines. So I removed any references to LoadView. I have now placed a method in all the main ViewControllers called RefreshMe which sets up the views, images texts etc in the class. And this method is placed inside the ViewDidLoad. Now I want to be able to call these methods after a web call has taken place, so effectively refreshing the application.
My viewDidLoad now looks like this in all my the main classes.
- (void) viewDidLoad {
[super viewDidLoad];
[self refreshMe];
}
And then the refreshMe method contains the code which sets up the screen.
The JSON data pulled from the web call will set up the content of each of the 5 tabs, so need them all to be refreshed after web call.
I tried looping through the viewControllers and calling viewDidLoad, which should in turn call the refreshMe method which sets up the class, however nothing happens. Code I used was this
NSArray * tabBarViewControllers = [self.tabBarController viewControllers];
for(UIViewController * viewController in tabBarViewControllers)
{
[viewController viewDidLoad];
}
For the time being I have also included
NSLog(#"Method called");
in the viewDidLoad of each class to test if it is being called. However the message is only being printed out when I first load the application or if I re-enter the application. This method should be called after I click the refresh button in the settings screen but it isn't and I have no idea why.
Anyone have any idea why this is not working?
From the question and your comments, it sounds like there are at least two problems:
You're having trouble accessing the view controllers managed by your app's tab bar controller.
You seem to be working against the normal operation of your view controllers.
The first part should be straightforward to sort out. If you have a pointer to an object, you can send messages to that object. If the corresponding method doesn't execute, then either the pointer doesn't point where you think it does or the object doesn't have the method that you think it does. Let's look at your code:
NSArray * tabBarViewControllers = [self.tabBarController viewControllers];
for(UIViewController * viewController in tabBarViewControllers)
{
[viewController viewDidLoad];
}
This code is supposed to call -viewDidLoad on each of the view controllers managed by some tab bar controller. Leaving aside the wisdom of doing that for a moment, we can say that this code should work as expected if self.tabBarController points to the object that you think it does. You don't say where this code exists in your app -- is it part of your app delegate, part of one of the view controllers managed by the tab bar controller in question, or somewhere else? Use the debugger to step through the code. After the first line, does tabBarViewControllers contain an array of view controllers? Is the number of view controllers correct, and are they of the expected types? If the -viewDidLoad methods for your view controllers aren't being called, it's a good bet that the answer is "no," so figure out why self.tabBarController isn't what you think.
Now, it's definitely worth pointing out (as Michael did) that you shouldn't be calling -viewDidLoad in the first place. The view controller will send that method to itself after it has created its view (either loaded it from a .xib/storyboard file or created it programmatically). If you call -viewDidLoad yourself, it'll either run before the view has been created or it'll run a second time, and neither of those is helpful.
Also, it doesn't make much sense to try to "refresh" each view controller's view preemptively. If your app is retrieving some data from a web service (or anywhere else), it should use the resulting data to update its model, i.e. the data objects that the app manages. When a view controller is selected, the tab bar controller will present its view and the view controller's -viewWillAppear method will be called just before the view is displayed. Use that method to grab the data you need from the model and update the view. Doing it this way, you know that:
the view controller's view will have already been created
the data displayed in the view will be up to date, even if one of the other view controllers modified the data
you'll never spend time updating views that the user may never look at
Similarly, if the user can make any changes to the displayed data, you should ensure that you update the model either when the changes are made or else in your view controller's -viewWillDisappear method so that the next view controller will have correct data to work with.
Instead of refreshing your view controllers when updating your tab bar ordering, why not simply refresh your views right before they will appear by implementing your subclassed UIViewController's viewWillAppear: method?
What this means is that each time your view is about to appear, you can update the view for new & updated content.
I've recently started developing for the iPhone and so far I'm doing pretty good but there's this basic pattern I really don't seem to get.
Say, I have a TabBar with two views and a custom delegate protocol, thus my structure is the following:
AppDelegate.h/.m
myDelegateProtocol.h
FirstViewController.h/.m
SecondViewController.h/.m
MainView.xib
FirstView.xib
SecondView.xib
Now I want to achieve the following: I placed a button in the FirstView.xib and I'd like the IBAction which it invokes (inside FirstViewController ofc.) to send a message to the SecondViewController ([self.delegate tellSecondViewContrToSayHi]) and invoke another method which simply prints a log into the console saying "hi I'm here."
So far I know what I need to do in theory:
Specify the protocol.
Implement the protocol in the SecondViewController.
Create an id< myDelegateProtocol > delegate inside my FirstViewController,...AND last but not least:
Set the self.delegate = secondViewControllerObject.
Now, nr.4 is where the problem's at. How on earth do I link the delegate to the other viewController? I mean I'm not the one instantiating the views as the tabBar kinda does that for me,... any advise? Or am I just way too tired to notice a really stupid thing I did somewhere?
Theoretically the same question also applies to the target:action: thing,... I mean, how do I define the target?
Thanks a lot,
wasabi
You have the right idea, assuming that you want relatively tight coupling between these controllers via that delegate protocol.
Since neither controller knows about the other until that delegate property is set you need to have some object which has a reference to both of them wire up that relationship. In your case that's probably the application delegate which can create both controllers, set one as the delegate of the other, and pass both along to your tab bar controller.
What you might actually want is to have the app delegate give both controllers a reference to some shared model object. Your FirstViewController can update that model when you tap a button and your SecondViewController can observe changes to the model to update it's display (or just update its view when it appears based on the current model state). That way your controllers don't need to know anything about each other.
I have a tab bar controller with 4 tabs. Each tab has its own view controller and a UIWebView.
Let's say I have a button (button1) in vc1 and an instance method onClick1 as well. In vc2 I have a method named reload. My question is, how do I access the specific instance method, onClick1 in vc2, from vc1?
For further detail, I'm actually trying to code a simple shopping utility for the iPhone. When a user adds an item to the cart from the browse view, I want to be able to automatically reload the cart view.
Below are some examples of what I mean. This problem has been more difficult than I thought. I'm not sure if I have redesign my application or what. Perhaps have both vc1 and vc2 belong to a subclass of vcmain and have reference to each of them there? However, if I do that, then how do I refer them to their corresponding .xib? Thanks guys!
#implementation viewController 1
//Reloads vc2
-(IBAction) onClick1: (id) sender {
//Calls vc2 reload
[vc2 reload];
}
#end
#implementation viewController 2
//Reload View
-(void)reload {
[webView reload];
}
#end
You've basically got three approaches, each one of which has plusses and minuses. I'm going to give you a high-level overview and let you go to Apple's quite comprehensive documentation for details. Hopefully I can give you the right terms to google for more specific help.
The approach that #dredful quite ably details is to have a handle to the "other" view controller(s) and call methods on them directly. That works fine, but it can be confusing and cumbersome handing pointers to all your controllers around, and traversing the view hierarchy to get get at the controller you want can be very tricky indeed.
The second approach is Key-Value Observing. You can register one view controller to "watch" a particular key (named property) of another view controller, and fire a particular selector when various things happen with it. This is kind of magical and nice, although at some point you have to have pointers to both controllers at the same time, which doesn't entirely relieve the downside of the "call it directly" method above. It is also a sort of unfortunate coupling of view control and data, kind of breaks MVC.
The third approach is using NSNotificationCenter. A class can post a notification, and any other object that registers itself to listen for that sort of notification can be triggered when that happens. It's nice because you might have LOTS of different objects adding items to the cart, and they can just shoot the notification center a note (even passing it an object or arbitrary data, if it wants), and the cart view can consume those notifications, catch the passed objects, and do its thing, not caring in particular who's talking to it. It keeps separate pieces of your application nicely decoupled. The downside is, it's got a bit of overhead to it, and whatever selector the notification-consuming class performs happens synchronously, so you can't hide network activity or some other long process there.
I'm thinking you should already have something like a base UIViewController (let's call it MyTabBars) that has a UITabBarController *tabBarController containing all your Tab Bar View Controllers. If that sounds familiar, you will want a method in MyTabBars called -(void)reloadCart. reloadCart will walk the array of tabBarController.viewControllers. On each viewController you can perform a respondsToSelector:#selector(reload) and if the specific viewController qualifies then it calls that selector method.
In order to do this you probably want all your vc1, vc2, ... files to have an id delegate defined and synthesized. When MyTabBars creates the different tab bars it sets the vc1 and vc2 delegate to self.
#implementation MyTabBars
//Reload Cart View
-(void)reloadCart {
for (UIViewController *thisUIViewController in tabBarController.viewControllers){
if ([thisUIViewController respondsToSelector:#selector(reload)]) {
[thisUIViewController reload];
}
}
}
#end
Assuming you know how to pass a delegate of MyTabBars into your vc1 and vc2, then you can now have the following code in vc1:
#implementation viewController1
//Reloads vc2
-(IBAction) onClick1: (id) sender {
//Calls MyTabBars reloadCart which will look for all tab bar view controllers
//that have the 'reload' method
[delegate reloadCart];
}
#end
This solution idea will cause MyTabBars to trigger any reload method found in any of our tab bar view controllers. Therefore be careful with the naming of such a method in your vc1, vc2, etc files. This solution will trigger a unique vc method or multiple vcs with the same method depending on your naming convention.
Hope this helps.
I should know this by now, but I am still a bit confused. When my app navigates from one view controller to the next (via the navigation controller) I want to "finalize" the data for the current VC before going to the next VC. The only way I see to intercept the "page swap" is in the [old view viewWillDisappear] -> [newView viewWillAppear] transition. It seems weird, though I guess it works okay.
Is that really the right way to handle navigation transitions? My app is a bunch of VCs which collectively build a database file. Each VC deals with a different aspect of the data.
I don't know your exact setup, so this may not be useful to you, but I have good experiences with saving data in -(void)textFieldDidEndEditing:(UITextField*)tf, using tf.tag to index the fields. From there I commit the data to a storage class, and have no worries about what happens in the UI.
What exactly is involved in the "finalizing" part? I assume you are storing some state in the view controller for various fields and then you want to write that to the database file before going on to the next view?
When it comes to "edit view controllers" I find a nice way to do it is to have the view controller directly write to a simple model object which is injected through a property before pushing it to the nav controller.
So something like:
/* Somewhere in the app delegate, like application:didFinishLaunching */
DatabaseFileModel *model = ...;
viewController1.model = model;
viewController2.model = model;
/* ... */
[self.window makeKeyAndVisible];
Then each view controller writes to this model by setting properties etc. when a text field ends editing or whatever. Having the view controller write straight to the object means you don't need to handle viewWillDisappear etc.
If you do still need to do this however you can add a delegate to the navigation controller and handle these two methods:
– navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
See the UINavigationControllerDelegate documentation for more information.
This will let you keep the logic in one place rather than spread out in each view controller.