将@IBSegueAction 与 UINavigationController 一起使用

Using @IBSegueAction with UINavigationController

感谢 AD Progress 出色的研究和下面的回答,我终于弄清楚了 IBSegueActions 是怎么回事。

如果您将 rootViewController segue(或任何其他类型,大概)连接到其 UINavigationContoller 中的 IBSegueAction,但它在那里找不到它,它将查看响应者链上的下一个视图控制器,直到找到具有正确签名的 IBSegueAction 。如果找不到,它将在 rootViewController 上调用 init(with coder: NSCoder)。如果在某处记录了这一点……


备注

注意:这是一个说明问题的最小示例 - 如何在初始化包含在模态 UINavigationController 中的视图控制器时传递参数使用 IBSegueAction。我知道这不是您通常呈现详细视图的方式。

注释 2:我知道如何在 prepare(for segue: UIStoryboardSegue) 中使用 segues。我想知道如何在使用 IBSegueAction

初始化模态 UINavigationController 中包含的视图控制器时如何传递非可选参数

注释 3:我知道如何将 IBSegueAction 用于未包含在 UINavigationController 中的视图控制器(例如推动导航堆栈)


原题

我有以下设置…

UITableViewController 中的人员列表。点击 PersonCell 会在 UINavigationController

中显示 PersonViewController

为了避免在 PersonViewController 中出现可选的 Person,我正在尝试使用 IBSegueActions。

我的第一个想法是我需要一个用于 PeopleViewControllerPersonNavController 之间的转接,以及 PersonNavControllerPersonViewController 之间的转接,如下所示…

class PeopleViewController: UITableViewController {

    // Table view delegate methods here

    @IBSegueAction func showPerson(_ coder: NSCoder, sender: Any?) -> PersonNavController? {
        guard let cell = sender as? PersonCell else { return nil }
        return PersonNavController(coder: coder, person: cell.person)
    }
}

class PersonNavController: UINavigationController {

    private let person: Person

    required init?(coder: NSCoder, person: Person) {
        self.person = person
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @IBSegueAction func root(_ coder: NSCoder) -> PersonViewController? {
        return PersonViewController(coder: coder, person: person)
    }
}

class PersonViewController: UIViewController {

    @IBOutlet private weak var name: UILabel!

    private let person: Person

    required init?(coder: NSCoder, person: Person) {
        self.person = person
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        name.text = person.firstName
    }
}

事实证明,这里的问题是,即使您可以将一个 segue 操作连接到 UINavigationController 和它的 rootViewController 之间的 segue,它也不会被触发,因为,在上面的示例中,PersonNavController 中的 super.init(coder: coder) 调用 PersonViewController.init(coder: NSCoder)(未实现)。

关于如何让它工作有什么想法吗?

为了达到您想要的结果,我重新创建了一个简单的项目,我将 link 在我的 GitHub 上 link。

首先,您需要删除为导航控制器创建的转场并重新创建它。

下一个 select 以模态方式呈现并使用标识符命名您的 segue

接下来您需要在第一个 ViewController

中定义 IBSegueAction

ViewController.swift

import UIKit

class ViewController: UITableViewController {
    
    let persons = ["Robert", "Peter", "Dave"]
    var selectedPerson = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    @IBSegueAction
    private func showPerson(coder: NSCoder, sender: Any?, segueIdentifier: String?)
        -> PersonViewController? {
        return PersonViewController(coder: coder, personDetails: persons[selectedPerson])
    }
    
    
    //MARK:- TableView Methods
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return persons.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "personCell", for: indexPath)
        cell.textLabel?.text = persons[indexPath.row]
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedPerson = indexPath.row
        performSegue(withIdentifier: "ShowPerson", sender: self)
    }
}

然后在PersonViewController中添加init?(coder: NSCoder, personDetails: String)的实现如下。 Xcode 会大喊你需要执行 required init?(coder: NSCoder) 只需点击修复。

人ViewController.swift

import UIKit

class PersonViewController: UIViewController {
    //Your data passed in below as non optional constant
    let personDetails: String
    
    //The received data gets initialized below
    init?(coder: NSCoder, personDetails: String) {
      self.personDetails = personDetails
      super.init(coder: coder)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @IBOutlet weak var personLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        personLabel.text = personDetails
    }
}

下一步是 select 故事板中 ViewController 的顶部,以便您可以看到三个图标。

之后,你必须 select 从第二个 UINavigationController 出来的 Segue 到你的目的地 ViewController 这里 PersonViewController 然后从 segue 拖回第一个黄色ViewController 你想从下面的屏幕截图中发送信息的图标。 Xcode 将自动识别您在代码中创建的 IBSegueAction 的名称。

就是这样。

点击第一个视图控制器中的任何单元格后,您应该会得到这样的结果

这是示例项目GitHub

我还找到了更多关于 IBSegueAction 的信息here

我认为这不是最好的方法。

但是我不想创建

  1. 自定义导航Class

  2. 目标中的初始化程序ViewController


故事板


继续

// MARK: - Segue
@available(iOS 13.0, *)
extension DrawingViewController {

    @IBSegueAction private func prepareImagePickerSegue(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> UIViewController? {
        guard let navigationController = UINavigationController(coder: coder),
           let viewController = UIStoryboard(name: "ImagePicker", bundle: nil).instantiateViewController(withIdentifier: "ImagePickerViewController") as? ImagePickerViewController else { return nil }


        // Prepare the target viewController
        viewController.delegate = self


        // Set navigation
        navigationController.setViewControllers([viewController], animated: false)

        return navigationController
     }
}

我遇到了同样的问题,但成功了。不要在 Show Detail segue 上设置 segue 动作选择器,而是在 Navigation Controller 的 Relationship Segue 上设置它,如以下屏幕截图所示: