简体   繁体   中英

Display an alert using storyboard and custom UITabBarController

I am dealing with a problem using UITabBarController. I have a small project using storyboards (XCode 13, IOS 15 as base system). I created a TabBarController but I later discovered I could not manage it effectively programmatically. Reading various docs, I discovered I could use two scenes from my storyboard and creating the tabbar programmatically. So I did this in SceneDelegate.swift :

let queryViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "QueryViewController")    
let settingsViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "SettingsViewController")
let starredViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "StarredViewController")
starredViewControllerTab.tabBarItem.title = "Starred"
starredViewControllerTab.tabBarItem.image = UIImage(systemName: "star")
// TODO: Discover why first two views keep reading image I setup previously in storyboard

let tabBarController = UITabBarController()
tabBarController.viewControllers = [queryViewControllerTab, settingsViewControllerTab, starredViewControllerTab]
tabBarController.selectedViewController = settingsViewControllerTab

self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()

This works perfectly and I can easily put a condition whether userDefaults are not set, load directly the settings.

In my class SettingsViewController I want to add an action where, upon pressing the button, you get an alert:

@IBAction func saveButtonPressed(_ sender: UIButton) {
        //        keychain.set(tokenInput.text ?? "", forKey: keychainKey)
        let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), 
                style: .default, handler: { _ in
        NSLog("The \"OK\" alert occured.")
        }))
        tabBarController.present(alert, animated: true, completion: nil)
}

But this makes the app crashing with unrecognized selector sent to instance 0x7f82f9705c30'

I've tried to debug the problem, and I understood I can't make the alert in this way because the view is really the tabBar and not the my scene. But here I got stuck. I tried to implement the UITabBarControllerDelegate , in StarredViewController , but I can't get it working.

extension StarredViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        print("did select tab bar item!")
    }
}

I start thinking my main setup with SceneDelegate and AppDelegate is wrong. Most of previous tutorials or threads I've found seems to fail even to compile because using deprecated versions.

This is a way to present an alert from any presented View Controller.

Add some extensions:

import UIKit

extension UIViewController {
    var customVisibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.customVisibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.customVisibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.customVisibleViewController
        } else if self is UIAlertController {
            return nil
        } else {
            return self
        }
    }
}

extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.customVisibleViewController
    }
}

Now you can show your alert in this way:

let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), 
            style: .default, handler: { _ in
NSLog("The \"OK\" alert occured.")
}))
UIApplication.topMostViewController?.present(alert, animated: true, completion: nil)

This is the code to trigger an alert. With addAction, you can add possible answers.

do {
    try //some method call or something else
} catch {
    let alert = UIAlertController(title: "There was an error while saving!", message: "Please try again", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "I understand", style: .cancel, handler: nil))
}

you can find more information here: how to show an alert

I solved the problem. Actually, all my assumptions and the question were wrong.

TL;DR the storyboard is corrupted or got damaged when I removed the tab bar from it to make it programmatically.

Here the long version. Before entering in this trouble, I had a storyboard with two views and a tab bar controller. It was working perfectly. At one point, I decided I wanted to make a choice during the app starting and, in case of missing defaults, load immediately the settings view. I found that, to do this, I had to move my tab bar down to the scene delegate and remove it from storyboards. I did it, so storyboard was showing to views no linked, and I instantiated the tab bar from the scene delegate.

Weirdly, the tab bar being rendered was still showing some properties previously set on the storyboard, even if that component was deleted.

Then, you know the problem. My reasoning did not make any sense. The UITabBarController can't show any alert. Alerts can be presented on a UIViewController only. So, it was pointless to keep trying to make an alert out from a tab bar. This wrong understanding led me also to wrong research which reported various similar questions (probably misleading).

I finally made a counter test. Created a brand new project with storyboard. Created two views on the storyboard and defined a tab bar controller on the scene delegate. It worked as expected. I linked each view to a specific UIViewController . Created a button on the view, added the IBAction and it worked. Then, I created the alert in the IBAction and, this time, worked exactly. I ended with the same code, and the only different is that I did not create and removed a tab bar from the storyboard.

I knew that storyboard can get damaged and, probably, I did it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM