在 Swift 中点击警报控制器后,应用程序会忘记当前的视图控制器和表视图

问题描述 投票:0回答:1

我遇到了这样的问题:在使用 Action 方法启动 AlertController 后,我的应用程序似乎忘记了当前的视图控制器及其属性(即 tableView)。

例如,在通过按钮操作呈现警报后,当函数引用屏幕上的表格视图(它说为零)时,点击按钮会导致崩溃。

所讨论的 VC 嵌入在导航控制器中,并从作为 UITabBarController 一部分的 VC 以模态方式启动

所以堆栈应该类似于 UITabBarController -> UINavigationController -> ViewControllerOnScreen -> AlertController

我解决了之前的一个问题,即使用以下方法点击 UIGesture 识别器后 VC 从堆栈中消失(尽管仍在屏幕上_):

func topVC() -> UIViewController {
        var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
        while (topController.presentedViewController != nil) {
            topController =  topController.presentedViewController!
        }
        return topController
    }

在这种情况下,我只需要启动警报即可。之前方法中的 topVC 是屏幕上的 ViewController,它成功启动了警报。

但是,现在屏幕上的tableView为零。 tableView 是 VC 的一个属性,它被声明为 IBOutlet

@IBOutlet var tableView : UITableView!

但是,在这种情况下,当我调用上面的topVC方法时,topVC是navigationController而不是屏幕上的VC,因此它没有tableView属性。

有没有办法从navigationController引用我想要的VC(屏幕上的VC)?

非常感谢任何建议。

编辑:

根据 Karmen 的建议,提供的链接中有很多答案。

我将它们拼凑在一起,如下所示,但仍然没有获得包含属性的实际 VC。我刚刚得到 UINavigationController:

extension UIWindow {
        func visibleController() -> UIViewController? {
            var top = self.rootViewController
            while true {
                if let presented = top?.presentedViewController {
                    top = presented
                } else if let nav = top as? UINavigationController {
                    top = nav.visibleViewController
                } else if let tab = top as? UITabBarController {
                    top = tab.selectedViewController
                } else {
                    break
                }
            }
            return top
        }
    }

由于我有一个alertController,我怀疑按键窗口存在一些问题。链接中的 Objective-C 答案解决了 AlertController 的 keyWindow 问题,但我正在努力翻译成 Swift:

-(UIWindow *) returnWindowWithWindowLevelNormal
{
    NSArray *windows = [UIApplication sharedApplication].windows;
    for(UIWindow *topWindow in windows)
    {
        if (topWindow.windowLevel == UIWindowLevelNormal)
            return topWindow;
    }
    return [UIApplication sharedApplication].keyWindow;
}

-(UIViewController *) getTopMostController
{
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal)
    {
        topWindow = [self returnWindowWithWindowLevelNormal];
    }

    UIViewController *topController = topWindow.rootViewController;
    if(topController == nil)
    {
        topWindow = [UIApplication sharedApplication].delegate.window;
        if (topWindow.windowLevel != UIWindowLevelNormal)
        {
            topWindow = [self returnWindowWithWindowLevelNormal];
        }
        topController = topWindow.rootViewController;
    }

    while(topController.presentedViewController)
    {
        topController = topController.presentedViewController;
    }

    if([topController isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *nav = (UINavigationController*)topController;
        topController = [nav.viewControllers lastObject];

        while(topController.presentedViewController)
        {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}
ios swift uiviewcontroller uinavigationcontroller uigesturerecognizer
1个回答
0
投票

这并不能回答你关于“寻找顶级风险投资家”的问题 - 因为我真的认为这不是你想要使用的方法。

相反,让我们看看向单元格类添加一个

Closure
,以便我们的控制器可以在点击单元格图像时“执行某些操作”。

首先,我们向单元类添加一个属性,如下所示:

class MyTestCell: UITableViewCell {

    var imgTapClosure: ((UITableViewCell) -> ())?

然后,同样在单元类中,在我们处理点击手势的函数中:

@objc func handleTap(_ g: UITapGestureRecognizer) {
    // call the Closure to inform the controller of the tap
    imgTapClosure?(self)
}

在控制器的

cellForRowAt
函数中,我们可以“设置”该闭包:

    // set the closure
    cell.imgTapClosure = { [weak self] theCell in
        guard let self = self,
              let idxPath = self.tableView.indexPath(for: theCell)
        else { return }
        
        // now we know which cell got the tap on the image, and
        //  we can "do something" with that information
        self.doSomething(indexPath: idxPath)
    }

现在,在同一个控制器中,我们可以编写一个

doSomething()
函数来呈现一个
UIAlertController

如果具有表视图的控制器是“根”控制器,或者它位于导航堆栈中,或者如果它位于选项卡控制器中,或者如果它位于选项卡控制器的导航堆栈中,则这将起作用。

这是一个完整的例子...


简单的数据结构 - “lastAction”将由警报控制器设置

struct MyTestObject {
    var title: String = ""
    var imgName: String = ""
    var lastAction: String = ""
}

Cell 类 - 带闭包

class MyTestCell: UITableViewCell {
    
    var imgTapClosure: ((UITableViewCell) -> ())?
    
    var titleLabel: UILabel = UILabel()
    var lastActionLabel: UILabel = UILabel()
    var theImageView: UIImageView = UIImageView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier);
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        // for this example, we're going to use SF Symbol images
        //  which do some weird things with framing
        // so let's embed it in a view
        let cView = UIView()
        cView.addSubview(theImageView)
        
        contentView.addSubview(titleLabel)
        contentView.addSubview(lastActionLabel)
        contentView.addSubview(cView)

        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        lastActionLabel.translatesAutoresizingMaskIntoConstraints = false
        cView.translatesAutoresizingMaskIntoConstraints = false
        theImageView.translatesAutoresizingMaskIntoConstraints = false
        
        let g = contentView.layoutMarginsGuide
        
        // avoid auto-layout complaints
        let b = cView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0)
        b.priority = .required - 1
        NSLayoutConstraint.activate([
            
            titleLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            
            lastActionLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            lastActionLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),

            cView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            cView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            b,
            
            cView.widthAnchor.constraint(equalToConstant: 60.0),
            cView.heightAnchor.constraint(equalToConstant: 60.0),

            theImageView.widthAnchor.constraint(equalToConstant: 60.0),
            theImageView.heightAnchor.constraint(equalToConstant: 60.0),
            theImageView.centerXAnchor.constraint(equalTo: cView.centerXAnchor, constant: 0.0),
            theImageView.centerYAnchor.constraint(equalTo: cView.centerYAnchor, constant: 0.0),

        ])
        
        // element properties
        theImageView.contentMode = .scaleAspectFit
        theImageView.tintColor = .systemRed

        // add tap gesture
        let t = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        theImageView.addGestureRecognizer(t)
        theImageView.isUserInteractionEnabled = true
        
        // so we can see the framing
        titleLabel.backgroundColor = .yellow
        lastActionLabel.backgroundColor = .init(red: 0.9, green: 0.95, blue: 1.0, alpha: 1.0)
        theImageView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        cView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
    }
    
    @objc func handleTap(_ g: UITapGestureRecognizer) {
        // call the Closure to inform the controller of the tap
        imgTapClosure?(self)
    }
    
}

视图控制器类 - 将表视图作为子视图...当点击单元格中的 imageView 时,我们将呈现一个带有几个选项的警报控制器。

class MyTestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var theData: [MyTestObject] = []

    // if you have set this up in Storyboard
    //@IBOutlet var tableView: UITableView!
    
    // for this example, we'll add the table view via code
    let tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        self.title = "Activities"
        
        // create some sample data, using a bunch of
        //  SF Symbol names from the "Fitness" category
        let imgNames: [String] = [
            "figure.american.football",
            "figure.archery",
            "figure.australian.football",
            "figure.badminton",
            "figure.barre",
            "figure.baseball",
            "figure.basketball",
            "figure.bowling",
            "figure.boxing",
            "figure.climbing",
            "figure.cooldown",
            "figure.core.training",
            "figure.cricket",
            "figure.skiing.crosscountry",
            "figure.cross.training",
            "figure.curling",
            "figure.dance",
            "figure.disc.sports",
            "figure.skiing.downhill",
            "figure.elliptical",
            "figure.equestrian.sports",
            "figure.fencing",
        ]
        for (i, str) in imgNames.enumerated() {
            let obj = MyTestObject(title: "Row \(i)", imgName: str)
            theData.append(obj)
        }

        // if you're using an @IBOutlet table view, the following is not needed
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
        
        tableView.register(MyTestCell.self, forCellReuseIdentifier: "c")
        tableView.dataSource = self
        tableView.delegate = self
        
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return theData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! MyTestCell
        let d = theData[indexPath.row]
        cell.titleLabel.text = d.title
        cell.lastActionLabel.text = "Last Action: " + d.lastAction
        if let img = UIImage(systemName: d.imgName) {
            cell.theImageView.image = img
        }
        
        // set the closure
        cell.imgTapClosure = { [weak self] theCell in
            guard let self = self,
                  let idxPath = self.tableView.indexPath(for: theCell)
            else { return }
            
            // now we know which cell got the tap on the image, and
            //  we can "do something" with that information
            self.doSomething(indexPath: idxPath)
        }
        
        return cell
    }
    
    func doSomething(indexPath: IndexPath) {
        
        // use preferredStyle: .actionSheet or .alert as desired
        //let alertController = UIAlertController(title: "My Title", message: "What do you want to do?", preferredStyle: .actionSheet)
        let alertController = UIAlertController(title: "My Title", message: "What do you want to do?", preferredStyle: .alert)
        
        alertController.addAction(UIAlertAction(title: "Save the image", style: .default, handler: { (alertAction: UIAlertAction) in
            // add code to Save the image
            //  for this example, let's just update the action and reload the row
            self.theData[indexPath.row].lastAction = "Save!"
            self.tableView.reloadRows(at: [indexPath], with: .automatic)
        }))
        
        alertController.addAction(UIAlertAction(title: "Share the image", style: .default, handler: { (alertAction: UIAlertAction) in
            // add code to Share the image
            //  for this example, let's just update the action and reload the row
            self.theData[indexPath.row].lastAction = "Share!"
            self.tableView.reloadRows(at: [indexPath], with: .automatic)
        }))
        
        // only if we are in a navigationController stack,
        //  add a "Show" action
        if let navVC = self.navigationController {
            alertController.addAction(UIAlertAction(title: "Show the image", style: .default, handler: { (alertAction: UIAlertAction) in
                // for this example, update the action and reload the row
                self.theData[indexPath.row].lastAction = "Show!"
                self.tableView.reloadRows(at: [indexPath], with: .none)
                // then push to a new VC to show the image
                let vc = ShowImageVC()
                vc.theImageName = self.theData[indexPath.row].imgName
                navVC.pushViewController(vc, animated: true)
            }))
        }
        
        alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (alertAction: UIAlertAction) in
            // user selected Cancel, so
            //  for this example, let's just update the action and reload the row
            self.theData[indexPath.row].lastAction = "Cancelled"
            self.tableView.reloadRows(at: [indexPath], with: .automatic)
        }))
        
        present(alertController, animated: true)
    }
}

“显示图像”视图控制器类 - 如果在导航控制器中运行,我们将有一个选项“显示”图像,这将实例化并推送此控制器。

class ShowImageVC: UIViewController {
    
    var theImageName: String = ""
    
    let imgView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        self.title = "Show Image"
        
        imgView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(imgView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            imgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
        ])
        
        imgView.contentMode = .scaleAspectFit
        imgView.tintColor = .systemRed
        if let img = UIImage(systemName: theImageName) {
            imgView.image = img
        }
    }
    
}

一些屏幕截图,显示上面的代码在导航堆栈中的控制器中运行,在选项卡视图控制器中......

首次加载时:

enter image description here

点击第 2 行单元格中的图像:

enter image description here

选择“保存” - 我们更新数据并重新加载行(实际代码将保存图像):

enter image description here

点击单元格第 3 行中的图像,选择“共享” - 我们更新数据并重新加载该行(实际代码将共享图像):

enter image description here

点击单元格第 4 行中的图像,选择“取消” - 我们更新数据并重新加载该行:

enter image description here

点击第 5 行单元格中的图像,选择“显示” - 我们更新数据并重新加载该行,然后推送到新的 VC:

enter image description here

演出回来:

enter image description here

如果需要,我们可以将警报控制器设置为

preferredStyle: .actionSheet
而不是
.alert

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.