ViewWillAppear is not called when the top-front VC is dismissed - ios

as I wrote in the title, I'm not sure why ViewWillAppear is not called when another VC is dismissed. I think my project is a little bit tricky, so I'm gonna explain what is going on in my project.
Mostly, I configure UIs in code. I have two VCs, ListVC and CameraVC, and I have a tab bar and a navigation var in ListVC, which I configured all in code.
So in the SceneDelegate
I wrote something like
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: scene.coordinateSpace.bounds)
window?.windowScene = scene
window?.rootViewController = createTabBar()
window?.makeKeyAndVisible()
}
func createTabBar() -> CustomTabBarController {
let tabbar = CustomTabBarController()
tabbar.viewControllers = tabbar.setUpTabbarItems()
return tabbar
}
and in the CustomTabBarController class, since I just wanted to present the event list, I added
func createListNC() -> UINavigationController {
let ListVC = ListViewController()
ListVC.tabBarItem = UITabBarItem(title: "", image: UIImage(named: "add-icon"), tag: 0)
return UINavigationController(rootViewController: ListVC)
}
func setUpTabbarItems() -> [UIViewController]{
return [createListNC()]
}
So now I can display List VC on the home of the app. However, I made the CameraVC all in the storyboard and I added the following code when a user taps one of the Event cells in List VC and it presents CamearaVC.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Camera", bundle: nil)
let CameraVC = storyboard.instantiateViewController(identifier: "Camera")
self.present(CameraVC, animated: true, completion: nil)
}
So I can also present the CameraVC from ListVC.
But the problem happens when I want to back from CameraVC to ListVC.
I added IBAction to the button saying "< back" in CameraVC, and I can dismiss the CameraVC, however, since the cameraVC's screen is horizontal, the ListVC also stack with horizontal, which I want to make the ListVC vertical.
#IBAction func unwindToLiveList (_ segue: UIStoryboardSegue) {
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
So, I was planning to write a code for fixing the orientation of the app when a user back from CameraVC to ListVC.
I just wanted to know where I should put that fixing orientation code, so I added viewwillappear and some print statements to ListVC, but none of them are called when back from CameraVC to ListVC. I also wrote the same code in CustomTabBarController class, but it is never called...
So, I was wondering where and which file I can write the code for fixing the orientation to trigger when the user came back from CameraVC to ListVC.
Also, if anyone can explain why this is happening, please let me know.

The problem seems to be the way View controllers are presented now. Since the bottom view controller never actually leaves view will appear isn't called when dismissing anymore. I'm pretty sure it did in the past, but now that controllers present as overlays I guess it doesn't anymore.
However, you are in a navigation stack so if you just use that you'll get a free back button and viewWillAppear will get called.
let cameraVC = storyboard.instantiateViewController(identifier: "Camera")
self.navigationController?.pushViewController(cameraVC, animated: true)
As I said you'll get a free back button on cameraVC and that will pop cameraVC off the stack for you.
If you want to keep from using the navigationController for some reason take a look at
https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print(size)
}

The reason is about the change they had made in iOS13 (or 14). Before the default modal presentation mode (it seems that you are presenting modally) was full screen, now they are presented over the current context without making the presenting view controller to disappear, that is why willAppear is not called, it' already there.
If you want to change that behavior you can setup a different presentation style as .fullScreen.

Related

Dismiss the presented view controller after rotataion

on iPad, I have a a ViewController that presented popover on another ViewController.
private lazy var menuPadViewController = MenuViewController()
private func presentMenuVC(from sourceView: UIButton) {
let nc = UINavigationController(rootViewController: menuPadViewController)
nc.modalPresentationStyle = .popover
nc.popoverPresentationController?.sourceView = sourceView
present(nc, animated: true)
}
Due to UI of the presented MenuViewController, I need to dismiss it when device is rotate, otherwise it would be look so mess.
So, in viewWillTransition I set that the MenuViewController should be dismissed after rotation. It works pretty fine
public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
menuPadViewController.dismiss(animated: true)
}
The issue is, it works fine when the MenuViewController is presented (on the display), but when it's already dismissed, menuPadViewController.dismiss(animated: true) will dismiss the parent ViewController. I need to implement some conditions to only dismiss it if it's presented and on the display.
Would be awesome if you can show me the best reliable way to do it, many thanks!
From the presenting view controller, you can check the type of view controller that is currently being presented before dismissing it.
if let presented = presentedViewController,
presented is YourPresentedViewController {
// dismiss
}

Best way to implement a passcode VC lock feature? swift

I'm working on my first iOS/swift app. It's a modified diary entry app, but I currently have a feature I'm trying to implement where a passcode view controller is presented if the app goes to the background (and then foreground) or is made inactive. The problem i've had is with implementation.
My question: Is there a simple way of presenting the passcodeVC on top of the current vc when it entersforeground and then dismissing it, leaving the current VC.
I understand that other view controllers can determine if the app enters foreground, but i'd imagine it's better to do it with app delegate since it seems to be the universal listener for app states. Provided i have the view controller and passcode features working, how do i get the passcode view to present itself and then dismiss if the code is correct; revealing the screen that was below it (some code would be helpful as I've spent a couple of days and it's a little over my head).
Here was an extension i tried to build:
public extension UIViewController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
func hide() {
dismiss(animated: true, completion: nil)
}
}
Thanks.

Trigger events when ViewController covered by a presented ViewController

I would like to process code when a ViewController is no longer visible due to presenting a new ViewController.
I cannot use ViewWillDisappear etc since the controller is not technically ever dismissed from the stack - you just can't see it.
What process can I use so that code runs when the controller is no longer visible (i.e. topmost) and when it becomes visible again?
EDIT:
Seems some confusion here - not sure why.
I have a viewcontroller.
I use the following code to present another controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewControllerWithIdentifier("NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
self.presentViewController(navController, animated: true, completion: nil)
This controller does not trigger a viewWillDisappear on the previous controller since the previous view is not removed - just hidden.
I need to process code when this view is hidden (i.e. not visible) and, more importantly, process code when it becomes visible again.
When presenting a UIViewController if the presentation style has been set to UIModalPresentationOverCurrentContext it doesn't call the viewWillDisappear and related methods as the view never disappears or gets hidden.
A simple test to check if thats the case would be to set the NavController that you are using to have a clear background color. If you do this and present the NavController and you can still view the first UIViewController below your NavController content. Then you are using UIModalPresentationOverCurrentContext and that is why the viewDidDisappear isn't called.
Have a look at the answer referenced by Serghei Catraniuc (https://stackoverflow.com/a/30787112/4539192).
EDIT: This is in Swift 3, you can adjust your method accordingly if you're using an older version of Swift
If you won't be able to figure out why viewDidAppear and viewDidDisappear are not called, here's a workaround
protocol MyControllerDelegate {
func myControllerWillDismiss()
}
class MyController: UIViewController {
var delegate: MyControllerDelegate?
// your controller logic here
func dismiss() { // call this method when you want to dismiss your view controller
// inform delegate on dismiss that you're about to dismiss
delegate?.myControllerWillDismiss()
dismiss(animated: true, completion: nil)
}
}
class PresentingController: UIViewController, MyControllerDelegate {
func functionInWhichYouPresentMyController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
thisController.delegate = self // assign self as delegate
present(navController, animated: true, completion: {
// place your code that you want executed when it disappears here
})
}
func myControllerWillDismiss() {
// this method will be called now when MyController will dismiss
// place your code that you want executed when it re-appears here
}
}
Firstly, thanks to Serghei for his time in helping work through this.
To clarify, both my potential presented controllers were set to Full Screen presentation style in the storyboard, however one was being set to Custom via a piece of pasted code dealing with the presentation. I can't find the error with the other.
However, if I force a presentation style of Full Screen as part of the presenting process then all is ok.
Hopefully my frustrating afternoon can help to save someone else's - always try to understand the implications and processes involved in pasted snippets.

Navigation Bar gone

How can I present a view controller from my AppDelegate and have a Navigation bar added to that view with a back button to the previous view? I need to do this programmatically from my AppDelegate. Currently I can push a controller from there, but it doesn't act like a segue. It doesn't add a nav bar with a back button. Now I know I should be able to add one myself, but when I do it gets hidden. Currently I'm using pushViewController(), but I imagine that's not the best way to do it.
I had something that I think is similar, if not the same:
HIGH LEVEL VIEW
The general composition of my App (thus far, and specific to the issue at hand - note: details about classes provided for context, not required for resolution) is as follows:
UIViewController (ViewController.swift) embedded in a UINavigationController
Buttons on UIViewController segue to a view with a custom class:
ExistingLocationViewController - subclass of:
UITableViewController
One of the buttons (Add New Location) in the UINavigationController's Toolbar segues to view with another custom class:
NewLocationViewController - subclass of:
UIViewController
CLLocationManagerDelegate
UITextFieldDelegate
There are a number of other items here, but I believe the above is sufficient as the foundation for the issue at hand
RESOLUTION
In order to preserve the navigation-bar (and tool-bar) going both forward and back - I have the following code in my custom classes (note: the following is Swift-3 code, you may have to adjust for Swift-2):
override func viewDidLoad() {
super.viewDidLoad()
//...
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) // #=# not sure if this is needed
navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
}
You could actually omit the last two lines in viewWillDisappear, or perhaps even omit the entire override function
The net result (for me) was as depicted below:
Hope that helps.
If you want add a NavigationController in appDelegate you can do it like this,in this way,your viewcontroller is load from storyboard
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let vc = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("vc") as! ViewController
let nav = UINavigationController(rootViewController: vc)
self.window?.rootViewController = nav
self.window?.backgroundColor = UIColor.whiteColor()
self.window?.makeKeyAndVisible()
return true
}

How can I make an iOS app go back to initialVC when a user opens the app?

I'm a new programmer and I'm trying to make my app go back to the initialVC when a user clicks on the app. Right now when I run my app on the simulator and go to hardware -> home and click back on the app to open it, the view that is displayed is the view that I left when going on the simulator home page, not the VC I want it to display. I'd love to get some help from some more experienced developers. Thanks guys!
Update: Alright, after playing around a little bit with #Christian Woerz solution, I've come to understand this;
func applicationDidEnterBackground(application: UIApplication) {
var storyboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
// Here when instituting my VC, the string I pass it is the "storyboardID" I gave my VC. Then the type has to be the VC I want to show.
if let myController = storyboard.instantiateViewControllerWithIdentifier("Home") as? ViewController {
// I may be wrong, but isn't there something that's supposed to go before "presentViewController"?
presentViewController(myController, animated: true, completion: nil)
}
}
After understanding a little more what's going on, I have a new question. If the VC I want to show is embedded in a navigationController, should I show the navigationController instead?
In your AppDelegate.swift to func applicationDidEnterBackground(application: UIApplication) add:
let nc = window.rootViewController as UINavigationController
nc.popToRootViewControllerAnimated(true)
This only works if your root view controller is a UINavigationController.
If you don’t have a UINavigationController you should be able to:
window.rootViewController.dismissViewControllerAnimated(true, completion: false)
You have to use the AppDelegate.swift file. In there, you find the method:
func applicationDidEnterBackground(application: UIApplication!) {
}
This method gets called, everytime your application enters the background. So you can add a function to show the ViewController. But first you have to access your storyboard:
var storyboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
After that, you can use the storyboard to access your ViewController.
func applicationDidEnterBackground(application: UIApplication!) {
if let yourController = storyboard.instantiateViewControllerWithIdentifier("YourId") as? ResultViewController {
presentViewController(yourController, animated: true, completion: nil)
}
}
But you have to add an identifier to your ViewController The nice thing about that is, that you can call every ViewController and not just the rootviewcontroller:
In your UIApplicationDelegate implementation, implement applicationWillEnterForeground and you can pop stuff off your navigation stack, or do whatever, there.

Resources