Can't Dismiss View controller - swift

I am trying to dismiss a View Controller that has been presented as a modal .overCurrentContext.
The controller is presented like so
let vc= UIViewController()
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
When I call dismiss inside the vc controller that has appeared it does nothing.
To give more detail about the parent ViewController, it is a ViewController inside a NavigationController and it is third on the NavigationController's VC stack.

So the actual solution was very obscure.
I was using a Pod called FloatingPanelController that was causing some issues with dismissing my entire view stack intermittently.
In order to resolve it I modified an extension in the Pod
public extension UIViewController {
#objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
}
#objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Call dismiss(animated:completion:) to a content view controller
if let fpc = parent as? FloatingPanelController {
if fpc.presentingViewController != nil {
self.fp_original_dismiss(animated: flag, completion: completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
}
return
}
// Call dismiss(animated:completion:) to FloatingPanelController directly
if let fpc = self as? FloatingPanelController {
if fpc.presentingViewController != nil {
self.fp_original_dismiss(animated: flag, completion: completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
}
return
}
// For other view controllers
self.fp_original_dismiss(animated: flag, completion: completion)
}
}
In particular I removed the last line:
self.fp_original_dismiss(animated: flag, completion: completion)
This is what caused the issue. I had no idea that this method was actually overriding the entire dismiss method. Once I re-inserted that line the issue was resolved.
This is likely very little use to anyone else!

Related

<_SFAppPasswordSavingViewController:> that is already being presented by <UIKeyboardHiddenViewController_Save>

I am working on an iPad application that has both Portrait and Landscape orientation. The issue that I am facing is regarding the the popup presentation that iPad shows you for saving password while you log-in in the application.
Now, when the pop-up is presented, at that moment if I try to rotate the device, the application crashes with the message
Fatal Exception: NSInvalidArgumentException
Application tried to present modally a view controller <_SFAppPasswordSavingViewController: 0x107be7230> that is already being presented by <UIKeyboardHiddenViewController_Save: 0x107a49190>.
Now, the problem is that SFAppPasswordSavingViewController presentation is not controlled by me. Also UIKeyboardHiddenViewController_Save,is something I am not controlling.
Please help me out on how to solve this issue or let me know if I am missing anything.
This is how I am presenting Login screen in my application.
guard let loginVC = LoginVC.loadFromXIB() else {
Log.w("LoginVC could not be loaded")
return
}
let navVC = PopNavigationController(rootViewController: loginVC)
UIWindow.key?.replaceRootViewControllerWith(navVC, animated: true, completion: nil)
Here replaceRootViewControllerWith is a custom function that is an extension on UIWindow.
extension UIWindow {
func replaceRootViewControllerWith(_ replacementController: UIViewController,
animated: Bool,
completion: (() -> Void)?) {
let snapshotImageView = UIImageView(image: self.snapshot())
self.addSubview(snapshotImageView)
let dismissCompletion = { [weak self] in // dismiss all modal view controllers
self?.rootViewController = replacementController
self?.bringSubviewToFront(snapshotImageView)
if animated {
UIView.animate(withDuration: 0.4, animations: { () -> Void in
snapshotImageView.alpha = 0
}, completion: { (_) -> Void in
snapshotImageView.removeFromSuperview()
completion?()
})
} else {
snapshotImageView.removeFromSuperview()
completion?()
}
}
DispatchQueue.main.async { [weak self] in
if self?.rootViewController?.presentedViewController != nil {
self?.rootViewController?.dismiss(animated: false, completion: dismissCompletion)
} else {
dismissCompletion()
}
}
}
}

How to push controller after dismiss presented controller in swift?

Hey I am showing a controller (A) in main controller with presentation style (not push), and I want to button tapped and push another controller (B) after dismiss this (A) controller, this situation occurred in main controller. I am using protocol for this situation. Any idea for that ? Code like below.`
//this is dismiss button action
var segueDelegate: segueFromController?
#objc func dismissController() {
self.dismiss(animated: true) {
self.segueDelegate?.segueFromController()
}
//and this one is protocol function in main controller
func segueFromController() {
let contProfile = ContViewController(collectionViewLayout: UICollectionViewFlowLayout())
navigationController?.pushViewController(contProfile, animated: true)
}
// and I am making "self" this protocol in main controller's didload
let aCont = AController()
override func viewDidLoad() {
super.viewDidLoad()
AController.segueDelegate = self
}
// protocol
protocol segueFromController {
func segueFromController()
}
// this is presenting (A) controller code in main page
func openController() {
let preController = AController()
preController.modalPresentationStyle = .fullScreen
self.present(preController, animated: true, completion: nil)
}
First you need to make this segueDelegate weak
protocol segueFromController : class {
func segueFromController()
}
weak var segueDelegate: segueFromController?
func openController() {
let preController = AController()
preController.segueDelegate = self
preController.modalPresentationStyle = .fullScreen
self.present(preController, animated: true, completion: nil)
}
Try to dismiss without animation
self.dismiss(animated: false) {
self.segueDelegate?.segueFromController()
}

access the last element of the UIPageViewController

How do I access the last item of a UIPageViewController so I can set self as its delegate?
self.present(wizard, animated: true, completion: {
let lastVC = wizard.viewControllers?.last as! thirdPageController
lastVC.delegate = self
})
I think the third item wasn't loaded yet so .last returns the wrong page (the first one)
Swift 5
Create your own UIPageViewController:
class MyPageViewController: UIPageViewController {
private lazy var _viewControllers = [UIViewController]()
public var lastViewController: UIViewController? {
return _viewControllers.last
}
override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
super.setViewControllers([viewController], direction: direction, animated: animated, completion: completion)
_viewControllers.removeAll()
_viewControllers.append(viewControllers)
}
}
Class usage:
let myPageViewController = MyPageViewController()
myPageViewController.lastViewController // This is an optional value
Finally you should change the class of wizard UIPageViewController to MyPageViewController
Then you can use this new class like:
self.present(wizard, animated: true, completion: {
if let lastViewController = wizard.lastViewController as? thirdPageController {
lastViewController.delegate = self
}
})

self.dismiss resets parent view controller's screen data

I am presenting a view controller inside the ParentViewController:
let item = getCurrentItem()
let presentViewController = viewController as! TestViewController
presentViewController.id = item.id
presentViewController.dismissController { // my custom delegate to inform the parent vc
self.dismiss(animated: true, completion: nil) // presenting view controller is closing then viewWillAppear will be triggered
}
self.present(presentViewController, animated: true, completion: nil)
But when dismiss is called, the ParentViewController's global arrays and variables are back to their initial values.
class ParentViewController {
// MARK: - Stored Properties
private let refreshControl = UIRefreshControl()
private var items = [String:[Item]]()
private var screenLoaded: Bool = false
// MARK: - Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// after dismiss is called viewWillAppear gets hit but the "screenLoaded" is always false
if !screenLoaded {
screenLoaded = true
}
// items is initialised as well
}
private func openPopover() {
let item = getCurrentItem()
let presentViewController = viewController as! TestViewController
presentViewController.id = item.id
presentViewController.dismissController { // my custom delegate to inform the parent vc
self.dismiss(animated: true, completion: nil) // presenting view controller is closing then viewWillAppear will be triggered
}
self.present(presentViewController, animated: true, completion: nil)
}
}
How can I keep the parent view controller's original state after the dismiss is called?
After hours of digging Apple's documentations finally I found the following solution and it works for me. Hope it helps someone in the future.
[https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/overcurrentcontext][1]
presentViewController.modalPresentationStyle = .overCurrentContext
Also, if there is a tabbar, the bottom of the child view controller will be under it if you set the presentation style to overCurrentContext. The workaround is to present the child view controller from the window's rootViewController.
sender.view.window?.rootViewController?.present(viewController, animated: true, completion: nil)
Now when the dismiss is called viewWillAppear is not triggered and I can keep the parent view controller's data and it's state.

Return control to function when modal viewcontroller dismissed

I need to present a modal VC that sets a property in my presenting VC, and then I need to do something with that value back in the presenting VC. I have to be able to pass pointers to different properties to this function, so that it's reusable. I have the code below (KeyPickerTableViewController is the modal VC).
It should work, except not, because the line after present(picker... gets executed immediately after the picker is presented.
How do I get my presenting VC to "wait" until the modal VC is dismissed?
#objc func fromKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &sourceKey, presentingFrom: button)
}
#objc func toKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &destKey, presentingFrom: button)
}
fileprivate func setKey(for key: inout Key!, presentingFrom buttonItem: UIBarButtonItem) {
let picker = KeyPickerTableViewController()
picker.delegate = self
picker.modalPresentationStyle = .popover
picker.popoverPresentationController?.barButtonItem = buttonItem
present(picker, animated: true, completion: nil)
if let delKey = delegatedKey {
key = delKey
}
}
You could use delegate pattern or closure.
I would do the following
1. I would not use inout pattern, I would first call the popover and then separately update what is needed to be updated
2. In KeyPickerTableViewController define property var actionOnDismiss: (()->())? and setting this action to what we need after initialisation of KeyPickerTableViewController
I could show it in code, but the abstract you've shown is not clear enough to come up with specific amendments. Please refer the illustration below.
import UIKit
class FirstVC: UIViewController {
var key = 0
#IBAction func buttonPressed(_ sender: Any) {
let vc = SecondVC()
vc.action = {
print(self.key)
self.key += 1
print(self.key)
}
present(vc, animated: true, completion: nil)
}
}
class SecondVC: UIViewController {
var action: (()->())?
override func viewDidLoad() {
onDismiss()
}
func onDismiss() {
action?()
}
}
While presenting VC, add dismissing modal VC action in its completion handler, so that Viewcontroller will be presented after dismissal is completed
present(picker, animated: true, completion: { (action) in
//dismissal action
if let delKey = delegatedKey {
key = delKey
}
})