swift Dismiss ViewController from a helper / another class - ios

I have a problem when i try to dismiss a view from a function of a helperClass instanced in the viewer class
public func set(playerController: AVPlayerViewController){
playerController?.dismiss(animated: true, completion: nil)
whose view is not in the window hierarchy!
how can I pass correctly the controller so the helper class can dismiss it?
Viewerclass:
helper.add(player: player)
helper.set(playerController: playerController)

You should be able to just do dismiss(animated: true, completion: nil) from the presented view controller, as Apple libraries handle the dismissal both from presenter and the presented view controllers. No need to pass a reference

You also can give a callback to dismiss
something like this:
helper.add(player: player) {
self.dismiss(animated: true, completion: nil)
}
Player:
public func set(playerController: AVPlayerViewController, completion: (Void)->Void){
completion()
}

Try like this from your helper class:-
AppDelegate.sharedInstance().window?.topMostController()?.dismiss(animated: true, completion: nil)
And add this function in your AppDelegate file :-
class func sharedInstance() -> AppDelegate{
return UIApplication.shared.delegate as! AppDelegate
}

Related

How do I prevent a navigationController from returning to root when dismissing MFMailComposeViewController

When I dismiss an instance of MFMailComposeViewController or MFMessageComposeViewController that is presented modally from the third viewController in a navigation stack, the navigation stack is reset, and the root VC is reloaded. How can I prevent this behavior and remain on the original presenting viewController (third VC in the stack)? I get the same behavior whether I call dismiss from the presenting VC, the presented VC, or the navigationController.
This has been asked before, but I have not seen a solution.
App Structure looks like this:
TabBarController
Tab 1 - TripsNavController
-> Trips IntroductionVC (root VC) segue to:
-> TripsTableViewController segue to:
-> TripEditorContainerVC
- TripEditorVC (child of ContainerVC)
- HelpVC (child of ContainerVC)
Tab 2...
Tab 3...
Tab 4...
In the TripEditorVC I present the MFMailComposeViewController. The functions below are declared in an extension to UIViewController that adopts the MFMailComposeViewControllerDelegate protocol
func shareWithEmail(message: NSAttributedString) {
guard MFMailComposeViewController.canSendMail() else {
showServiceError(message: "Email Services are not available")
return
}
let composeVC = MFMailComposeViewController()
composeVC.setSubject("My Trip Plan")
composeVC.setMessageBody(getHTMLforAttributedString(attrStr: message), isHTML: true)
composeVC.mailComposeDelegate = self
present(composeVC, animated: true, completion: nil)
}
Then in the delegate method I dismiss the MFMailComposeVC:
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("Mail sent")
case .saved:
print("Mail saved")
case .cancelled:
print("Mail cancelled")
case .failed:
print("Send mail failed")
}
if error != nil {
showServiceError(message: "Error: \(error!.localizedDescription)")
}
dismiss(animated: true, completion: nil)
}
I have tried the following to present and dismiss and get the same behavior, i.e.: the TripsNavController clears the nav stack and reloads the TripsIntroductionVC as its root VC:
self.present(composeVC, animated: true, completion: nil)
self.parent?.present(composeVC, animated: true, completion: nil)
self.parent?.navigationController?.present(composeVC, animated: true, completion: nil)
self.navigationController?.present(composeVC, animated: true, completion: nil)
You can also check with presentingViewController?.dismiss method to get the solution.
I have tried with following navigation stack.
I am able to send email successfully from Container VC's Send Email button using your code only.
Can you please check and verify navigation flow?
Please let me know if you still face any issue.
dismiss(animated: true, completion: nil)
to
self.dismiss(animated: true, completion: nil)
Try this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerId") as! SecondViewController
self.presentViewController(secondViewController, animated: true, completion: nil)
or
https://stackoverflow.com/a/37740006/8196100
Just Use Unwind Segue its Pretty simple and perfect in your case
I have used many times..
just look at this Unwind segue's example
. If u still don't able to find answer or not able to understand the Unwind segue then just reply to me.
Hope this will solve your problem.
I found the problem with my navigation stack today. I built a simple
project that duplicated the tabBarController/NavigationControler architecture of my problem project, component by component, until dismissing a MFMailComposeViewController caused my navigation stack to reset as described in my original post.
That immediately pointed to the source of the bug. In my subclassed UINavigationCotroller I was instantiating the root viewController in code so that I could skip an introductory view if the user had set a switch in the apps settings. In order to pick up changes in that switch setting I was calling my instantiation code in viewDidAppear of the navigationController. That worked fine EXCEPT when dismissing the mailComposeVC. The fix was to add a guard statement in viewDidAppear to return if the navControllers viewController collection was not empty, and send and respond to an NSNotification when the switch was changed.
class TopNavigationController: UINavigationController {
var sectionType: SectionType?
var defaults = UserDefaults.standard
var showIntroFlag: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// Handle initial load of the tab bar controller where we are not sent a sectionType
if sectionType == nil {
sectionType = .groups
}
setShowIntroFlag()
NotificationCenter.default.addObserver(self, selector: #selector(resetControllers), name: NSNotification.Name(rawValue: "kUserDidChangeShowIntros"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
guard self.viewControllers.isEmpty else {
return
}
loadControllers()
}
func setShowIntroFlag() {
showIntroFlag = true
// Check NSUserDefaults to see if we should hide the Intro Views for all sections
if defaults.bool(forKey: "SHOW_SECTION_INTROS") == false {
showIntroFlag = false
}
}
func loadControllers() {
if showIntroFlag == true {
showIntro()
} else {
skipIntro()
}
}
func resetControllers() {
setShowIntroFlag()
loadControllers()
}

Dismiss pushed view controller

So I have a view controller which I display as follows:
func showProfileForTrainer(trainer: Trainers) {
let viewPTProfileVC = ViewPTProfileVC()
viewPTProfileVC.trainer = trainer
self.navigationController?.pushViewController(viewPTProfileVC, animated: true)
}
But when trying to dismiss the view, I cannot get it to work. It has a back button in a navigation bar which functions fine, but when trying to dismiss the view via a button for example, it does nothing. I have currently tried:
func handleMessageTrainer() {
dismiss(animated: true) {
print(1)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
}
self.dismiss(animated: true) {
print(2)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
}
navigationController?.dismiss(animated: true, completion: {
print(3)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
})
self.navigationController?.dismiss(animated: true, completion: {
print(4)
self.tabBarVC?.showChatLogForTrainer(trainer: self.trainer!)
})
print(5)
}
As you can see I have tried varying ways and none work, and the console just outputs 5.
Frustratingly, elsewhere in my app I presented a view in the same way as shown at the beginning and it dismissed fine using dismiss(animated: true) { ... }
Any idea why it won't work here?
You must pop the view controller from the corresponding navigation controller:
self.navigationController?.popViewController(animated: true)
If you are using pushviewcontroller method then to dismiss you have to use popviewcontroller method.
Try this:
self.navigationController?.popViewController(animated: true)

Using callbacks to reproduce UIAlertViewController

I want to reproduce the functionality of the UIAlertController as the class isn't very customisable. So here is what I did:
Create a DeleteDeckViewController that only has two buttons Yes, and No, with a callback method
class DeleteDeckViewController: UIViewController {
var deleteDeck : ((deleteDeck : Bool) -> ())?
#IBAction func yesButton(sender: AnyObject) {
deleteDeck!(deleteDeck: true)
}
#IBAction func noButton(sender: AnyObject) {
deleteDeck!(deleteDeck: false)
}
}
In the main view controller when the delete button is pressed it creates a popup DeleteDeck
func handleDelete(detailDeckView: DetailDeckView) {
let deleteDeckViewController = DeleteDeckViewController(nibName: "DeleteDeckViewController", bundle: nil)
let popup = PopupDialog(viewController: deleteDeckViewController, transitionStyle: .BounceDown, buttonAlignment: .Horizontal, gestureDismissal: true)
presentViewController(popup, animated: true, completion: nil)
deleteDeckViewController.deleteDeck(true) { () -> Void in
print("Deck deleted")
}
}
What I want to do is to use a callback to tell my main view controller whether the user pressed yes or no and then do some actions depending on yes or no. Although, I don't know the correct way to do it. I've read a few articles on callbacks, but I don't know how to apply it in this situation. I've tried using delegates it's just that I would need to pass the DetailDeckView object around without it being used so that's why I thought callback would be a better choice.
Would someone be able to point me in the right direction?
Within DeleteDeckViewController you have declared the deleteDeck variable as a function. Therefore, when you use an instance of this class, you need to set it as a variable. Therefore you code would look more like that below (untested):
func handleDelete(detailDeckView: DetailDeckView) {
let deleteDeckViewController = DeleteDeckViewController(nibName: "DeleteDeckViewController", bundle: nil)
deleteDeckViewController.deleteDeck = myDeleteHandler
let popup = PopupDialog(viewController: deleteDeckViewController, transitionStyle: .BounceDown, buttonAlignment: .Horizontal, gestureDismissal: true)
presentViewController(popup, animated: true, completion: nil)
}
func myDeleteHandler(shouldDelete: Bool) {
if shouldDelete {
print("Delete deck")
}
}
Also, in your code, rather than using deleteDeck! which will fail if deleteDeck is nil, it is better practice to use code like:
if let deleteDeck = self.deleteDeck {
deleteDeck(true)
}
This will create a local non-Optional variable called deleteDeck, that can be assured to not contain nil.
You could build this code into DeleteDeckViewController to streamline further:
class DeleteDeckViewController: ....
func showDialog(vc: UIViewController, completion: ((deleteDeck : Bool) -> Void)?) {
self.deleteDeck = completion
let popup = PopupDialog(viewController: vc, transitionStyle: .BounceDown, buttonAlignment: .Horizontal, gestureDismissal: true)
presentViewController(popup, animated: true, completion: nil)
}

single function to dismiss all open view controllers

I have an app which is a single view application. I have a navigation controller linked up to all child controllers from the root view controller.
In each child controller, I have a logout button. I'm wondering if I can have a single function I can call which will dismiss all the controllers which have been open along along the way, no matter which controller was currently open when the user presses logout?
My basic start:
func tryLogout(){
self.dismissViewControllerAnimated(true, completion: nil)
let navigationController = UINavigationController(rootViewController: UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginViewController") )
self.presentViewController(navigationController, animated: true, completion: nil)
}
I am looking for the most memory efficient way of carrying out this task. I will put my logout function in a separate utils file, but then I can't use self. And I still have the issue of knowing which controllers to dismiss dynamically.
Update
Pop to root view controller has been suggested. So my attempt is something like:
func tryLogout(ViewController : UIViewController){
print("do something")
dispatch_async(dispatch_get_main_queue(), {
ViewController.navigationController?.popToRootViewControllerAnimated(true)
return
})
}
Would this be the best way to achieve what I'm after?
You can call :
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)
Should dismiss all view controllers above the root view controller.
Updated answer for Swift 4 and swift 5
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil)
and when you use navigationController
self.navigationController?.popToRootViewController(animated: true)
Works for Swift 4 and Swift 5
To dismiss any unwanted residue Modal ViewControllers, I used this and worked well without retaining any navigation stack references.
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: false, completion: nil)
self.view.window! did crash for my case possibly because its a Modal screen and had lost the reference to the window.
Swift3
navigationController?.popToRootViewControllerAnimated(true)
Take a look at how unwind segues work. Its super simple, and lets you dismiss/pop to a certain viewcontroller in the heirarchy, even if it consists of a complex navigation (nested pushed and or presented view controllers), without much code.
Here's a very good answer (by smilebot) that shows how to use unwind segues to solve your problem
https://stackoverflow.com/a/27463286/503527
To dismiss all modal Views.
Swift 5
view.window?.rootViewController?.dismiss(animated: true, completion: nil)
If you have a customed UITabbarController, then try dismiss top viewController in UITabbarController by:
class MainTabBarController: UITabBarController {
func aFuncLikeLogout() {
self.presentedViewController?.dismiss(animated: false, completion: nil)
//......
}
}
If you have access to Navigation Controller, you can try something like this. Other solutions didn't work for me.
func popAndDismissAllControllers(animated: Bool) {
var presentedController = navigationController?.presentedViewController
while presentedController != nil {
presentedController?.dismiss(animated: animated)
presentedController = presentedController?.presentedViewController
}
navigationController?.popToRootViewController(animated: animated)
}
works for Swift 3.0 +
self.view.window!.rootViewController?.dismiss(animated: true, completion: nil)
I have figured out a generic function to dismiss all presented controllers using the completion block.
extension UIWindow {
static func keyWindow() -> UIWindow? {
UIApplication.shared.windows.filter({ $0.isKeyWindow }).first
}
}
func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
var rootVC = rootViewController
if rootVC == nil {
rootVC = UIWindow.keyWindow()?.rootViewController
}
var presented = rootVC?.presentedViewController
if rootVC?.presentedViewController == nil {
if let isTab = rootVC?.isKind(of: UITabBarController.self), let isNav = rootVC?.isKind(of: UINavigationController.self) {
if !isTab && !isNav {
return rootVC
}
presented = rootVC
} else {
return rootVC
}
}
if let presented = presented {
if presented.isKind(of: UINavigationController.self) {
if let navigationController = presented as? UINavigationController {
return navigationController.viewControllers.last!
}
}
if presented.isKind(of: UITabBarController.self) {
if let tabBarController = presented as? UITabBarController {
if let navigationController = tabBarController.selectedViewController! as? UINavigationController {
return navigationController.viewControllers.last!
} else {
return tabBarController.selectedViewController!
}
}
}
return getVisibleViewController(presented)
}
return nil
}
func dismissedAllAlert(completion: (() -> Void)? = nil) {
if let alert = UIViewController.getVisibleViewController(nil) {
// If you want to dismiss a specific kind of presented controller then
// comment upper line and uncomment below one
// if let alert = UIViewController.getVisibleViewController(nil) as? UIAlertController {
alert.dismiss(animated: true) {
self.dismissedAllAlert(completion: completion)
}
} else {
completion?()
}
}
Note: You call anywhere in code at any class
Use:-
dismissedAllAlert() // For dismiss all presented controller
dismissedAllAlert { // For dismiss all presented controller with completion block
// your code
}
Swift
Used this to jump directly on your ROOT Navigation controller.
self.navigationController?.popToRootViewController(animated: true)

Back to previous ViewController from class func

I try to close and go back to previous viewController using:
class func closeViewController()
{
presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
But this gives me a compilation error.
if i remove the "class" identifyer it works, but i need this function to be class function
Just because you receive a callback from another class doesn't mean that you need your closeViewController method to be a class method. It's likely that it should not be a class method.
presentingViewController is an instance method of UIViewController. There is an implied "self" at the beginning:
self.presentingViewController?.dismissViewControllerAnimated(
true, completion: nil)
However, a class method is performed on the class, not an instance, so self is the class, not an instance.
If you really do need it to be a class method for some reason, you will need to pass in either the current view controller or the presenting view controller as a parameter to the method.
class func closeViewController(theModal: UIViewController)
{
theModal.dismissViewControllerAnimated(
true, completion: nil)
}
try this code :
self.dismissViewControllerAnimated(true, completion: nil)

Resources