I have a RootViewController and a SubViewController. I traverse the views in either direction. The RootViewController is a UITableView SubClass. Depending upon the row selected I want to change the title of the subview using self.navigationItem.title = [chunks objectAtIndex:1];
It works when the subview is first time loaded. But when I return to the RootViewController and load the subview again the previous title persists.
Any ideas what am I missing out on?
In your particular case, you probably want to set your title in viewWillAppear: so that the title gets set every time the view comes on the screen.
I just tried it in an app of mine. When I set the title using
self.navigationItem.title = #"Foo"
the name in the navigation bar changes instantly. I think you have a bug somewhere else that you code is only getting called the first time you invoke your SubViewController. Stick a break point on that line and see if it actually gets called a second time. Or perhaps [chunks objectAtIndex:1] is always returning the same string.
Or perhaps I'm not understanding your question. As far as I can tell it does work like you are expecting it to.
I guess you are trying to change the title in the viewDidLoad method. The viewDidLoad is called only the first time the view is loaded. In case you are reusing the same viewcontroller's instance, viewDidLoad will be called only once.
Instead try setting the title in viewWillAppear method. This method is called every time the view is going to be displayed. That should work.
Related
Is there a BOOL or some other way of knowing if viewDidLoad: has been called?
I need to change a view property every time my view has entered active, but if the view hasn't ever been loaded, I don't want to prematurely trigger viewDidLoad:. If there isn't way of easily telling if viewDidLoad: has been called, I'll simply add a BOOL called loaded set to NO in the view controller init, then change the view properties after entered active if loaded is YES or in viewWillAppear: if loaded is NO then set loaded to YES.
Use isViewLoaded. Other than that it does exactly what you want, there's not that much to say about it. The documentation is as simple as:
Calling this method reports whether the view is loaded. Unlike the
view property, it does not attempt to load the view if it is not
already in memory.
Perhaps you should init your UIView in viewDidLoad, and then change it in whichever way you need to inside viewWillLayoutSubviews.
Here's the pedantic answer to this question. If you want to know when viewDidLoad has been triggered, you have to implement viewDidLoad in your view controller
- (void)viewDidLoad
{
[super viewDidLoad];
viewDidLoadCalled = YES; // Not actually the best way to do this...
// Set up more view properties
}
But as Tommy says, you actually need to use isViewLoaded. This gets around the problem of doing a check like
if (!self.view) {
// do something
}
which inadvertently loads the view by virtue of asking about it.
Be aware that by the time viewWillAppear: is called, the view will always have loaded. Also, on older (pre-iOS 6 I think) releases, the view can unload and be reloaded many times over a view controller's lifetime. Refer to the very nice Big Nerd Ranch view lifecycle diagram for the old behavior. It's almost the same in iOS 6+, except that the view doesn't unload under low memory conditions and viewDidUnload doesn't get called:
In iOS, is there a possibility of stopping a view from loading in the viewWillAppear ?
Something like [self close]; ?
What I am trying to do is:
create a view with an object partially loaded
display the view
in the viewWillAppear, I finish the object loading and fill the view fields
if the object loading fails, I do not want to show the view
I know that it is not really the good way to do that,
but it's existing code that I do not want to change too much.
I'm not sure if I misunderstood your question, but if you just try to have a view inside a viewController not to be shown at the beginning and to appear later you can easily viewName.hide = YES; it in viewWillAppear and then viewName.hide = NO; when you want it to be shown.
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 have 5 view controllers, which are in a navigation controller hierarchy. When I reach my last view controller, there's a button that lets me go back to the "Home" view controller (named CardWalletViewController) which is a table view controller. Here's the method I have in my last VC (PointsResultsVC)
- (IBAction)homePressed:(id)sender {
NSArray *VCs = [self.navigationController viewControllers];
[self.navigationController popToViewController:[VCs objectAtIndex:0] animated:YES];
}
CardWalletVC's cells is being filled up by values coming from the saved instances of a Card in NSUserDefaults and it works fine.
Now, what I want is to update the value in my CardWalletViewController, which is the points of a card coming from my PointsResultsVC. Note that this points is saved in NSUserDefaults.
Upon the process of trying to update of the value shown in my CardWalletVC, I placed [self.tableView reloadData]; inside -viewDidLoad, -viewWillAppear, and -viewDidAppear of the said class. I tried placing it one by one in each of this methods, yet it doesn't seem to work.
Advice Please.
EDIT: problem solved
This one served as my guide Save data from one tab and reloadData in another tab.
And as I have discovered, -viewDidLoad of a certain class will only be called once, all throughout runtime of app. Whereas -viewWillAppear, it will be called every time the view appears.
So, I just moved the way I am loading the values saved in NSUserDefaults from -viewDidLoad to -viewWillAppear. Also, inside -viewWillAppear I placed the [self.tableView reloadData]. Then there, problem solved.
There's a lot of things that could be going wrong so you'll need to provide more info to narrow it down.
First of all, viewDidLoad will not be called so you can remove it from here. Both viewWillAppear and viewDidAppear will be called, so you only need it one of these 2 methods.
Next, you need to determine what the actual issue is.
1 - is self.tableView a proper reference to the table? Are you properly maintaining the reference?
2 - is the table actually reloading but using the old data, or is it not reloading at all? You can log the willDisplayCell method to see what is going on when the view appears.
With that info, the root cause can be narrowed down to a solvable problem.
So I have this UINavigationController, I'm on the first moving to the next view, than I want to hit the 'back' button and to go back to the first view with the data that I saved into 'strAddress' on the second view. I want to present the data on the first view on 'lblShowStr.text'.
how can I manage to do that? I've searched all the web, found some people that wrote, but couldn't understand what they have been told there.
Thanks!
You can get a reference to the previous viewController in your navigation stack by saying:
NSArray *viewControllers = self.navigationController.viewControllers;
MyViewControllerClass *previousController = [viewControllers objectAtIndex:[viewControllers count] - 2];
You can then set a property on the 'previous' view controller to store your text, or even set the label outlet's text directly like this:
previousController.lblShowStr.text = self.strAddress;
It's not the best way to do it (the best way involves creating a custom delegate protocol or using NSNotificationCenter) but it's the easiest way.
In your first view controller you might have an NSString property called strAddress.
and you put that string into lblShowStr.text every time the view appears.
In your second view controller you might have a property pointing to an instance of view controller one. When you instantiate your second view controller you could assign the property on it to the first view controller.
secondViewController.firstViewController = self;
or
[secondViewController setFirstViewController:self];
Then when the user presses the back button viewDidAppear would get called for the first view and update the string.
I am assuming you don't want to store this data anywhere else e.g. in your model or nsuserdefaults etc.