UIViewController and UITabBarController app - viewDidLoad called only once - iphone

My current application consists of a UITabBarController with 4 different tabs and a UIViewController. My tabBarController is the rootController and the UIViewController is primarily being used for my user login view. When the application loads both views are loaded with the login view covering the tabBar.
I have added some logging to my viewDidLoad method for the loginViewController. I see this method being called on initial launch. Once the user successfully logs in, the tabBarController view is now viewable. I setup an action to logout from the tabBarController. This method sets user/pass to nil and calling a delegate method to load my loginView again.
-(void)setLoginView
{
[window addSubview:loginController.view];
}
I'm adding some checks but nothing is firing because viewDidLoad (in loginViewController.m)never gets called again. When logging out my text fields still have the user/pass that was entered when I first logged in. So one of my checks is to reset the text fields to nil etc..
I may be using the wrong terminology here but is there anyway to "refresh" this loginController where the viewDidLoad method is called every time the user will log out?
Also for the record. I can't thank everyone enough that has helped answer my previous questions. It has really helped and I appreciate it.
Thanks

Maybe try using -(void)viewWillAppear:(BOOL)animated {...} instead of viewDidLoad?

viewDidLoad will be called only once for each time when the view has been inited. I'm not sure if doing loginController.view = nil will cause view to unloaded or not, but I think it should work so next time you access view property the view will be loaded again.

Related

Refreshing the content of TabView

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.

iOS5 - manual Storyboard view selection in code

I am new to iOS and using storyboards for the first time. When my app starts it checks back with the a server app I have written to see if the saved credentials are authenticated and I then in my AppDelegate class I then attempt to show the appropriate scene in the app's storyboard - MainMenu if authenticated or a Login Screen if not authenticated.
I have tried using instantiateViewControllerWithIdentifier on the storyboard and also the performSegueWithIdentifier on the initial NavigationController which is set to be the "Initial View Controller" to display the appropriate view..
However with both methods only the blank navigation bar shows and I am unsure where to go from here.
If there was some example code on how others manually manipulate storyboard scenes and viewcontrollers that would be great. Am I maybe putting the code in the wrong place (ie should it go into the first View Controller) or should that not matter? No exceptions are raised and I seem to have access to instantiated objects as required.
I am thinking I need to understand the operation of the app delegate's window more, or maybe should I focus on manually loading the storyboard by removing it's reference from the InfoPlist settings?
Any thoughts would be greatly appreciated.
From my (admittedly haphazard) understanding of storyboards (at the moment), you should have two named segues going from a first viewcontroller, and then you can simply trigger one or the other as need be (I presume there's some sort of "loading/authenticating" screen, however brief?)
if (success) {
[self performSegueWithIdentifier: #"MainMenuSegue" sender: self];
} else {
[self performSegueWithIdentifier: #"LoginSegue" sender: self];
}
To debug, I'd set up buttons on the initial viewcontroller just to be sure the segue linkings/etc are proper.
You really shouldn't need to instantiateViewControllerWithIdentifier unless you're working around segue/storyboard limitations. I think.
I've put the performSegueWithIdentifier in my app's first viewcontroller's viewDidAppear (not the best idea, I think; but that's sort of the soonest it should happen? and I would hedge towards saying it should be triggered somewhere in the viewcontroller stack, not from the appdelegate, but I haven't tested that).

UITabBarController inside a UIViewController = viewDidAppear: not called

I start off with a login screen. Then after the user logs in I load a Viewcontroller with UITabBArController in it. The problem is viewdidAppear: does not get called for any of the individual viewControllers in the tabBarController.
I have a feeling that this is not the best programming practise so does anyone have any ideas how to improve the structure of my code or how to fix my problem ?
I'd guess your trouble here comes from incorrect use of UIViewController and UITabBarController.
UITabBarController exists as a container for multiple view controllers. It probably should not, itself, be contained. It's designed to sit at the top of a view controller hierarchy. So step one is probably to re-arrange your application so that the UITabBarController is no longer under anything else and see if that straightens you out.
After that, slev's approach of presenting the login view sounds like the right one.
I had a problem because I was subclassing also UITabBarControler where I have overridden viewDidAppear without calling [super viewDidAppear:...]
After calling this, viewDidAppear was called also inside sub-view-controller.
Why not make an app which is TabBarController-based, then immediately call a modal screen at app start (for your login)? After you're done with the login, just dismiss it to allow the TabBarController to become key window.
You could try to manually call viewdidAppear on the subviewcontrollers: when it's called on the rootviewcontroller also call subviewcontroller's ones manually.

Objective-C object dealloc'd while other objects still has a delegate reference to it causes crashes. How to prevent this?

I have an app with a navigation controller as the root view. There are many views that can be pushed in.
The user has to create an account to use the app. The user can then log into this account from other devices, but only one device can be logged onto the same account at a time. So if multiple devices try to log into an account, only the latest device will be logged in and the other devices are logged off (a push is sent to the devices).
Since there are multiple views that the device could be showing before it was logged off, I call popToRootViewControllerAnimated: to get back to the root view. This is because when the user logs in the next time I only want the root view to be shown (the new account might not have access to the previously shown view).
If the user has an alert view or action sheet presented (which uses the current view as its delegate) before the push is received, the view will still be shown after the popToRootViewControllerAnimate: method is called. If the user then taps on a button for the alert view or action sheet, it will send a message to the dealloc'd view and crash the app.
An example:
myViewController is being shown to the user.
myViewController create an action sheet prompting the user for a decision.
The push is received for the device to log out.
The navigation controller pops all the views controllers and now shows myRootViewController.
Since the view controllers are popped, myViewController is now dealloc'd.
The action sheet from myViewController is still shown.
When the user selects an option form the action sheet, a message is sent to myViewController, and since it is already dealloc'd, a crash will occur.
Is there any way to prevent this?
One solution I have considered would be to keep track of all the objects that uses a specific view controller as its delegate. Then when that view controller dealloc's it will also set all the object's delegates to nil. This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list.
Any better solution (or improvement to mine) would be appreciated!
Edit: The alert view and action sheet are only examples of some objects that I would use myViewController as a delegate. I am also using a number of other classes (and third-party libraries) that implements this delegate pattern.
A few ideas:
you can encapsulate the alert/action sheet view and delegate in a single class. Then when you need an alert view, create MyAlertView instead, which will also be its own delegate and will do [self release] after the user taps a button.
make your App Delegate the only delegate for all your alert views and action sheets. App Delegate is always around while the application is running, so there won't be a problem with a released delegate.
The problem with both solutions is that if you need your application to know what happened in the alert view/action sheet, you somehow need to tell the interested class of the user's choice.
You can do that by either using delegates of your own - which would mean you're back to square one - or use notifications: when the alert view/action sheet delegate is called, it would post a notification ([[NSNotificationCenter defaultCenter] postNotificationName:NotificationName object:self userInfo:userInfo];), while the interested object would look for that notification ([[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onNotification:) name:NotificationName object:nil];) and perform whatever tasks necessary in onNotification:(NSNotification*)aNotification method.
You'll be able to agree with yourself on what type of information is passed in those notifications (I would think the button number in a NSNumber class would be enough, or perhaps pass the button text, too). And you won't have to keep track of all alert views - just don't forget to remove the observer ([[NSNotificationCenter defaultCenter] removeObserver:self name:postNotificationName object:nil];) in the views' dealloc.
Edit:
"This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list."
Actually you probably can do this in a semi-automated way: make a singleton object with a method like
-(id)delegate:(id)delegate for:(id)forWhom
And then instead of
someThingy.delegate = self;
you'd do
someThingy.delegate = [[DelegateLocker defaultLocker] delegate:self for:someThingy];
Inside the DelegateLocker you'd have a MutableDictionary with delegate class as a key and a MutableArray of someThingies as a value. Then in your view controllers' deallocs you'd call
[[DelegateLocker defaultLocker] delegateIsDying:self];
which would go through the thingies and assign delegate = nil for each
The drawback of course is that you'll be retaining all the thingies for an indefinite period of time instead of releasing them immediately.
So the ViewController that presented the action sheet iand set itself as the delegate right? So why dont you keep a reference to the ActionSheet in the ViewController, in the dealloc method of the view controller, you can check if the action sheet is visible, if it is then set the delegate of the action sheet to nil,and dismiss it...
so
-(void)dealloc
{
if(myActionSheet && [myActionSheet visible])
{
[myActionSheet setDelegate: nil];
//dismiss
}
}
Hope that helps
If you want automated solution, I think you can make a function to iterate through Ivars of your view controller to see if any Ivar has delegate property and set it to nil.

How to know when a UIViewController view is shown after being in the background?

In iOS4.2/iPhone4
Click icon to launch app (some view
controllers view is displayed)
Click iPhone Home button (return to
home screen)
double click Home button
Select previously launched app from
the selection
Now I can see that my app delegate gets a message "applicationDidBecomeActive" when its selected after the last step, but how does my viewController (the one who's view is currently displayed) know?
viewDidLoad was already called, so that isn't called again. viewWillLoad is not called again.
Can't seem to figure it out. Reason I'm asking is I want to check to see if any Settings changes were made, but would like to do that in the view controller cause that's the thing that cares.
The answer is here: Handling applicationDidBecomeActive - "How can a view controller respond to the app becoming Active?"
Use NSNotificationCenter to get notified of UIApplicationDidBecomeActiveNotification events in your view controller.
in you're appDelegate applicationDidBecomeActive put this :
- (void)applicationDidBecomeActive:(UIApplication *)application
{
UINavigationController *navc = (UINavigationController *)[tabBarController selectedViewController];
UIViewController *topvc = [navc topViewController];
if ([topvc respondsToSelector:#selector(viewControllerDidBecomeActive)])
{
[topvc performSelector:#selector(viewControllerDidBecomeActive)];
}
}
This gets the viewController that is being seen on screen. You just have to implement viewControllerDidBecomeActive on every viewControllers ;)
In the appDelegate applicationDidBecomeActive set a boolean property marking that it just appeared from background.
Then in your viewcontroller, specifically in the viewDidAppear override, check for the appDelegate property, if its true then you know it has come from the background, otherwise it has just appeared as normal. BTW Afterwards, set the boolean property to false for neatness.
EDIT-
You would have to call viewDidAppear manually in the applicationDidBecomeActive unless you were re-creating your navigation stack. If you were able to get a pointer to the current visible view controller, then calling viewDidAppear should be a no fuss approach as all view controllers have this method. You wouldn't need any delegates or etc.