self.dismiss resets parent view controller's screen data - swift

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.

Related

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()
}

Refreshing tableview data after dismiss() called on another tableview

Setup: table view controller has button (Add) that pops up another view controller with a form. I'm using Realm to store data, so no need to pass data back. However, when I dismiss() the view controller, and return to the table view controller, I cannot get tableView.reloadData() to work.
I have tried viewWillAppear() and viewDidAppear() but neither appear to be in the call stack.
Any ideas where I need to put this?
You need a delegate
let second = ///
second.delegate = self
When you dismiss in 2nd vc
delegate?.refresh()
Probably your second controller is being displayed modally. According to Apple Developer Documentation:
If a view controller is presented by a view controller inside of a popover, this method is not invoked on the presenting view controller after the presented controller is dismissed.
You can solve your problem with a delegate:
protocol ControllerBDelegate {
func willDismiss()
}
class ControllerA {
func open() {
let vc = ControllerB()
vc.delegate = self
self.present(vc, animated: true)
}
}
extension ControllerA : ControllerBDelegate {
func willDismiss() {
self.tableView.reloadData()
}
}
class ControllerB {
weak var delegate: ControllerBDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
delegate?.willDismiss()
}
}

Present Next ViewController to Dismiss current ViewController Using Swift

My Scenario, I have three ViewControllers, those ViewControllers First, Second, Third. Here, First ViewController UIButton click to present model second ViewController and then Second ViewController UIButton click to present model Third ViewController. I need to Implement the Second ViewController button click to dismiss current ViewController after presenting Third ViewController because once I close Third ViewController I need to show First ViewController. How to Achieve this?
NOTE: I am having NavigationController for Third ViewController also I am using Storyboard.
Here's my code:
#IBAction func ClickThirdVC(_ sender: Any) {
if let VC_Third = self.storyboard?.instantiateViewController(withIdentifier: "thirdviewcontroller") as? ThirdvViewController {
weak var pvc = self.presentingViewController
VC_B.modalTransitionStyle = .crossDissolve
let navController = UINavigationController(rootViewController: VC_B)
self.dismiss(animated: true, completion: {
pvc?.present(navController, animated: true, completion: nil)
})
}
}
You can try to dismiss self after presenting the third UIViewController, in the completion handler of the present function .
secondVC?.present(navController, animated: true) {
self.dismiss(animated: false, completion: nil) //dismiss self after presenting the third.
}

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
}
})

Dismiss Popover after touch

I've created a popover inside my MainViewController when some button its touched using the UIPopoverPresentationController and set like it's delegate like it's showed in the WWDC 2014, in the following way :
MainViewController.swift
class MainViewController : UIViewController, UIPopoverPresentationControllerDelegate {
#IBAction func showPopover(sender: AnyObject) {
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("PopOverViewController") as UIViewController
popoverContent.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = popoverContent.popoverPresentationController
popoverContent.preferredContentSize = CGSizeMake(250, 419)
popover!.delegate = self
popover!.sourceView = self.view
popover!.sourceRect = CGRectMake(180,85,0,0)
self.presentViewController(popoverContent, animated: true, completion: nil)
}
}
The popover have a View inside it and when the View it's clicked with a Tap Gesture Recognizer I show LastViewController using a modal segue, the modal segue is created through the Interface Builder, not in code using an action to present the another LastViewController
Once the LastViewController is dismissed and I'm back in the MainViewController the popover remains open.
Inside the PopOverController I only have the default code nothing more.
LastViewController.swift
#IBAction func dismissVIew(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
The above code is used to dismiss the LastViewController once the button inside is touched.
Storyboard
How can I dismiss the popover once the another LastViewController it's visible, or before the another LastViewController should be opened?
Thanks in advance
I have already answer same problem over here.
There scenario is different but solution is same
You have to write code for dismiss presented view controller on completion of current view controller.
Write below code on your dismissVIew method of LastViewController.swift
var tmpController :UIViewController! = self.presentingViewController;
self.dismissViewControllerAnimated(false, completion: {()->Void in
println("done");
tmpController.dismissViewControllerAnimated(false, completion: nil);
});
Download link
In your button action on the FinalViewController, have you tried:
#IBAction func dismissMe() {
//this should tell the popover to tell the main view controller to dismiss it.
self.presentingViewController!.presentingViewController!.dismissViewControllerAnimated(false, completion: nil)
}
here is how I would do it.
I usually use lazy initialization for the PopoverViewController and it's ContentViewController
lazy var popoverVC: UIPopoverController = {
let vc = UIPopoverController(contentViewController: self.contentVC)
vc.delegate = self
return vc
}()
lazy var contentVC: UIViewController = {
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as UIViewController
vc.modalInPopover = true
return vc
}()
inside my contentViewController I hold a reference to the UIPopoverController.
var popoverVC: UIPopoverController!
then when I show the popover i just assign the popoverController to the contentViewController
#IBAction func showPopover(sender: UIButton) {
contentVC.popoverVC = self.popoverVC
let viewCenterRect = self.view.convertRect(self.view.bounds, toView: self.view)
popoverVC.presentPopoverFromRect(CGRectMake(CGRectGetMidX(viewCenterRect), CGRectGetMidY(viewCenterRect), 1, 1), inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.allZeros, animated: true)
}
finally I dismiss the Popover programmatically inside an #IBAction
#IBAction func dismissPopover(sender: AnyObject) {
popoverVC.dismissPopoverAnimated(true)
}
Inside the viewcontroller you can override viewWillAppear()
Inside of this block dimiss it
override public func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
_viewToDismiss.removeFromSuperView()
}
But the above code assumes you have a reference to the PopOver object, which I don't think is good practice based on how you described the problem.
Rather, why not have the viewcontroller that created the PopOver be responsible for destroying it. Put this in the class that listens for the button touch (which I also assumes creates the PopOver as well)
- (void)viewWillDisappear:(BOOL)animated
{
_popOver.removeFromSuperView()
}
The popover have a View inside it and when the View it's clicked with a Tap Gesture Recognizer I show another ViewController using a modal segue.
As far as I understand from what you say, you should be able to call dismissViewControllerAnimated(_:completion:) from the action associated to your tap gesture recogniser. This will dismiss the popover you presented calling:
self.presentViewController(popoverContent, animated: true, completion: nil)
You can call this method on the popover view controller itself, depending on what is more convenient for you:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
Based on #Jageen answer
Swift 4.2
let tmpController :UIViewController! = self.presentingViewController;
self.dismiss(animated: false, completion: {()->Void in
print("done");
tmpController.dismiss(animated: false, completion: nil);
});