I know this might look like a duplicate but it's not because all of the questions/posts that I have found have not been related to mine, or not working.
Here is what I have. My root view controller is a HoldingViewController that has a left, main and right view controller. In my main view controller, in the view did load, I have a tutorial view controller that is launched if the user has not seen the tutorial yet. The problem I get when I dismiss the view controller is Warning: Attempt to present <TutorialViewController: 0xade1780> on <HoldingViewController: 0xaaaa500> while a presentation is in progress!
The call in the Main View Controller's view did load is:
if(![[NSUserDefaults standardUserDefaults] valueForKey:#"hasSeenTutorial"])
{
[[NSUserDefaults standardUserDefaults] synchronize];
GRxTutorialViewController *grxTutorialViewController = [[GRxTutorialViewController alloc]
initWithNibName:#"GRxTutorialViewController" bundle:[NSBundle mainBundle]];
grxTutorialViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:grxTutorialViewController animated:NO completion:nil];
}
Inside the tutorial view I created a button to dismiss this modal view controller. It is:
-(void)skipIntro
{
NSLog(#"Skip pressed");
[self dismissViewControllerAnimated:YES completion:nil];
}
I've also tried to put in the HidingViewController's viewDidLoad and it doesn't even display it and this message is logged:
Warning: Attempt to present <TutorialViewController: 0xaac0ed0> on <HidingViewController: 0xaabe8d0> whose view is not in the window hierarchy!
Does anyone have any ideas how to resolve the problem or what my issue is and what I'm doing wrong? Any help is appreciated. Thanks in advance!
Check [[NSUserDefaults standardUserDefaults] valueForKey:#"hasSeenTutorial"] in your AppDelegate and change your root view controller as follow:
GRxTutorialViewController *grxTutorialViewController = [[GRxTutorialViewController alloc]
initWithNibName:#"GRxTutorialViewController" bundle:[NSBundle mainBundle]];
self.window.rootViewController = grxTutorialViewController;
In your Tutorial don't dismiss it. Present HoldingViewController instead.
Launching tutorial view controller in HidingViewController's viewDidAppear method may help you
Edit For More Details
Just keeping one boolean value, you can avoid loop. If you want to show tutorial only once,
- (void)viewDidLoad
{
[super viewDidLoad];
self.tutorialShouldDisplay = YES;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.tutorialShouldDisplay && ![[NSUserDefaults standardUserDefaults] valueForKey:#"hasSeenTutorial"])
{
self.tutorialShouldDisplay = NO;
/*
should set NSUserDefaults properly for key #"hasSeenTutorial"
*/
GRxTutorialViewController *grxTutorialViewController = [[GRxTutorialViewController alloc] initWithNibName:#"GRxTutorialViewController" bundle:[NSBundle mainBundle]];
grxTutorialViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:grxTutorialViewController animated:NO completion:nil];
}
}
As #hasan said you can do some modification on rootViewController in AppDelegate. However, I don't think is suitable for your structure.
Actually, there are many solutions exits, but you have to change structure. If you don't want to change your structure, this solution should work.
Related
This is going to take some explaining... So I'll do it in order:
I have a navigation controller where the rootViewController is called TipsCollectionViewController.
I have a UserViewController that's loaded as a popup:
UserViewController * userView = [[UserViewController alloc] initWithNibName:#"UserView" bundle:[NSBundle mainBundle]];
[userView setUEmail:email];
[self presentPopupViewController:userView animationType:MJPopupViewAnimationSlideTopBottom];
I then have another popup that loads on top of THAT popup:
Place *p = [placeArray objectAtIndex:indexPath.row];
DetailPlaceViewController *pvc = [[DetailPlaceViewController alloc] init];
[pvc setPlace:p];
NSLog(#"%#", p.PName);
[self presentPopupViewController:pvc animationType:MJPopupViewAnimationSlideTopBottom];
Now there's a reason I've done this: the AppDelegate features a Navigation controller and previously I loaded the UserView like this:
[self.navigationController presentViewController:userView animated:YES completion:nil];
But this meant that the UserView would load on the iPhone but not on the iPad for some odd reason. But when I switched it to the popup view it worked fine.
So now I load both the UserView and the DetailPlaceView in a popup... but now it CLOSES on the iPad but not on the iPhone.
Here's the code for closing the detail view:
- (void) didTapBackButton:(id)sender {
NSLog(#"View Controller Number: %lu", (unsigned long)self.navigationController.viewControllers.count);
if(self.navigationController.viewControllers.count > 1) {
[self.navigationController popViewControllerAnimated:YES];
NSArray *stack = self.navigationController.viewControllers;
TipCollectionViewController *tipsVC = stack[stack.count-1];
[tipsVC.collectionView reloadData];
}
I know there's a better way to handle this whole thing... but what should I be doing differently?
UPDATE
If I switch it back to:
[self.navigationController pushViewController:userview animated:YES];
...for the UserView on the iPhone and:
[self.navigationController pushViewController:pvc animated:YES];
...for the view that loads after that, then the UserView will load... but the next viewcontroller (the DetailPlaceViewController) won't load. I think that's my main problem. I could probably dismiss the second view controller at that point if I could get it to load. Any ideas?
[self presentPopupViewController:pvc animationType:MJPopupViewAnimationSlideTopBottom];
if you are using this,presentPopupViewController is used to display like modal view which cant be dismissed without any custom cancellation action.
To use popOverViewController method, use pushViewController. Then that one you can dismiss by this technique.
Ultimately all I had to do was switch:
[self.navigationController pushViewController:pvc animated:YES];
to:
[self presentViewController:pvc animated:YES];
And it works. It opens and closes the view and everything works as it should.
I'm getting a warning on the DetailView once you click an item:
Presenting view controllers on detached view controllers is discouraged <UserViewController: 0x16e02020>
So I'll wait a couple of days to accept my answer. Otherwise, despite the warning, it seems to be working.
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];
}];
}];
EDIT
After some more digging around it has become apparent that if i try to call a segue from the view controller class after a fast application switch to/from Safari i get an exception stating that the segue does not exist. It definitely does, in the VC that is loaded on screen, and i have checked the spelling and it's 100% correct.
What makes it even weirder is that if i call that exact same method from an IBAction it works fine.
Here's the code i am using:
-(void)goToPhotoPage{
[self performSegueWithIdentifier:#"twitterAuthComplete" sender:self];
}
If i call this when the app resumes i get an exception, however if i call it from this IBAction, attached to a UIButton, with doing nothing different the segue works as expected and no exception.
- (IBAction)twitterToPhoto:(id)sender
{
[self goToPhotoPage];
}
Arrrrgrghrhhgrgrg!
ORIGINAL QUESTION
I am working on an iPad application that uploads user photos to Facebook/Twitter.
As part of the process i have to jump to Safari to do OAuth for twitter, the app then jumps back via a specific URL and get's the tokens e.t.c
However for some reason when the application re-awakes i cannot trigger any segue's or manually load any VC's.
I have a success block in my AppDelegae which get's called when the upload is complete which calls a method in the VC to close a spinner and segue to the success view.
The spinner stops as expected, but no matter what i try i cannot get the segue to work.
Everything is hooked up, the segue has an ID and i get no error or exceptions in the console, just nothing happens. The only way i can get code triggered segue to work after this point is to use a user trigger one connect to a UIButton, after that one complete they start to work again.
Here is the code with callback in my AppDelegate:
- (void)postToTwitter:(NSString*)postText image:(UIImage*)image
{
NSData *data = UIImageJPEGRepresentation(image, 0.5);
[_twitter postStatusUpdate:postText
mediaDataArray:#[data]
possiblySensitive:nil
inReplyToStatusID:nil
latitude:nil
longitude:nil
placeID:nil
displayCoordinates:nil
uploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
} successBlock:^(NSDictionary *status) {
ViewController * vc = [[ViewController alloc]init];
[vc triggerSuccess];
} errorBlock:^(NSError *error) {
ViewController * vc = [[ViewController alloc]init];
[vc uploadFail:error];
}];
}
in the VC i have tried the following:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
}
As well as:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
In a moment of pure desperation i even tried:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
//[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
[self performSelector:#selector(segueToSuccessPart2) withObject:nil afterDelay:0.25];
}
- (void)segueToSuccessPart2
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
I've obviously missed something, and i'm guessing it's to do with the application going into the background and the VC being "unloaded" from memory, and needing re-instatiating, but i'm not sure where to begin with that.
Any help would be greatly appreciated as i'm about to throw the computer out of the window . .
Thanks
Gareth
Edit 1
After doing some more troubleshooting it appears that during this call in the VC:
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
self.navigationController is nil . . .
I'm guessing that is likely the cause of the issues i'm seeing?
Edit 2
So as suggested by Kelin, i am trying to make sure i retain the Navigation Controller.
I have done the following in AppDelegte:
#property (nonatomic, strong) UINavigationController *navController;
and
#synthesize navController;
But when i try to access this from the VC using the below code, it's still nil . .
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
UINavigationController *navController = appDelegate.navController;
[self dismissViewControllerAnimated:NO completion:nil];
[navController popToRootViewControllerAnimated:NO];
Edit 3
I have also added this to the AppDelegate, which means the Navigation Controller is no longer nil. But still nothing happens . . .
if (!self.navController)
{
self.navController = [[UINavigationController alloc]initWithNibName:#"Main" bundle:nil];
}
The problem is you trying to perform segue from storyboard using programmatically created View Controller.
You use -performSegueWithIdentifier: sender: completely wrong. In this method "sender" is a button, or any other control which action initialises segue, but not the view controller to be pushed.
Usually segue is created with storyboard. But if you really need to create it manually, call:
-[UIStoryboardSegue initWithIdentifier:source:destination:]
where source and destination are view controllers.
Read this: https://developer.apple.com/library/ios/recipes/xcode_help-interface_builder/articles-storyboard/StoryboardSegue.html
or this (about custom segues):
You can not do both dismiss and pop together either you have to pop or dismiss the view controller and
Every ViewController have it's own NavigationController which is default point out to navigation controller you added to AppDelegate.
So, You also can access it by self.navigationController (Edit 3)
Also check whether you initialised navigation controller in
AppDelegate
self.navController = [[UINavigationController alloc] initWithRootViewController:firstVC];
I use the following code to switch from main view (ViewController.h)to another view (TouchViewController) and then similarly switch from this view to the next view (QuestionsViewController).
How do I go back to the main view from here? i.e what is the value for initWithNibName for MainStoryboard.storyboard?
- (IBAction)startGame:(UIButton *)sender {
TouchViewController *second = [[TouchViewController alloc] initWithNibName:#"TouchViewController" bundle:nil];
[self presentViewController:second animated:YES completion:nil];
}
and
- (IBAction)selectTopic:(UIButton *)sender {
NSString *category = [[sender titleLabel] text];
[[NSUserDefaults standardUserDefaults] setObject:category forKey:#"MyKey"];
QuestionsViewController *second = [[QuestionsViewController alloc] initWithNibName:#"QuestionsViewController" bundle:nil];
[self presentViewController:second animated:YES completion:nil];
}
Now to go back to the main view i have a problem i.e
- (IBAction)backtoMainMenu:(id)sender {
ViewController *second = [[ViewController alloc]initWithNibName:#"MainStoryboard.storyboard" bundle:nil];
[self presentViewController:second animated:YES completion:nil];
}
At this point the app just crashes! I suppose it's because initWithNibName:#"MainStoryboard.storyboard" can't be used but the ViewController does not have any other xib file - so what do I do?
First of all storyboard can't be used as a xib file they are not the same thing. Second you have one view controller that presents a view controller that also present a view controller so what you can do is something like this from the third view controller:
[self.presentingViewController.presentingViewController
void)dismissViewControllerAnimated:YES completion:nil];
This is the fastest way you can do it, but it's not really reusable, if you have a more complex view controller hierarchy than it will be really ugly to do it like this, other solutions can be delgates or notifications.
Also just a piece of advice, when you want to go back to a previous screen in general you don't what to create a new view controller (as you try in backToMainMenu method, you simply want to remove all the view controllers (screens) until you reach the desired screen.
I have a view-based application with three xib files, each with its own view controllers. How do I change from one to another? I use this to move from xib 1 to xib 2, but when I use the same code to move from xib 2 to xib 1, i get a EXC_BAD_ACCESS on the [self presentModal....] line.
MapView *controller = [[MapView alloc] initWithNibName:#"MapView" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:controller animated:YES];
How can I freely move from one xib to another?
What I think you are trying to do is is present a modal view and then dismiss it, right? If that is the case then you put the code below in the method that you use to dismiss it(e.g. -(IBAction)dissmissModalView)
[self.parentViewController dismissModalViewControllerAnimated:YES];
Hopefully that works. Let me know.
initWithNibName isn't really necessary... you can change that to nil.
So, here is the correct code (without animation):
MapView *mapView = [[MapView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:mapView animated:NO];
You should not be receiving EXC_BAD_ACCESS when trying to go back to view 1 using present. If you cannot resolve it, just use this instead:
[self dismissModalViewControllerAnimated:YES];
The second view controller will disappear and the first view controller will be visible again.
Note that presenting modal view controllers like the other answers here will mean that you have an ever-accumulating stack of view controllers. Use the application long enough and it will crash.
Instead, you can swap out the view from the application's window. Here's one way of doing that:
Add a data member to your app delegate to store the current view:
#class MyAppDelegate : NSObject <...>
{
UIViewController* currentVC;
}
and add a message there to swap VCs:
-(void)setCurrentVC:(UIViewController*)newVC
{
if (newVC==currentVC) return;
if (currentVC!=nil)
[currentVC.view removeFromSuperview];
currentVC = newVC;
if (newVC!=nil)
[self.window addSubview:newVC.view];
}
and to swap from one screen to another:
MapView* mapView = [[MapView alloc] init];
[[[UIApplication shared] delegate] setCurrentVC:mapView];