I am relatively new to iOS development. I developed an app for Android first, and now am porting it to iOS. One thing that I can't figure out is how to make a ViewController "go away". I'm accustomed to the finish() method in Android. With that method, the current activity ends itself and the user is presented with the the previous screen that was open prior to opening the current screen.
What I'm trying to accomplish is making my "create" screen go away after a record is saved. In the Android world, I would just call the finish() method and that would be taken care of. What is the iOS equivalent?
I have tried the following code in my iOS app, hoping that the view would be animated away.
[self.navigationController popToRootViewControllerAnimated:YES];
Edit:
The view was presented as below.
SettingsViewController *vc = [[SettingsViewController alloc] initWithNibName:#"SettingsView"];
controllers = [NSArray arrayWithObject:vc];
self.sideMenu.navigationController.viewControllers = controllers;
[self.sideMenu setMenuState:MFSideMenuStateClosed];
where controllers is defined as such:
NSArray *controllers = nil;
How did you present this view controller?
Did you use presentViewController:animated:completion:? If so, you want something like this:
[self dismissViewControllerAnimated: YES];
If you are using pushViewController:animated:, you are not talking about a modal view. You are talking about a normal ViewController you pushed onto the stack. To "undo" this, you need to pop the view controller:
[self.navigationController popViewControllerAnimated: YES];
Though finish() and [self dismissViewControllerAnimated:YES] are similar in terms of functionality, but they are not exactly same, when we call finish() method in the Android, we are programatically telling the Android system to destroy the activity completely from the memory, when we override the onDestroy() activity life cycle callback and add a log, then logs are shown when finish() is called. onDestroy() is also called when the system needs resources and it frees the memory by finishing the activity. But
[self dismissViewControllerAnimated: YES];
does not remove the UIViewController instance from the memory, the equivalent callback method for onDestroy() in IOS is viewDidUnload() which is not called on the message dismissViewController. I think the IOS system can only free the memory for the UIViewController instance when system needs resources but we can't programatically do that.
after pushing your viewcontroller call this method
func finish(){
var navigationArray = self.navigationController?.viewControllers //To get all UIViewController stack as Array
navigationArray!.remove(at: (navigationArray?.count)! - 2) // To remove previous UIViewController
self.navigationController?.viewControllers = navigationArray!
}
Related
I'm attempting to convert our application to storyboards and have hit what I believe is a bug in the handling of unwind segues when dealing with custom container controllers. We have a view controller which displays another and uses the view controller containment api to do this, I wire up the segue in IB then select a custom class for the implementation. The perform method looks something like this:
-(void) perform {
UIViewController *container = [self sourceViewController];
UIViewController *child = [self destinationViewController];
[container addChildViewController:child];
[container.view addSubview:child.view];
child.view.center = container.view.center;
[UIView transitionWithView:container.view
duration:0.35
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
child.view.alpha = 1;
} completion:^(BOOL finished) {
[child didMoveToParentViewController:container];
}];
}
That works perfectly, however I can't make it perform the unwind segue back to the container controller. I override viewControllerForUnwindSegueAction: fromViewController: withSender: and ensure that it's returning the correct value:
-(UIViewController *) viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
id default = [super viewControllerForUnwindSegueAction:action fromViewController:fromViewController withSender:sender];
NSAssert1(default == self, #"Expected the default view controller to be self but was %#", default);
return default;
}
I can also confirm that canPerformUnwindSegueAction:fromViewController:withSender is being called and doing the right thing, but to be sure I overrode it to return YES
-(BOOL) canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
return YES;
}
The next step I would expect to happen is for segueForUnwindingToViewController:fromViewController:identifier: to be called, however it never is. Instead the application crashes with an NSInternalInconsistencyException.
2012-10-01 10:56:33.627 UnwindSegues[12770:c07] *** Assertion failure in -[UIStoryboardUnwindSegueTemplate _perform:], /SourceCache/UIKit_Sim/UIKit-2372/UIStoryboardUnwindSegueTemplate.m:78
2012-10-01 10:56:33.628 UnwindSegues[12770:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not find a view controller to execute unwinding for <USCustomContainerViewController: 0x75949a0>'
*** First throw call stack:
(0x1c8e012 0x10cbe7e 0x1c8de78 0xb61f35 0x581711 0x45ab54 0x10df705 0x16920 0x168b8 0xd7671 0xd7bcf 0xd6d38 0x4633f 0x46552 0x243aa 0x15cf8 0x1be9df9 0x1be9ad0 0x1c03bf5 0x1c03962 0x1c34bb6 0x1c33f44 0x1c33e1b 0x1be87e3 0x1be8668 0x1365c 0x1e7d 0x1da5)
libc++abi.dylib: terminate called throwing an exception
Has anyone successfully used unwind segues combined with the view controller containment APIs? Any idea what step I'm missing? I've uploaded a demo project to github which shows the issue in the simplest demonstration project I could come up with.
The problem in your example is that there's no there there. It's too simple. First, you create your container view controller in a rather odd way (you don't use the new IB "container view" which is there to help you do this). Second, you've got nothing to unwind: nothing was pushed or presented on top of anything.
I have a working example showing that canPerformUnwindSegueAction really is consulted up the parent chain, and that viewControllerForUnwindSegueAction and segueForUnwindingToViewController are called and effective, if present in the right place. See:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch19p640presentedViewControllerStoryboard2
I have now also created a fork of your original example on github, correcting it so that it illustrates these features:
https://github.com/mattneub/UnwindSegues
It isn't really a situation where "unwind" is needed, but it does show how "unwind" can be used when a custom container view controller is involved.
This seems to be a bug – I would also expect unwind segues to work as you implemented.
The workaround that I used is explicitly dismissing the presented view controller in the IBAction method:
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController
identifier:(NSString *)identifier
{
return [[UIStoryboardSegue alloc] initWithIdentifier:identifier
source:fromViewController
destination:toViewController];
}
- (IBAction)unwind:(UIStoryboardSegue*)segue
{
UIViewController *vc = segue.sourceViewController;
[vc willMoveToParentViewController:nil];
if ([vc respondsToSelector:#selector(beginAppearanceTransition:animated:)]) {
[vc beginAppearanceTransition:NO animated:YES]; // iOS 6
}
UIView *modal = vc.view;
UIView *target = [[segue destinationViewController] view];
[UIView animateWithDuration:duration animations:^{
modal.frame = CGRectMake(0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
} completion:^(BOOL finished) {
[modal removeFromSuperview];
[vc removeFromParentViewController];
if ([vc respondsToSelector:#selector(endAppearanceTransition)]) {
[vc endAppearanceTransition];
}
}];
}
Brief history before the answer: I just ran into the same exact error message when trying to use multiple Container Views on one iPad screen in iOS 6 and calling unwind segues from code. At first I thought this may be a problem because my segue was created using Storyboards by CTRL-dragging from File Owner to Exit instead of from some UI control to Exit, but I got same results when I put test Close buttons on each VC and had them trigger the unwind segues. I realized that I'm trying to unwind an embed segue, not a modal/push/popup segue, so it makes sense that it fails to do it. After all, if the unwind segue succeeds and the view controller is unloaded from a Container View, iOS 6 thinks there'll just be an empty space on the screen in that spot. (In my case, I have another container view taking up screen real estate that's shown behind the container view which I'm trying to unload, but iOS doesn't know that since the two aren't connected in any way.)
Answer: this led me to realize that you can only unwind modal, push, or popover segues, be it within the main window or as part of a Navigation/Tab Controller. This is b/c iOS then knows that there was a previous VC responsible for the whole screen and it's safe to go back to it. So, in your case, I'd look into a way to tell iOS that your child container view is connected to your parent container view in a way that makes it safe to dismiss the child container view. For example, perform a modal/push/popover segue when displaying the child container view, or wrap both into a custom UINavigationController class (I assume you don't want the navigation bar, that's why custom class).
Sorry I can't give exact code, but this is the best I got to so far and I hope it's helpful.
Looks like this bug is fixed in iOS9.
What is the best way to display a view (in my case a login screen) on app resume. From looking around, I've been playing with the applicationDidBecomeActive event in my AppDelegate, but I cannot seem to get my head around how to properly display a view from here.
I've tried to grab the current window by using self.window and/or it's subviews, but from the AppDelegate self.window is nil.
So far this application seems to be wired up correctly, but I am baffled by two things.
A) why is self.window nil from within my AppDelegate's applicationDidBecomeActive event handler.
B) what is the correct/normal way of display a login view (or the like) on application resume.
Implement a custom UIViewController for all of your applications to inherent from. In this view controller implement logic in the viewWillAppear message to determine and show the login screen if necessary.
//CustomViewController.h
#interface CustomViewController : UIViewController
#end
//CustomerViewController.m
#implementation CustomViewController
-(void)viewWillAppear:(BOOL)animated{
if(login_required){
LoginViewController *loginView = [[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil];
[self presentModalViewController:loginView animated:false];
}
}
#end
Then, simply, in your login view controller make sure you call:
[self dismissModalViewControllerAnimated:false];
The benefits of this approach are two fold. Firstly, it's a very simple implementation. However, most compellingly, having a base class for an application's view controller presents the opportunity to extract common logic.
Jason,
I have worked on a security tutorial provided by Chris Lowe on raywenderlich.com that was intended to demonstrate how to use basic iOS security to lock the application.
The premise behind this tutorial though was that the application would prompt for login upon first launch and if application was resumed upon unlocking the device through the use of NSNoftificationCenter in viewDidLoad and subscribe the the notifications: deviceWillLock and deviceWillUnlock. All of this assumes the device is set to lock.
Basic iOS Security Tutorial Part 2 - This is the part that has the NSNotification registration.
Basic iOS Security Tutorial Part 1 - This is the first part of the tutorial for clarity.
I also ran into this problem and came across this question whilst researching a solution. I didn't want to create the intermediate super class for my views and I wasn't sure how it would work out with navigation controllers. I have come up with another solution that works well for me - so thought I would share it. It is based around the use of NSNotificationCenter .
In your app delegate create a property to hold a reference to the currently displayed view controller - say currentViewController.
Then in the applicationDidFinishLaunching method, register a block observer to update the currentViewController property like this:
[[NSNotificationCenter defaultCenter] addObserverForName:#"CurrentViewChanged"
object:nil
queue:nil
usingBlock:^(NSNotification *note)
{self.currentViewController = (UIViewController *)note.object;} ];
In your view controller implementations, update the viewDidAppear methods to notify the observer that a new view controller is being displayed by adding the following line
[[NSNotificationCenter defaultCenter] postNotificationName:#"CurrentViewChanged" object:self];
Finally, include code in the applicationDidBecomeActive method in your app delegate to force the modal display of your login screen.
UIStoryboard *mainStoryBoard = self.window.rootViewController.storyboard;
UnlockViewController *uvc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"modalUnlockView"];
uvc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.currentViewController presentViewController:uvc animated:YES completion:NULL];
A couple of additional items to note :-
You can disable the login screen display at anytime by posting a notification where the view controller passed is nil.
You only need to post the notification once for a navigation view controller at the top level. All view controllers in the navigation controller stack will be covered. I haven't checked, but I suspect the same is true for a tab view controller.
If you want to display the login screen the first time you enter the app after startup then include the following line in the applicationDidFinishLaunching method.
self.currentViewController = self.window.rootViewController;
I hope this is of some use.
Thanks
I just came across a crash showing a NSInvalidArgumentException with this message on an app which wasn't doing this before.
Application tried to present modally an active controller
UITabBarController: 0x83d7f00.
I have a UITabBarController which I create in the AppDelegate and give it the array of UIViewControllers.
One of them I want to present modally when tapped on it. I did that by implementing the delegate method
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
If that view controller is of the class of the one I want to present modally, I return NO and do
[tabBarController presentModalViewController:viewController animated:YES];
And now I'm getting that error, which seems to mean that you can't present modally a view controller that is active somewhere else (in the tabbar...)
I should say I'm on XCode 4.2 Developer Preview 7, so this is iOS 5 (I know about the NDA, but I think I'm not giving any forbidden details). I currently don't have an XCode installation to test if this crashes compiling against the iOS4 SDK, but I'm almost entirely sure it doesn't.
I only wanted to ask if anyone has experienced this issue or has any suggestion
Assume you have three view controllers instantiated like so:
UIViewController* vc1 = [[UIViewController alloc] init];
UIViewController* vc2 = [[UIViewController alloc] init];
UIViewController* vc3 = [[UIViewController alloc] init];
You have added them to a tab bar like this:
UITabBarController* tabBarController = [[UITabBarController alloc] init];
[tabBarController setViewControllers:[NSArray arrayWithObjects:vc1, vc2, vc3, nil]];
Now you are trying to do something like this:
[tabBarController presentModalViewController:vc3];
This will give you an error because that Tab Bar Controller has a death grip on the view controller that you gave it. You can either not add it to the array of view controllers on the tab bar, or you can not present it modally.
Apple expects you to treat their UI elements in a certain way. This is probably buried in the Human Interface Guidelines somewhere as a "don't do this because we aren't expecting you to ever want to do this".
I have the same problem. I try to present view controller just after dismissing.
[self dismissModalViewControllerAnimated:YES];
When I try to do it without animation it works perfectly so the problem is that controller is still alive. I think that the best solution is to use dismissViewControllerAnimated:completion: for iOS5
In my case i was trying to present the viewController (i have the reference of the viewController in the TabBarViewController) from different view controllers and it was crashing with the above message.
In that case to avoid presenting you can use
viewController.isBeingPresented
!viewController.isBeingPresented {
// Present your ViewController only if its not present to the user currently.
}
Might help someone.
The same problem error happened to me when I tried to present a child view controller instead of its UINavigationViewController parent
I had same problem.I solve it. You can try This code:
[tabBarController setSelectedIndex:1];
[self dismissModalViewControllerAnimated:YES];
For React Native Developer - Problem might not be in AppDelegate Or main.m if app has been successfully build and is running and will crash after splash or perhaps the error screen
Issue might be due to use of fonts/resources that is not available with xcode and not properly configured.. You can find out the error by commenting certain portion starting from App.js and drilling inside the navigation/screens and commenting the components till you find the component that is generating the error....
In my case the resource of fontFamily was making an issue which was used right after splash in walkthrough screen
<Text style={{fontFamily: Fonts.roboto}}>ABC</Text>
Here font roboto wasnot configured properly. Wasted entire days just debugging the error hope its helps you
In my case, I was presenting the rootViewController of an UINavigationController when I was supposed to present the UINavigationController itself.
Just remove
[tabBarController presentModalViewController:viewController animated:YES];
and keep
[self dismissModalViewControllerAnimated:YES];
Instead of using:
self.present(viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?)
you can use:
self.navigationController?.pushViewController(viewController: UIViewController, animated: Bool)
This is my way which supporting multiple Windows(from a single APP) on the iPad and nested modal present.
import UIKit
///✅Use this public method
public func SheetViewController(ViewController:UIViewController) {
for i in returnAvailableViewControllers().shuffled() {
if i.presentedViewController == nil && !ViewController.isViewLoaded {i.present(ViewController, animated: true, completion: {})}
}
}
///Returns all possible ViewControllers
private func returnAvailableViewControllers() -> [UIViewController] {
let 场景 = UIApplication.shared.connectedScenes
var 存储VC : [UIViewController] = []
for i in 场景 {
if i.activationState == .foregroundActive {
//⭐️Set up “foregroundActive” to give the user more control
var 视图控制器 = (i.delegate as? UIWindowSceneDelegate)?.window??.rootViewController
if 视图控制器 != nil {
存储VC.append(视图控制器!)
}
var 结束没 = true
while 结束没 {
//🌟Enumerate all child ViewController
视图控制器 = 视图控制器?.presentedViewController
if 视图控制器 != nil {
存储VC.append(视图控制器!)
} else {
结束没.toggle()
}
}
}
}
return 存储VC
}
I'm building a Cocoa application and have a question about using window controllers. The idea is that if the user selects New from the menu bar, an instance of MyWindowController which is a subclass of NSWindowController is created and a new window from MyWindow.xib is displayed.
I'm handling the action in the application delegate. From what I have seen after searching around something like the following could be done. Once the window is displayed I don't have any reason to store a pointer to the window controller anymore and since I allocated it I also autorelease it before displaying the window.
[[[[MyWindowController alloc] init] autorelease] showWindow:self];
Since the window is released soon afterwards the window will briefly display on the screen and then go away. I have found a solution where I retain the window controller in the -showWindow: method and let it release itself once it gets a windowWillClose notification.
- (IBAction)showWindow:(id)sender
{
[self retain];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification
object:self.window
queue:nil
usingBlock:^(NSNotification *note) {
[self release];
}];
[super showWindow:sender];
}
Is there a better way to do this? I have searched the Apple documentation and have not found anything on which practices to use. It sounds like something very basic which it should cover so maybe I'm just searching with the wrong terms.
Normally you would hold on to the window controller, and only release it when you are done with it. I'd say that your app delegate would be responsible for that. Just store them in an array if there can be multiple. Whilst your solution may work, it's not very elegant.
If you are working on a document based Cocoa app, you create the window controller in your document subclass method makeWindowControllers and let that class hold a pointer to your window controller.
func windowShouldClose(_ sender: NSWindow) -> Bool {
#if DEBUG
let closingCtl = sender.contentViewController!
let closingCtlClass = closingCtl.className
print("\(closingCtlClass) is closing")
#endif
sender.contentViewController = nil // will force deinit.
return true // allow to close.
}
I have a very simple app. 1 navigationController with 2 viewControllers.
The first view has only a button.
The second view has a map view (MKMapView).
I checked show currentlocation property of this map view
I created outlet and then connect everything by using Builder Interface.
In the dealloc method of the second one, I set nil to delegate of mapview, and then release mapview outlet.
mapView.delegate = nil;
[mapview release];
When I tap the button in view1, view2 will be loaded, and then I tap back button. If I do it normally, everything works well. But If I do it very quickly, repeat many times. The app will be crashed.
If I do not release mapView or I do not check showcurrentlocation property, app works well.
I can't figure out why this happen. Anyone helps me solve this problem. Thanks so much !
My fix in controller class was to ..
(void)dealloc
{
mapView.showUserLocation = NO; // Work around bug in MKMapView
[super dealloc];
}
Try launching your app via Instruments with Zombie instrument added. It should show you overreleased object. You'd also should add the Allocations instrument with VM tracker to see how memory consumption goes over time. This way you'd get your answer very quick.
p.s. more code would help better.