I am new to storyboard and xcode (using 4.4)
I would like to implement a login condition, that if accomplished - the segue would work. Otherwise, the user should stay on the same view.
I created 2 UIView: ViewController and Bar Controller.
I also created a segue from ViewController to Bar Controller and set its identifier to loginSegue as modal.
In LoginViewController.m I added the following:
- (IBAction)login:(id)sender {
self.email = self.emailText.text;
self.password = self.passwordText.text;
if ([self.email isEqualToString:#"O"] && [self.password isEqualToString:#"O"])
[self performSegueWithIdentifier:#"LoginSegue" sender:sender];
else
[passwordText setText:#""];
}
When I debug the code - I see that the if skips to else in case of email and password that to not equal to O, however the segue (transition to the new view) still happens.
How can I prevent the user to be transferred to the new view?
BTW, if I remove the if else condition - the user is being transferred to the next view automatically. It seems to me that it was configured somewhere when I drew the segue from one view to the other.
Delete the blank line in between your if and [self perform...]
Additionally, you may want to consider keeping your if-else statements in braces to keep this problem from happening again:
if ([self.email isEqualToString:#"O"] && [self.password isEqualToString:#"O"]){
[self performSegueWithIdentifier:#"LoginSegue" sender:sender];
}else{
[passwordText setText:#""];
}
If this doesn't solve the problem then be sure to varify that the segue is attached to the two controllers and not directly to a button.
Related
I have created an UIViewController sub class which can either be pushed in a navigation stack of a UINavigationController or presented(modally) from any UIViewController. I need to identify whether my view controller is presented, if it is presented I need to add a toolbar with a close button at the top of the view controller. (else if it is pushed in navigation stack then the default close button will get added, by using that the user can go back.)
In all the available versions say 4.3, 5.0, till 6.0, from inside an UIViewController sub class, Can I assume that the view controller is presented(modally) if the following condition is satisfied.
if(self.parentViewController == nil || self.navigationController == nil)
With iOS 5, UIViewController gained a readonly property named presentingViewController, that replaces the older semantics of parentViewController (which now describes containment). This property can be used when a view controller needs to get at the view controller that’s presenting it — note: this will often be something else than what you’d expect, if you’re new to the API!
In addition, the isBeingPresented property had been introduced to pretty much solve the class of situations you’re currently in. Check for this property in your view controller’s viewWillAppear:.
Update
I overread that you seem to target iOS 4.3 as well:
In that case, you need to guard the call to isBeingPresented with an if ([self respondsToSelector:…]) you can then in the else block check for whether the parentViewController is not nil.
Another approach to backwards compatibility might be to override +resolveInstanceMethod: to add an implementation for -isBeingPresented at runtime. This will leave your calling sites clean, and you’d get rid of runtime-magic as soon as you let go of ancient iOS support ;-)
Note, though, that there are edge cases to this, and you initial approach as well, when running on iOS <5:
The view controller can be presented contained in any other view controller—including navigation controllers. When that last case happens, you’re out of luck: parentViewController will be nil, while navigationController will not. You can try to add gobs of unwieldy code to mitigate this limitation in older iOSes…or you could just let it go.
I use the this code to check whether the UIViewController is presented.
if (uiviewcontroller.presentingViewController != nil) {
// do something
}
I had a similar case, however the view controller that I presented is wrapped in it's own navigation controller. So in that view controller when I need to determine whether or not to add the close button vs a back button, I just check the navigation controllers stack size. If the screen is presented, the stack size should be one (needs close button)... and if it is pushed using an existing navigation controller, then stack size will be larger than one (needs back button).
BOOL presented = [[self.navigationController viewControllers] count] == 1;
To handle this kind of behavior, I usually set/reset a BOOL toggling it in viewWillAppear/viewWillDisappear methods.
By the way, your test condition seems incorrect. I think you should use
if(self.parentViewController != nil || self.navigationController != nil)
Why can't you just always add the toolbar to your view controller? Is there any case the view is loaded but never presented?
In Swift on iOS 9 (or later):
if viewController.viewIfLoaded?.window != nil {
// viewController is visible
}
#saikamesh.
As you use UINavigationController to navigate your viewControllers, I think you can use topViewController (Doc here) and visibleViewController (Doc again) to reach your intention.
You mention that :
when it is pushed in navigation stack then the default close button
will get added, by using that the user can go back
If the instance of the the specific UIViewController is important, I think it better to create a shared singleton instance and provide a global presented flag:
id specificVC = [SpecificViewController sharedInstance];
if (specificVC.isPushed) {
[self.navController popToViewController:specificVC animated:YES];
}
and to check if it is presented:
if ([self.navController.visibleViewController isKindOfClass:[SpecificViewController class]]) {
// Hide or add close button
self.isPresented = YES;
}
Or, you can read the much accepted answer.
:) Hope helps.
Please check this way:
for (UIViewController*vc in [self.navigationController viewControllers]) {
if ([vc isKindOfClass: [OffersViewController class]]){ //this line also checks OffersViewController is presented or not
if(vc.isViewLoaded){
NSLog(#"Yes");
}
}
}
You could do it like this, it's fast and safe
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
// Find the top controller on the view hierarchy
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
// If the top controller it is not already presented
if (![topController isKindOfClass:[YourViewController class]]) {
// Present it
[topController presentViewController:yourViewController animated:YES completion:nil];
}
else {
// do some stuff here
}
You can at any point check whether you have a modal view controller presented or not by using the modalViewController property from your navigation controller.
Ex:
UIViewController *presentedController = self.navigationController.modalViewController;
if (presentedController) {
// At this point, you have a view controller presented from your navigation controller
if ([presentedController isKindOfClass:[controllerYouWantToCheck class]]) {
// add your toolbar/buttons/etc here
}
}
One elegant answer that I haven't seen here:
// Edit: Added 2 other modal cases
extension UIViewController {
var isModal: Bool {
return self.presentingViewController?.presentedViewController == self
|| (navigationController != nil && navigationController?.presentingViewController?.presentedViewController == navigationController)
|| tabBarController?.presentingViewController is UITabBarController
}
}
credit: based on this gist
As Martin Reed said, this is the best way
BOOL presented = [[self.navigationController viewControllers] count] == 1;
if (presented) {
[self dismissViewControllerAnimated:YES completion:^{
// do whatever you need here
}];
}
else {
[self.navigationController popViewControllerAnimated:YES];
}
If it was me I'd have a custom init method and use that when creating the vc.
vc = [[[MyUIViewControllerSubClass alloc] init] initWithToolbarAndCloseButton:YES];
Small modification in #AmitaiB answer to create a function,
func isModallyPresented(tmpVC:UIViewController) -> Bool {
return tmpVC.presentingViewController?.presentedViewController == tmpVC
|| (tmpVC.navigationController != nil && tmpVC.navigationController?.presentingViewController?.presentedViewController == tmpVC.navigationController)
|| tmpVC.tabBarController?.presentingViewController is UITabBarController
}
Just check by calling:
if(isModallyPresented(tmpVC:myTopVC)){
//return true if viewcontroller is presented
}
I am making an application using two view controlers. When I am working on my first view I have posibility to go to another view using button "Settings" and method conected to this button looks like this:
-(IBAction)Settings:(id)sender{
[self presentModalViewController:settingsHandle animated:YES];
settingsHandle is an object of a second view class which is alloceted when the first view is loaded.
My problem starts while I am in a second view and i call method which include NSTimer object. This method is working during hmmm lets say 30 sec but it also can be 5 min, the result of this method is calling onother view the 3rd one.
Everything goes fine while am waiting for the result of this function in a second view.The result is that i am in a 3rd view.
When, during the method is working I am going to the first view from the second (using [self dismissModalViewControllerAnimated:YES]; )I can see that the method has finished(using NSLOG) but [self presentModalViewController:thirdview animated:YES]; is not working, just nothing happens.
so to sum up:
Waiting for the result in a secodnview (Succes third view uploaded)
Waiting for the result in a firstview (fail nothing happens)
And my goal is to make it happens from the firstview!
You can't present a ModalViewController from a ViewController that is dismissed. So, you need to keep track of the visible ViewController and call presentModalViewController from there. In your scenario, the easiest solution would be to make your NSTimer invoke a method in your first ViewController which goes like this
- (void)presentViewController:(NSTimer *)timer
{
if(self.modalViewController == nil)
[self presentModalViewController:settingsHandle animated:YES];
else
[self.modalViewController presentViewController];
}
If you create the NSTimer in your 2. ViewController, you would of course need a reference to the first ViewController. You could just pass this reference like this
-(IBAction)Settings:(id)sender{
settingsHandle.myParentViewController = self; //You need to create this var in settingsHandle
[self presentModalViewController:settingsHandle animated:YES];
//...
}
I am working this on Xcode 4.3 target iOS5 for iPhone.
I have View B which is segue-ed from View A using simple button action. In View B have text field and I need this text field input validated. The validation rule is simple: if the text field is empty then textFieldShouldEndEditing return NO. The code is:
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
if (![textField.text length]) {
return NO;
}
return YES;
}
This working fine. However, if I have not input anything and 'back' button is pushed (which is pop the view controller) return to View A and I push the button again in View A to navigate to View B, the view B is completely disabled and I can not edit anything in the text field. This is not the case when the validation is not implemented OR always return YES. I also tried if I input something but textFieldShouldEndEditing always return NO;
I trace the code and notice that the textFieldShouldEndEditing is also fired if the controller pop-ed. I think the problem is something to do with thins setting textFieldShouldEndEditing set to YES or NO, but I completely confused.
Please help...
Try checking if the view controller is the top view controller first:
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
if (self == self.navigationController.topViewController)
if (textField == self.confirmationCodeField)
if (textField.text.length < kAuthCodeMinLength)
return NO;
return YES; // default
}
I am using the following code to retrieve some messages and putting them into my inbox.
MyInboxVC *inboxVC=[MyInboxVC get ];
//upload all the pending messages
UINavigationController *devNavController=[[MyappMgr get]getDeveloperNavigationController ];
[devNavController pushViewController:inboxVC animated:YES];
[devNavController setNavigationBarHidden:NO];
I get the exception
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Pushing the same view controller instance more than once is not supported (<MyInboxVC: 0x1452a0>)'
What does it mean? What am I doing wrong?
I believe when you do some actions really fast this can happens too. I build something in like this:
if(![self.navigationController.topViewController isKindOfClass:[YOURCLASS class]]) {
Firstly handle the crash so it doesnt kill your app:
#try {
[self.navController pushViewController:viewController animated:NO];
} #catch (NSException * e) {
NSLog(#"Exception: %#", e);
} #finally {
//NSLog(#"finally");
}
Then if you get the error use popTo
- (void)pushViewController:(UIViewController *)viewController {
if (viewController) {
#try {
[self.navController pushViewController:viewController animated:NO];
} #catch (NSException * ex) {
//“Pushing the same view controller instance more than once is not supported”
//NSInvalidArgumentException
NSLog(#"Exception: [%#]:%#",[ex class], ex );
NSLog(#"ex.name:'%#'", ex.name);
NSLog(#"ex.reason:'%#'", ex.reason);
//Full error includes class pointer address so only care if it starts with this error
NSRange range = [ex.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
if ([ex.name isEqualToString:#"NSInvalidArgumentException"] &&
range.location != NSNotFound) {
//view controller already exists in the stack - just pop back to it
[self.navController popToViewController:viewController animated:NO];
} else {
NSLog(#"ERROR:UNHANDLED EXCEPTION TYPE:%#", ex);
}
} #finally {
//NSLog(#"finally");
}
} else {
NSLog(#"ERROR:pushViewController: viewController is nil");
}
}
It means that the ViewController returned from [MyInboxVC get] is already in the navigation stack of devNavController. You can not add the same object to the stack multiple times.
Apparently, you already have a MyInboxVC pushed earlier. Insure that you've popped it when it was no longer needed.
That's the "what's it mean" answer, but don't have enough info to know what you need to do to fix it.
My guess is your Navigation Stack is growing larger than you are expecting, meaning you are not popping as often as you should.
Are you performing this as part of a segue? If you are, there is no need to push a VC onto your Navigation Controller because the segue will do it already. That is why your error is occurring - you are pushing a VC that is already on the stack of the NavController.
It means you are pushing the same viewcontroller object to stack again when it's already in there.
[self.navigationController pushViewController:viewControllerObj animated:NO];
[self.navigationController pushViewController:viewControllerObj animated:NO];
check if u r pushing inside a loop or if u've accidentally placed the code more than one time..
The Main Reason for this problem, obviously if the code that pushed the view controller is called more than once. This could occur for many reasons, most common mistake when a callback method is triggered from a background thread, where this method could be executed more than once while it is still pushing the view controller.
Example:
Calling a service api on background thread when tapping a button, which will allow you to press the button more than once, and therefore the callback which pushes the view controller get called more than once.
#Melvin and #Sam solution is valid as long as you do not want to fix the original problem by not pushing more than once.
This is an expected behavior of UINavigationController where an exception is thrown when trying to push a view controller which is already present in the stack (Its there from iOS 2.2).
This was happening to me on a bar button click happening too fast, and was hard to reproduce, unless you went nuts on the button taps. The following fixed it by disabling the the button, starting the nav push, then enabling the button on the main thread (because it would be called after animation from the push occurred).
- (void)showMore
{
self.navigationItem.leftBarButtonItem.enabled = NO;
[self.navigationController pushViewController:moreVC animated:YES];
[self.navigationItem.leftBarButtonItem performSelectorOnMainThread:#selector(setEnabled:) withObject:#(YES) waitUntilDone:NO];
}
Make sure you are not adding the view controller twice in the navigation stack.
Eg - in below example self.mainViewC is pushed twice because it is initially instantiated in the navController, and is then pushed onto the navController again in the last line, which would cause this issue.
navController=[[UINavigationController alloc] initWithRootViewController:self.mainViewC];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
[navController pushViewController:self.mainViewC animated:NO];
In this case mainViewC has already been added to stack when initWithRootViewController was written. There is no need of pushViewController again.
In my case i was pushing a viewcontroller, but then also trying to clear the navigation stack so that there was no vc's to pop to after this new VC had shown
self.show(viewController, sender: nil)
if clearNavigationStack {
self.navigationController?.viewControllers = [viewcontroller]
}
you cant do this directly after pushing a viewcontroller, you will need to wait till the viewcontroller has fully shown before trying to reset the navigation stack
Another option that I have experienced is that [MyInboxVC get ] is not returning an instance of a MyInboxVC object at all. A tell tale sign of this would be that the error is saying 'Pushing the same view controller instance more than once is not supported (notTheInboxVC: 0x9e31660)' ie. the class being pushed more than once is not the MyInboxVC expected (a fall through from MyInboxVC not being allocated)
I fixed the same issue (Swift 4) with IB segue using :
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return navigationController?.topViewController is MainController ? true : false
}
In my case, I was pushing view controller and then trying to set array of navigation stack view controllers immediately after that. So it resulted in random crashes on transition (sometimes it crashed, sometimes it did not):
navigationController?.pushViewController(newViewController, animated: true)
navigationController?.viewControllers = [newViewController]
What I needed to do, is to use single line instead of those two (https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621861-setviewcontrollers):
navigationController?.setViewControllers([newViewController], animated: true)
[devNavController pushViewController:inboxVC animated:NO];
Set animated as NO
I've got a Tab Bar, and each of the tabs' view controllers is a nav controller. If you press on a tab that is already selected, it pops the view controller back. for one the tabs, i want this not to happen. What do i make a delegate of? I tried overriding
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
in the nav controller to return 0, but that doesn't stop it from popping!
user74574 is close, but you shouldn't return nil, you should return NO. Yes, technically they are both the same in term of the bits, bit types have meaning and ignoring that will (depending on the situation) result in warnings and/or bugs that could be detected via static analysis. The could you want to implement in your delegate should be something like this:
- (BOOL)tabBarController:(UITabBarController *)tabBarController_ shouldSelectViewController:(UIViewController *)viewController {
if (viewController == tabBarController_.selectedViewController) {
return NO;
} else {
return YES;
}
}
You can accomplish this by subclassing UITabBarController, and setting it as the UITabBarControllerDelegate for itself.
Implement tabBarController:shouldSelectViewController:, testing whether it is selecting the view controller you are concerned about, and if it is already selected. return nil if it satisfies the above.
If you'd like, I can put together some actual code, but this should get you in the right direction.