Window coordinates changing when alertview is visible while switching rootviewcontroller - ios

I am facing an issue with window coordinates for new UINavigationController as windows rootViewController, so I created a simple sample project which also reproduces it.
I launch the app with an initial ViewController having a button with the following action:
- (IBAction)buttonTapped:(id)sender {
[[[UIAlertView alloc] initWithTitle:#"" message:#"New VC in 3 seconds" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
[self performSelector:#selector(delayCall) withObject:nil afterDelay:3];
}
- (void)delayCall {
NewViewController *nvc = [[NewViewController alloc] initWithNibName:#"NewViewController" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:nvc];
[[UIApplication sharedApplication].keyWindow setRootViewController:nav];
}
NewViewController is in landscape mode, but comes up as in the below screenshot if a UIAlertView is shown before changing the rootViewController of the window and is not dismissed.
If I don't show the alert view or dismiss the AlertView before changing the rootViewController the new ViewController comes up fine.
Is there anything that can be done to prevent this? Switching rootViewController and showing AlertView is part of a larger app we are creating. Unfortunately we cannot change it now, so we'll have to find some workaround for this flow itself.

Related

UIAlertView delayed or not showing up when calling methods from another view

UIAlertView delayed or not showing up when pass another view. Any help would be greatly appreciated.
-(void)viewDidLoad{
levelContentController = [[UIAlertView alloc] initWithTitle:#""
message:#"Loading...
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil];
}
-(void)passToTestView:(id)sender{
[levelContentController show];
ViewController *viewController = [[ViewController alloc]init];
clickedLevelId = [[NSString alloc] init];
clickedLevelId = [testIdStringArray objectAtIndex:[sender tag]-1];
[viewController sendIndexMethod:sendIndex];
[viewController testCompletedArrayMethod:arrayOfCompletedTest];
[viewController parseTestURL:buttonTag getTestIdString:clickedLevelId];
viewController.viewSoundCheck = _levelSoundCheck;
[self presentViewController:viewController animated:YES completion:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[levelContentController dismissWithClickedButtonIndex:0 animated:YES];
}
The above code has some problems.
When passToTestView: selector is called, it'll try to present the alertView. But within the same method you are trying to present another view controller.
This will in turn call viewWillDisappear: where you are hiding the alertView.
It's recommended that if you want to present the alertView while displaying ViewController, create and display UIAlertView instance in the ViewController class's viewDidLoad or viewWillAppear:. Do not initialise and display it in this viewController.
To make the UIAlertView appear, you should call:
[contentLoadingController show];
Note that UIAlertView is deprecated in iOS9, and you should use UIAlertController.

PresentViewController only working once

I'm a bit dazzled here. And I do think it might be something stupid, but here it goes.
I'm using ABPadLockScreen to set a password and it works just fine when I open the app first time and if the app is closed (terminated) and reopened, but if I just go home and return to it I get the following warning
Warning: Attempt to present <ABPadLockScreenViewController: 0x7fdc70f1d5a0> on <ViewController: 0x7fdc70f18e00> whose view is not in the window hierarchy!
Just for more information here is the code that triggers it, it's inside the viewDidAppear
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
if (!self.pin && delegate.terminated) {
// [[[UIAlertView alloc] initWithTitle:#"No Pin" message:#"Please Set a pin before trying to unlock" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
//return;
[self dismissViewControllerAnimated:YES completion:nil];
} else if (!self.isPin && !delegate.terminated) {
ABPadLockScreenViewController *lockScreen = [[ABPadLockScreenViewController alloc] initWithDelegate:self complexPin:YES];
[lockScreen setAllowedAttempts:3];
lockScreen.modalPresentationStyle = UIModalPresentationFullScreen;
lockScreen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:lockScreen animated:YES completion:nil];
}
If you're wondering about the delegate, it was so the Password Screen would show up when the app was simply closed and reopened and that's when the warning occurs.
Also, if I simply switch from one view to another, the damn thing shows up again.
Thanks in advance

Push to ViewController without back button

I am developing an iOS app which contains login/authentication functionality - basically first time a user logins, in they need to enter relevant login details - then they are passed to main app screens - subsequent visits to the app they will be automatically authenticated.
All above works fine - the issue I have is with the Navigation bar - it appears in the main screen in the main part of the app with a back button - I don't want this to be displayed as they should not be able to return to the login screen once authenticated. I guess it's using the root navigation controller which explains the logic, but is there a way to ignore the navigation controller of the login section so the back button is not displayed in the main app.
Below is a screenshot of the structure to help my explanation - left hand group of screens are the login process right hand is the main app structure.
The code used to switch screens is as follows -
SWRevealViewController *swRevealController = (SWRevealViewController *)navVC;
swRevealController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:controller animated:YES];
Don't push view controller. Create new hierarchy with:
Objective-C
[self.navigationController setViewControllers:#[controller] animated:YES];
Swift
navigationController.setViewControllers([controller], animated:true)
In the Screen which implements after login, the ViewDidLoad method add the line to hide back bar button.
self.navigationItem.hidesBackButton = YES;
Additionally you can add an 'Logout' option as
UIBarButtonItem *backBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Logout" style:UIBarButtonItemStyleBordered
target:self
action:#selector(LogoutClick)];
self.navigationItem.leftBarButtonItem = backBarButton;
-(void)LogoutClick {
[self showLogoutAlert];
}
-(void)showLogoutAlert {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#""
message:#"Do you want to Logout ?"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Logout", nil];
[alert show];
}
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
Use a modal view controller for the login screen so its not part of your navigation controller hierarchy within your navigation root probably in view did load - sample code below:
- (void) viewDidLoad {
......
LoginVC *loginViewController = [LoginVC alloc] init];
loginViewController.parent = self;
[self presentViewController: loginViewController animated:YES completion:NULL];
return;
}
You may or may not dismiss the modal view from the root - if you do, you will need to be able to call the dismiss routine from loginViewController but there are other ways as well and you can put the dismiss inside the modal view (loginViewController)
(void) login_exit:(BOOL)success {
[self dismissViewControllerAnimated:YES completion:NULL];
if !(success) {
.... send message (UIAlertview and ask if he or she wants to try again)
}
else {
}
return;}
Very simple.. Just navigate to rootviewcontroller
SWRevealViewController *swRevealController = (SWRevealViewController *)navVC;
swRevealController.managedObjectContext = self.managedObjectContext;
//-- I think above lines not needed as per your question
[self.navigationController popToRootViewControllerAnimated:YES];
You need to hide the Navigation Bar in the ViewController before you push the SWRevealViewController by using the below line in viewDidLoad
Objective C
self.navigationController.navigationBarHidden = true;
Swift
self.navigationController?.navigationBarHidden = true;
It will not show the back button in the next view controller pushed
2022 answer
#IBAction func SomeScreen() {
let s = UIStoryboard ...
navigationController?.isNavigationBarHidden = true;
navigationController?.pushViewController(s, animated: true)
}
It's that easy.
Write this in viewDidLoad method
self.navigationItem.hidesBackButton = YES;
This will hide the back button for the first view.
ClassA* controller = [storyboard instantiateViewControllerWithIdentifier:#"ClassAIdentifier"];
if (controller != nil) {
[self.navigationController pushViewController:controller animated:YES];
[controller.navigationItem setHidesBackButton:YES animated:NO];
}
You MUST hide the navigationItem after push, otherwise it will be nil.

tabBarController delegate didn't work

I use below delegate method to set the tabBarController not to pop to another sub view controller by setting the return value to NO,
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
UIViewController *vc =[((UINavigationController *)viewController).viewControllers objectAtIndex:0];
if ([vc isKindOfClass:NSClassFromString(#"LYAppCategoryViewController")]) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"" message:#"" delegate:self cancelButtonTitle:#"" otherButtonTitles:#"", nil];
[alert show];
return NO;
}
else {
return YES;
}
}
but after "NO" was actually returned,the tabBarController still pop to another sub view controller. So it's kinda puzzling,am I getting it wrong, the scenario of using it?
quotes from Apple API "YES if the view controller’s tab should be selected or NO if the current tab should remain active."
thanks a lot for your kind help.
I still didn't get the reason why this happened,but I accomplished my target by adding codes like this:
[tabBarController setSelectedViewController:[[tabBarController viewControllers] objectAtIndex:0]];
[tabBarController setSelectedIndex:0];
tabBarController will not pop to another view controller if "forcing" to pop to the first one.

How should I present a Modal VIew Controller from TabBarController

I have an app whose initial scene is a tab bar controller with 3 tabs. I created a uitabbarcontroller class and set it to that scene (MainTabViewController).
In that class I call presentLogin from the viewDidAppear method and that method reads:
- (void)presentLogin{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (![prefs stringForKey:#"storedUser"] && ![prefs stringForKey:#"storedPass"]) {
NSLog(#"No user prefs stored");
// BUT WAIT, before all this, lets pop up a view controller for user registration
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
ModalViewController *popupController = [sb instantiateViewControllerWithIdentifier:#"ModalViewController"];
[self presentViewController:popupController animated:YES completion:nil];
} else {
NSString *storedUser = [NSString stringWithFormat:#"User:%#",[prefs stringForKey:#"storedUser"]];
NSString *storedPass = [NSString stringWithFormat:#"User:%#",[prefs stringForKey:#"storedPass"]];
UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:storedUser
message:storedPass
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Ok", nil];
[internetAlert show];
}
}
But the modalVC isnt showing for some reason. I get this crash log:
Attempting to begin a modal transition from <MainTabViewController: 0xa55d0d0> to <ModalViewController: 0x15e2b5e0> while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear to know the current transition has completed
I believe you get this error because the tab bar controller is putting the view of the controller in its first tab on screen at the same time you're presenting the modal controller. Instead of presenting it from the tab bar controller, present it in the viewDidAppear method of the controller in the first tab. Call it with no animation to see the modal view controller without seeing the firs tab controller.
Try to add a tiny delay like below:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(presentLogin) withObject:nil afterDelay:0.1];
}
The view of the tabbarcontroller contains the viewHierarchies of the viewControllers that the tab bar itself owns. Maybe something is of because of that. Try to see of you still get the error if you only have one viewcontroller set to the tabbar.

Resources