I Have 2 views. View1 and view 2.
I am opening view 2 from view 1 by using
view2 *store2 = [[view2 alloc] initWithNibName:#"view2" bundle:[NSBundle mainBundle]];
[self presentViewController:store2 animated:YES completion:nil];
Then I have added a button on view2 to to dismiss view2 and return to view1 with the code:
[self dismissViewControllerAnimated:YES completion:nil];
The dismiss works well and get back to view1. But each time when I click on any button or any part on view1 after I return there, app crashes.
Kindly guide to go through this. I used this code in past projects and it always worked.
Sounds like no other object is retaining view1's view controller. The view is presented properly since it doesn't need to be retained by anybody in order to be displayed, however, once you try to interact with it, it need its view controller to be alive and responsive. My guess is that if you have view1's view controller as another object's property, or hold it in any other way as long as its view is displayed, you'll be able to use view1's buttons without crashing the app.
I want to display/animate a UIView above the whole content of my app. The following approach works, but is not a very good idea to attach everything to the window.
[[[[UIApplication sharedApplication] delegate] window] addSubview:view]
What is a good alternative?
I think you can try visibleViewController method to get the top viewController
//Try this to get the top viewController
UIViewController *vc = [app.rootViewController visibleViewController];
// After getting the top viewController, and then add your subview to it
[vc addSubView:view];
// and try bringing this view to the top
[vc bringSubViewTo....(fogot~):view];
see if it could help :)
I think the approach is correct, you do need to attach it to the window, as you may not know what the view hierarchy is.
What you can do to tidy up the code a little bit, is encapsulate that logic into some methods accessible through the AppDelegate or something:
- (void)addAlwaysVisibleView:(UIView *)topmostView;
- (void)removeAlwaysVisibleView;
The AppDelegate can then keep the state, and know if an "always visible" view is already attach, to keep things in order and do some housecleaning when needed.
The main view of my app is a UIImagePickerController camera view.
When the app becomes active (in didBecomeActive), I present a modal view controller that shows some settings generated from a network request. (Note that for debugging purposes, I took the network request out and am currently just showing a dummy view)
The modal view animates in smoothly, but after loading it freezes for 3 seconds then responds normally. After dismissing the view (also animates smoothly), my image picker controller pauses for 2 seconds then resumes normally.
I have removed all functionality from the modal view controller to make sure there was no operations clogging the main thread. I am presenting the most basic of controllers, and still get the choppy ui. I would suspect that this is from my presenting view controller calling viewDidLoad/Unload or something similar, but my search did not give me any information on what delegate methods are called in the presenting view controller when a modal view is shown.
My problem can be solved by answering:
What delegate methods are called in the presenter when a modal view is shown?
(If any ^) How can I not call those methods, or make them run smoother?
What common pitfalls are associated with modal view controllers?
This is probably because you are making a lot of processing in the main thread (usually when UI stops, it's because main thread processing). Try to provide us some code, specifically the one you think is the most heavy processing code! Sorry about my poor english :P!
Try dispatching most heavy code to another thread with
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//your heavy code here =)
});
Regards,
Lucas
There are multiple methods invoked. ViewDidLoad ViewWillAppear ViewDidAppear ViewWillDisappear ViewDidDisappear. Check all of those methods. Also, check any subviews you have created and see if they are doing any thing on their thread involving image loading in the methods i stated. Also does this occur in the simulator as well as a test device?
ModalViewControllers do not have too many pitfalls but understanding how many views are allocated on things like navigation stacks and how many views you have on top of each other. When you get rid of the modal viewcontroller do you call dismissviewcontroller?
One thing that might be a contributor to some slight lag is reloading the same viewController from scratch each time.
BProfileTableViewController * _profileViewController = [[UIStoryboard storyboardWithName:#"Profile" bundle:[NSBundle chatUIBundle]] instantiateInitialViewController];
UINavigationController * profileNavigationController = [[UINavigationController alloc] initWithRootViewController:profileViewController];
[self.navigationController presentViewController:profileNavigationController animated:YES completion:nil];
You can see here that if this is on a tableView click then each time the app needs to create the viewController again. If instead we just reuse the view then it gets rid of some of that lag.
Add this in the header file
BProfileTableViewController * _profileView;
Then the modal view load code changes to:
// Open the users profile
if (!_profileView) {
_profileView = [[UIStoryboard storyboardWithName:#"Profile" bundle:[NSBundle chatUIBundle]] instantiateInitialViewController];
}
UINavigationController * profileNavigationController = [[UINavigationController alloc] initWithRootViewController:_profileView];
[self.navigationController presentViewController:profileNavigationController animated:YES completion:nil];
This means we are just reloading the view instead of recreating the view
Try
layer.masksToBounds = true
Here is a visual representation of the UISplitViewController:
I'm trying to present a UIViewController (myVC) that's a subview of a UIView inside the DetailViewController of a UISplitViewController using this:
[appDelegate.splitViewController presentViewController:myVC animated:NO completion:nil];
It presents, I dismiss it, the app rotates without a hitch, then when I try to reload the detail view by clicking on a cell in the rootViewController (a UINavigationController), all the [orangeUIView removeFromSuperview] messages are mysteriously disabled (there are more orange UIViews hidden here that are inside a mainScrollView). The "INFO LABEL" in the topToolbar is updating, so I know the cell is sending data...
Why would this activity disable removeFromSuperview if presented using splitViewController? I've presented myVC using the detailViewController...
[appDelegate.detailViewController presentViewController:myVC animated:NO completion:nil];
which doesn't disable removeFromSuperview, but causes an assortment of other rotational crashes.
What's going on here?
Since this question was created as a result of a previous question, the answer to this problem was...try something else. I was unable to fix this problem by presenting a view controller, most likely because I'm using a custom UISplitViewController with a hierarchy I'd rather not get into.
Here is the solution I came up with:
Core Plot - Options on making the graph full screen - addSubview after presenting modal problems
I have an application created from the tabbed application template. (ARC, iOS 4)
There are several tabs and there is a button on the 2. tabs viewcontroller.view(ViewCont2).
This button loads another viewcontroller's(ModalViewCont) view by presentModalViewController method.
There is a close button on ModalViewCont which calls dismissModalViewControllerAnimated.
In viewDidDisappear of ViewCont2, i am setting self.view = nil and other outlets to nil to unload the view so it will be fresh loaded next time it appears on screen. I am doing this because it inherits from a base class(BaseViewCont) which initializes some general properties of the view controller and adds some buttons, labels etc. in viewDidLoad method. So, ViewControllers that inherit from this base class may configure those properties differently as they wish in their viewDidLoad method.
Problem
Now, when ModalViewCont on screen, pressing the Home button to put application in background and after getting the application back, closing the ModalViewCont does not bring back the ViewCont2's view but a black screen with the tabbar at the bottom. The same thing happens without putting the application background/foreground; if other tabs tapped before tapping the 2. tab.(EDIT : This happens only if self.view set to nil in viewWillDisappear instead of viewDidDisappear.)
I determined that ViewCont2 loads a new view (checked it's reference) but view's superview is nil so the new view is not displayed but a black screen.
Things that did not work
Using [self.view removeFromSuperview]; before setting self.view=nil,
In viewWillAppear adding view to the parent; [self.parentViewController.view addSubview:self.view]; This one did not work smoothly, view placed slightly up of the screen. This is because there are several other superviews in the hierarchy.
Solutions i considered;
1- If superview is nil in viewDidLoad, it becomes available in viewWillAppear (assumption). So, viewWillAppear method of ViewCont2 could be used to get the superview loaded correctly by the following;
_
if (self.view.superview == nil)
{
self.tabBarController.selectedViewController = nil;
self.tabBarController.selectedViewController = self;
}
2- viewWillAppear method of base class could be used instead for initialization so there is no need to unload the view. So, performance could be optimized, it will not be unloaded each time view disappears. Also, it would be better to perform initialization only once by checking a flag, instead of performing it every time it appears.
Questions
1- Why does not the superview restored? What should i do for it? (This is the main problem i want to understand and solve instead of trying alternatives...)
2- Am i doing something wrong by assigning nil to view for unloading it? If so, how should i unload the view properly in such case like this(tabbed application)?
3- Is anything wrong with the 1. solution? Does it seem like a kludge? Is that assumption about superview and viewWillAppear correct?
EDIT : It seems that when viewDidLoad is called earlier than it should(i.e when view nilled in viewWillDisappear instead of viewDidDisappear), superview is not set.
It seems weird, but your suggestion (1) is indeed a correct workaround for this problem:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.view.superview) { // check if view has been added to view hierarchy
self.tabBarController.selectedViewController = nil;
self.tabBarController.selectedViewController = self;
}
}
Your second suggestion is good for performance (because view loading is an expensive operation) - but it will not solve the problem. You can also end up with a black screen without setting the view to nil in the following situation (test this in the iOS simulator):
open the modal view
simulate a memory warning -> this will unload the views in the tabbarcontroller
press home button and open the app again
close modal view -> black screen
Generally you can assume that in viewDidLoad the view property is set and in viewWillAppear + viewDidAppear the view has been added to the view hierarchy; so the superview should be there at that time (Here the superview is a private view of the tabbarcontroller of class UIViewControllerWrapperView). However in our case, although the view is reloaded (at the time of app resume), it is not added to the view hierarchy resulting in a black screen. This seems to be a bug in UITabBarController.
The workaround forces the appearance selectors to be performed again. So viewWillAppear will be called again, this time with a superview in place. Also viewDidAppear will be called twice!
Setting self.view to nil is okay, but should not be necessary in most cases. Let the system decide when to unload the view (iOS can unload views when memory gets low). The view controller code should be designed in a way so that the UI can be reconfigured at any time without reloading the view.
You do not have full control over when views are loaded and unloaded, and you are not supposed to load/unload views manually yourself.
Instead, you should think of view loading/unloading as something that's entirely up to your UIViewControllers, with you being responsible only for:
Implementing the actual loading, by associating your UIViewController subclass with a nib file or by implementing loadView manually.
Optionally implementing the viewDidLoad, viewWillUnload and viewDidUnload callbacks, which are called by the view controller when it decides to load/unload its view.
The fact that you have no full control of when the above callbacks will be called, has implications about what should go into them.
In your case, if I understand correctly, whenever your ViewCont2's view disappears, you want to reset it so that when it reappears it will be in some "clean" state. I would implement this state reset in some method, and call it both from viewDidLoad and from viewDidDisappear. Alternatively, you can have the "clean" logic in viewWillAppear.
Or maybe you want to clean ViewCont2's view only when the present button is tapped? In that case, clean the view both in viewDidLoad, and when the button is tapped.
What I offer is that when the modal view controller is active, and you dismiss the view, that you add a new view to the navigation view controllers viewControllers, then that view is told to remove its predecessor.
You can play with my project to see if you think it works for you.
EDIT: my comment on the selected answer is that this technique obviously works now, but I myself am having a hard time followiing it. The code in my project uses the system in a simple and direct fashion - when the modal view is told to dismiss itself, it calls a method (could be in any class) that adds a new view to the navigation controller's array, then dismisses itself. For a bit of time there are two view controllers of the same time, the new one stacked over the old one. When the new view controller appears, based on seeing a flag it silently and behind the scenes removes the undesired viewController from the nab bar's stack, and poof, it goes away.
I have found the actual solution to the UITabBarController bug(memory warning,app enter back/foreground,dismiss modal). Using UITabBarController as the root view controller is the cause of the bug. So, we could use another view controller as the root view controller and present the tab bar from it. I have tested it on iOS 5.1 simulator.
Of course, the overhead of extra UIViewController is subject to debate. Also, it's against the Apple documentation;
Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller.UITabBarController Class Reference
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// A root view controller other than the actual UITabBarController is required.
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, ..., nil];
[self.window.rootViewController
presentModalViewController:self.tabBarController animated:NO];
}
I have found other solutions;
First one causes the warning: "Application windows are expected to have a root view controller at the end of application launch" although there is root view controller.
Although it seems kludgy, the temporary view controller will be released with the first one.
Second one seems more reasonable.
.
- (void) tabBarBlankScreenFix1
{
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
[self.window addSubview:self.tabBarController.view];
self.window.rootViewController = self.tabBarController;
}
- (void) tabBarBlankScreenFix2
{
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
[self.window addSubview:self.tabBarController.view];
}
I think you shouldn't assign the view to nil.
If I understand you right you want to refresh/reload content every time the view appears.
So instead of setting the view to nil, you should try to refresh it. You can do it by adding:
- (void)viewWillAppear{
[self.view setNeedsDisplay];}
Please tell me if I understand your issue right