i'm try to find a way with google to reload/refresh view from app delegate. none of solutions work. my idea was for vie to reload inside applicationDidBecomeActive: , but i can't get it to work, it doesnt matter what i try. anyone?
Update:
last thing i used (in applicationDidBecomeActive: with imported ViewController.h in AppDelegate) was:
ViewController *vc = [[ViewController alloc] init];
[vc viewDidLoad]
There were a coupled of other variations, but i think you get what i'm trying to achieve.i'm a rookie so please don't blame me if this is totaly useless :)
Do you mean that you want to perform some action (sending a refresh message to a controller, for example) when the app is reopened?
The method to override is -applicationDidBecomeActive: in your app delegate. You'll want to keep a reference to the view controller in question around. So:
#property MyViewController *myView;
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self.myView refreshSettings];
}
You could probably use -viewDidLoad like you did in your example code, if you wanted, but I find it's easier to factor out the part that you want to update and call it from both places.
Related
I think this is a pretty common usecase as I have seen it in several apps. But after spending couple of days, I am still struggling with this. I have a structure like the following:
UITabBarController
-- UINavigationController1
---- UITableViewController1
-- UINavigationController2
---- UITableViewController2
Now I have a logout button on UITableViewController2. When I click on that logout button I want all and any viewcontroller to be deallocated, all view unloaded. Basically start fresh like launching the app. I basically want the viewDidLoad on each of those UITableViewController called again.
I tried the following method to be called in my appdelegate when the logout action on UITableViewController2 is taken.
-(void) logout {
for (UINavigationController* ctrl in self.tabBarController.viewControllers) {
[ctrl popToRootViewControllerAnimated:NO];
ctrl.visibleViewController.view = nil;
}
[self.tabBarController.view removeFromSuperview];
[self.window addSubview:self.tabBarController.view];
}
But alas, it does not seem to work?
Any ideas how such a thing is accomplished? Also I see different behaviors in iOS4 vs iOS5 with the visibleViewController. I am not using any modal viewcontroller here. Any gotchas?
Update: I am not using ARC
thanks
mbh
Your for-loop will release and thus dealloc any view controllers that you have pushed onto the respective UINavigationController roots (depending on how many tabs you have), i.e. as these will not have a superview when you pop back to the root of each navigation controller, these are dealloc-ed automatically. These are your UITableViewControllers taken care of.
As for the respective UINavigationControllers, you would need your tabbar-controller to release the old instance. IMHO, this should be done for you when you release the UITabBarController.
This then leaves the UITabBarController itself. I don't think it can be done tbh. Your code will only remove the view, but not dealloc the tabbar controller itself. And as Krishna K points out, you need at least one view controller to reload all others.
Putting the code into the appdelegate makes sense, but you need to ensure that your logout() will not cause a retain on the UITableViewController2 as well as the UITabbarController as it's called from UITableViewController2 somewhere.
One idea to explore, does your AppDelegate hold an instance to the TabBar-Controller which you could release and create a new instance after removing the view from self.window?
// manually create UITabBarController - AppDelegate holds instance
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
}
- (void) logout {
[self.tabBarController.view removeFromSuperview];
[mytabcontroller release];
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
[self.window addSubview:self.tabBarController.view];
}
But as I said, there might be caveats with memory management at this point.
You need to release your view controllers. When their release method is called, that method should include statements to release all of its object's resources (and also dealloc its superclass).
Your rootViewController for both Navigation controllers are their respective TableView controllers. So I don't think popToRootViewController would do anything.
You probably need to reset the data and refresh the views instead of deallocating the views.
I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)
I have been developing iphone applications for around 3months now and theres a few things that stump me and i don't really have an idea how to work round them.
I have a navigation controller controlling the views in my application however every screen that is loaded, used then pushed back loses all the information as it seems to be reinstantiated... I believe this is a possible memory management issue?
But how to i create an app that navigates and retains all information in its views until the application is closed.
Thanks :)
Possible you didn't keep a reference to the view controller, the issue is for UIVIewController not to be released.
Make the view controller an ivar you will instanciate only one time when you push it on stack.
// in .h
MyViewController *mVC;
// in .m
// maybe when the user selects a row in a tableview
if(mVC == nil) {
// first time use, alloc/init
mVC = [[MyViewController ....];
}
// then push on the stack
[self.navigationController ....];
Of course don't forget to release it later.
In this part:
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
You will probably have something like this... Instead, make your myViewController a class's property so you have a reference to it. And drop the [myViewController release]; statement.
Possibly your app is receiving a didReceiveMemoryWarning.
In such cases, when the super class is called, the framework does memory cleaning by unloading all the views that are not currently displayed. This could explain the behavior you are seeing.
To check it further, override didReceiveMemoryWarning in one of your view controllers or applicationDidReceiveMemoryWarning in your app delegate, and put a breakpoint in it. Don't forget to call [super...] appropriately, otherwise pretty soon your app will be killed. What you should see in this way is that the views do not disappear before hitting the breakpoint, and do disappear after that.
If the hypothesis is correct, you should find a way to save the state of your view in viewDidUnload and restore it in viewDidLoad. Look also at didReceiveMemoryWarning reference.
Try to save data in NSUserDefaults it its small or use plist or it its too small like 5-10 objects save in in some variable in appDelegate, and if its too large use sqlite and for saving something like images of files like xml use Document directory
The UINavigationController works like a stack: you push and pop UIViewControllers on it. That means when a UIViewController get popped, it will have its retain count decremented by 1, and if no other object holds a reference to it, it will be deallocated. You can avoid the UIViewControllers getting dealloced by keeping a reference to them yourself by calling -retain on the objects, for instance in your appDelegate.
You can use NSUserDefaults to save the states of the UIControls in the view.
So whenever u r loading a view, set the values to the controls so that it looks like it resume from the place where we left.
I have a design question/technical question about my iPhone app.
I have a pretty simple (read really really simple) single view application. And it does everything that I need it to do. However I find myself in need of a help view. And I really don't quite know what to do!
I have a simple helpButton() method in my main view controller, and I really just want to display a scrollview with a bunch of images that show what to do during the use of my app. However, should I make a new viewcontroller class? How do I call it from my method?
Really I was thinking of an unfortunately simple method, just putting a scrollview behind everything and hiding it. Then showing it when the IBAction is called. Horrible...
Sorry if this is elementary, I haven't needed to do anything more yet!
You can push a modalViewController. To do that just make a new viewController with the scrollview and associated data in it, then
MyViewController *myViewController = [[MyViewController alloc] init];
[self presentModalViewController:myViewController animated:YES];
Create an IBAction in your new viewController and a hooked up button to that action to dismiss the modalView (something like this:
IBAction done {
[self dismissModalViewControllerAnimated:YES];
}
A couple options:
1) Create a new UIView object, either programmatically, or even in your existing XIB file. Use the [self.view addSubview:view] method to display it.
2) Create a new UIViewController with its own XIB file. Use [self presentModalViewController:anaimated:] to display it.
Either way, you'll need to add something to the new view to dismiss it when you're done.
Before iOS4, my app's initial view controller would check a passcode on/off settings variable in viewWillAppear and if set on, present a modal passcode screen that would stay there until the correct passcode was entered or the Home button was pressed.
With iOS4, if my app has been in the background, I would like the user to feel comfortable that the data contained within the app is not easily accessible if they were to hand their phone to someone to use.
Since the app could return to any screen (the screen that the app was last on), I figured I would use the UIApplicationWillEnterForegroundNotification all over the place with local selectors (duplicate enterPasscode methods), each having the correct view controller to push based on the local screen, but there has to be a better way.
I may be having a slight conceptual misunderstanding here. Can someone suggest another approach or nudge me along to the next step. Can I have this as a shared method but still know the correct local view controller to push?
Thanks
EDIT/UPDATE:
Short version: It works, but may there still may be a better way (any help appreciated)...
I created a standard singleton with a view controller.
PasscodeView.h containing:
UIViewController *viewController;
#property (nonatomic, retain) UIViewController *viewController;
PasscodeView.m containing:
#synthesize viewController;
I put this in the AppDelegate:
-(void)applicationWillEnterForeground:(UIApplication*)application {
PasscodeView *passcodeView = [PasscodeView sharedDataManager];
UIViewController *currentController = [passcodeView viewController];
NSLog(#"%#", currentController); //testing
EnterPasscode *passcodeInput = [[EnterPasscode alloc] initWithNibName:#"Passcode" bundle:nil];
[currentController presentModalViewController:passcodeInput animated:NO];
[passcodeInput release];
}
and the following in all my viewDidLoad, updating the current view controller as I went into each screen (only 2 lines but still seems that there's a better way):
PasscodeView *passcodeView = [PasscodeView sharedSingleton];
passcodeView.viewController = [[self navigationController] visibleViewController];
I wish there were a way to have gotten the current view controller from applicationWillEnterForeground but I couldn't find it - any help here still appreciated.
For consistency, I changed a line and added a line to get a nav bar to match the rest of the app and to include a title.
UINavigationController *passcodeNavigationController = [[UINavigationController alloc] initWithRootViewController:passcodeInput];
[currentController presentModalViewController: passcodeNavigationController animated:NO];
You can centralize this behavior in your app delegate by implementing
-(void)applicationWillEnterForeground:(UIApplication*)application;
You might implement a singleton that stores the currently appropriate modal view controller, updating it in viewWillAppear in each of your view controllers.
Edit: I was assuming that you already had a series of view controllers that you wanted to show. I doubt you actually need it. If you have one called, say PasscodeInputController, then your applicationWillEnterForeground would look something like:
-(void)applicationWillEnterForeground:(UIApplication*)application {
UIViewController *currentController = [window rootViewController];
PasscodeInputController *passcodeInput = [[PasscodeInputController alloc] init];
[currentController presentModalViewController:passcodeInput animated:NO];
[passcodeInput release];
}
I hope that addresses your question more directly.
Your application's behaviour when reentering the foreground should be as similar as possible to when it launches for the first time. With this in mind, you could combine your applicationDidFinishLaunching: with applicationWillEnterForeground: but considering some views might already be loaded. The code is something like this:
id myState = (isReentering) ? [self validateState] : [self loadStateFromSave];
NSArray * keyForObjectToLoad = [self objectsToLoadForState:myState];
for(NSString * key in keyForObjectToLoad)
if(![self objectForKey:key]) [self loadObjectForKey:key];
Depending on your app's detail it might require initial work, but it has some benefits:
It will ensure launching and relaunching are similar, hence the user experience is not "random".
You can free many unwanted memory easier when in the background.
It's easier to manage your application's state from a central place.