I have two View Controllers. ViewController & Next.
ViewContoller.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
bool static check = FALSE;
if(!check){
check = true;
[self changeView];
}
}
-(void)changeView{
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
Next *viewController = (Next *)[storyboard instantiateViewControllerWithIdentifier:#"next"];
[self presentViewController:viewController animated:YES completion:nil];
});
}
Next.m is nothing more than the xcode boilerplate code of a view.
When i execute this as above, i receive this output:
2014-11-27 13:20:21.005 changeView[1804:72014] Unbalanced calls to begin/end appearance transitions for <ViewController: 0x7fdd80f41e20>.
You'll notice that i have a static boolean in the viewDidLoad. Without this, the viewDidLoad is called infinitely causing an animated loop of Next's view appearing ontop of ViewController's view.
I'm almost 100% certain that i should not have to implement the boolean to prevent the loop but this is the only way i can. What do?
Update!
When i embed a navigation controller, and use [self.navigationController presentViewController:viewController animated:NO completion:nil]; i do not experience the loop.
While this resolves the situation, the issue of changing views (without a navigation controller) still stands
Related
I'm playing around with view life cycles & am having trouble changing a view from the load of a different view.
In my ViewController.h i have:
-(void)viewDidAppear:(BOOL)animated{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ViewController2 *viewController = (ViewController2 *)[storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
[self presentViewController:viewController animated:YES completion:nil];
}
However this only causes the view to be between ViewController, and ViewController2 appearing with animation (in a loop).
I used the code in viewDidLoad however neither of the view's loaded (from reading you cannot change view until the viewWillAppear)
Update: When using the code in viewWillAppear, whose view is not in the window hierarchy error is thrown.
How does one change the view from view setup stage?
Update
Using the above code, inside & out of GCD, in viewDidLoad, viewWillAppear & viewDidAppear either results in an infinite loop of animated showing of the ViewController2, or crash on 2nd attempt of segue (result from the loop).
EDITED:
I'm not sure exactly what you're trying to do, but assuming you are wanting the first viewcontroller to appear and then the second viewcontroller to immediately animate on top of the first one, you should be able to accomplish using several options:
First you could just wrap your calls in a dispatch_async call:
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ViewController2 *viewController = (ViewController2 *)[storyboard
instantiateViewControllerWithIdentifier:#"ViewController2"];
[self presentViewController:viewController animated:YES completion:nil];
});
Or you could use a show modally segue:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"myModalSegue" sender:self];
});
}
Or you could use a navigation controller and use a standard show segue (formally push). This one doesn't require the dispatch_async:
- (void)viewDidLoad {
[super viewDidLoad];
[self performSegueWithIdentifier:#"myshowsegue" sender:self];
}
I've posted working examples of all three on: github
It is better to exchange views in loadView method.
- (void)loadView {
CGRect rect = [[UIScreen mainScreen] applicationFrame];
MyView *view = [[MyView alloc] initWithFrame:rect];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
self.view = view;
}
Since testing my app on iOS 8, I find a work around view controllers initialization and presentation really baaadly slow.
I used to work with a code similar to this on iOS 6 & 7:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
....
[self.window setRootViewController:_rootController];
[self.window makeKeyAndVisible];
// Conditions
if (#first launch condition#) {
// quite small controller containing Welcome showcase
WelcomeViewController *w = ....
[_rootViewController presentViewController:w animated:NO];
}
else if (#last opened item condition#) {
// pretty big container, root view controller contains
// a grid view which opens Item detail container the same way
ItemDetailController *item = ....
[_rootViewController presentViewController:item animated:NO];
}
}
This became a really sluggish hell with iOS 8. Root view controller now appears visible for 0.5-1 second and then instantly redraws the screen with presented one. Moreover, the slowness of the presentation began to cause an Unbalanced calls to begin/end appearance transitions _rootViewController warning.
Initial quick hint was to move both conditions with calls to another function and call it with a zero-delay so it's processed in next main run loop:
[self performSelector:#selector(postAppFinishedPresentation) withObject:nil afterDelay:0];
or something like that. This fixes the unballanced calls issue, but the visual gap (rootviewcontroller, gap, presented one) becomes (obviously) even bigger.
The slowness of the presentation is also obvious when you call something usual as:
// Example: Delegate caught finished Sign In dialog,
// dismiss it and instantly switch to Profile controller
-(void)signInViewControllerDidFinishedSuccessfully
{
[self dismissViewControllerAnimated:NO completion:^{
UserProfileViewController *userProfile = ...
[self presentViewController:userProfile animated:NO];
}];
}
which should be completely fair piece of code, which used to perform direct transition without a visible flick of parent view controller on iOS 7. Now, same thing – parent flicks during the transition, even it's both handled without animation.
Does anybody face this as an issue? Any solutions? I'd love to solve this without a need to do some hilarious magic with UIWindows for each thing I need to transit flawlessly.
I am not sure the requirement is restricted to have root view controller and present anything over there.
But according to your code it's have welcome view controller thing and I think in this case, this logic is more useful.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Conditions
if (#first launch condition#) {
// quite small controller containing Welcome showcase
WelcomeViewController *w = ....
//It can be navigation or tab bar controller which have "w" as rootviewcontroller
[self.window setRootViewController:w];
}
else if (#last opened item condition#) {
// pretty big container, root view controller contains
// a grid view which opens Item detail container the same way
ItemDetailController *item = ....
//It can be navigation or tab bar controller which have "item" as rootviewcontroller
[self.window setRootViewController:item];
}
[self.window makeKeyAndVisible];
}
If you use Storyboard, why not try:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:[[NSBundle mainBundle].infoDictionary objectForKey:#"UIMainStoryboardFile"] bundle:[NSBundle mainBundle]];
ViewController *_rootController = [storyboard instantiateViewControllerWithIdentifier:#"root"];
[self.window setRootViewController:_rootController];
[self.window makeKeyAndVisible];
if (vcToShow == 1) {
ViewController2 *w = [storyboard instantiateViewControllerWithIdentifier:#"vc2"];
[_rootController presentViewController:w animated:NO completion:nil];
}
else if (vcToShow == 2) {
ViewController2 *w = [storyboard instantiateViewControllerWithIdentifier:#"vc3"];
[_rootController presentViewController:w animated:NO completion:nil];
}
It looks like there is no delay here.
The delay I had from a dismiss/present pair was fixed by this. It may or may not help your instance. I had to completely change my strategy of displaying/dismissing modal view controllers under iOS 8:
[_rootViewController presentViewController:vc1 animated:NO completion:nil];
if(iNeedToDisplayVC2) {
[vc1 presentViewController:vc2 animated:NO completion:nil];
}
So once I am done with vc2 later on, I dismiss both vc1 and vc2 in the same call. This strategy works under iOS 7 as well. I assume earlier versions too but I did not test them.
With iOS8, I've found the old presentation is not quite completed yet in the completion block, and calling dismiss or present right away can lead to console messages and sometimes the present/dismiss does not even occur. I have had some success by adding a further delayed perform to the second presentation:
[self dismissViewControllerAnimated:NO completion:^{
UserProfileViewController *userProfile = ...
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self presentViewController:userProfile animated:NO];
}];
}];
I'm trying to load my view then transition to it using a custom segue. The problem is that it's lagging quite a bit, and I can't seem to find the source other than the view being presented is loading during the segue.
- (IBAction)continueButtonClicked:(id)sender{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
IntroViewController *imageLoader = (IntroViewController*)[storyboard instantiateViewControllerWithIdentifier:#"imageLoader"];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// present
[self customSegue:imageLoader];
// dismiss
[self dismissViewControllerAnimated:YES completion:nil];
});
}
I've gone to the extent of incorporating grand central dispatch, but I see no difference. I have little to no experience with GCD either. I have the same segue going to another view and it loads without lag just fine.
You can only present UI on the main thread and you are trying to do on a background thread. Try this:
- (IBAction)continueButtonClicked:(id)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
IntroViewController *imageLoader = (IntroViewController*)[storyboard instantiateViewControllerWithIdentifier:#"imageLoader"];
// present
[self customSegue:imageLoader];
});
}
I've removed the [self dismissViewControllerAnimated:YES completion:nil]; line because dismissing the presenting view controller will result in the presented view controller not being able to unwind the segue.
I want to open a login screen and I have tried both programmatically and both with a segue.
I have seen similar questions but it didn't fix it for me.
Here are my two versions of code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (![[API sharedInstance] isAuthorized]) {
NSLog(#"I should Open login screen");
[self performSegueWithIdentifier:#"ShowLogin" sender:nil];
}
}
or
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (![[API sharedInstance] isAuthorized]) {
NSLog(#"I should Open login screen");
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
LoginScreen *vc = [sb instantiateViewControllerWithIdentifier:#"LoginScreen"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:vc animated:YES completion:NULL];
}
}
The segue is modal style.
In both cases the NSLog is printed and then I see a warning:
Warning: Attempt to present <LoginScreen: 0x1e5bd010> on <PhotoScreen: 0x1e5b82e0> whose view is not in the window hierarchy!
and the new view does not open.
Any hint on that?
Don't do this in viewDidLoad, your view isn't on screen yet, so that's why you get that error. You should do it in viewDidAppear (with no animation if you don't want to see the view of the controller that this code is in).
I am using a class inherited from UINavigationController present as a modal view, in the navigation bar I have a button 'Done' which will dismiss the modal view when user tap on it. Everything behave normal except the dealloc() in ImagePickerController, GroupPickerController (which is initialized as root view) not get called when I dismiss the modal view. This cause the leak of the memory.
Here is the code use it:
ImagePickerController *picker = [[ImagePickerController alloc] initWithRootViewController:nil];
// don't show animation since this may cause the screen flash with white background.
[self presentModalViewController:picker animated:NO];
picker.navigationBar.barStyle = UIBarStyleBlack;
[picker release];
Here is what's inside ImagePickerController, which is a UINavigationController:
- (id)initWithRootViewController:(UIViewController *)root {
GroupPickerController *controller = [[GroupPickerController alloc] initWithNibName:nil bundle:nil];
self = [super initWithRootViewController:controller];
[controller release];
if (self) {
self.modalPresentationStyle = UIModalPresentationPageSheet;
}
return self;
}
- (void)dealloc {
[super dealloc];
}
-(void) dismiss
{
[self.navigationController setViewControllers:nil];
[self dismissModalViewControllerAnimated:YES];
}
Here is the code in GroupPickerController, it response to a button in the navigation bar, to dismiss the modal view:
...
#pragma mark - button actions
- (void)done {
[self.parent dismiss];
}
I tried to manually remove the views from NavigationController, seemed not no effect at all...
[self.navigationController setViewControllers:nil];
Thanks for the help!
UPDATED:
Please disregard this question, apparently it's a mistake. :(
Finally get the problem solved... not change any of the code, but a rebuild the project. :(
First of all, you should not be subclassing UINavigationController:
This class is not intended for subclassing.
What does this line do?
controller.parent = self;
If the controller retains the parent-property, you have a retain cycle which would cause the issue you are describing. Remember that all view controllers in the UINavigationController stack can access the navigation controller with the -navigationController property.
There's is a difference between a UIViewController begin dismissed and released.
When you dismiss it, it can be released at any moment, but not necessarily immediately.
Are you sure you have a memory leak ? Maybe the picker is released a few seconds after the dimiss.
If you really have a memory leak, that means there is another place where you picker is retained.