Использование @IBSegueAction с UINavigationController

Благодаря отличному исследованию AD Progress и ответу ниже, я наконец понял, что происходит сIBSegueActionс.

Если вы подключите rootViewController segue (или любого другого, предположительно) к IBSegueAction в его UINavigationContoller, но он не находит его там, он будет смотреть на следующий контроллер представления в цепочке респондента, пока не найдет IBSegueActionс правильной подписью. Если он не найдет, он позвонитinit(with coder: NSCoder) на rootViewController. Если бы только это было где-то задокументировано...


Заметки

Примечание. Это минимальный пример, иллюстрирующий проблему - как передать параметры при инициализации контроллера представления, содержащегося в модальном окне.UINavigationController используя IBSegueAction. Я знаю, что это не то, как вы обычно представляете подробное представление.

Примечание 2. Я знаю, как использовать переходы сprepare(for segue: UIStoryboardSegue). Что я хочу знать, как передать необязательные параметры при инициализации контроллера представления, содержащегося в модальномUINavigationController используя IBSegueAction

Примечание 3: я знаю, как использоватьIBSegueAction к контроллеру представления, не содержащемуся в UINavigationController (например, нажатие на стек навигации)


Исходный вопрос

У меня следующая установка…

Список людей в UITableViewController. Нажав наPersonCell представляет PersonViewController внутри UINavigationController

Чтобы избежать необязательного Person в PersonViewController, Я пытаюсь использовать IBSegueActionс.

Моя первая мысль заключалась в том, что мне нужен один для перехода между PeopleViewController и PersonNavController, и переход между PersonNavController и PersonViewController следующее…

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

Проблема здесь, оказывается, в том, что даже если вы можете подключить действие перехода к переходу между UINavigationController и это rootViewController, он не запускается, поскольку в приведенном выше примере super.init(coder: coder) в PersonNavController звонки PersonViewController.init(coder: NSCoder) (что не реализовано).

Есть мысли о том, как я могу заставить это работать?

3 ответа

Решение

Чтобы добиться желаемого результата, я воссоздал простой проект, который я размещу на своем GitHub.

Для начала вам нужно избавиться от перехода, который вы создали для вашего контроллера навигации, и воссоздать его.

Затем выберите Present Modally и назовите свой сегмент идентификатором.

Затем вам нужно определить IBSegueAction в вашем первом ViewController.

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

Затем добавьте реализацию init?(coder: NSCoder, personDetails: String)в PersonViewController следующим образом. Xcode будет кричать, что вам нужно реализоватьrequired init?(coder: NSCoder) Просто нажмите исправить.

PersonViewController.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
    }
}

Следующим шагом является выбор верхней части ViewController в раскадровке, чтобы вы могли видеть три значка.

После этого вам нужно выбрать переход, который идет от второго UINavigationController к целевому ViewController здесь PersonViewController, и перетащить назад от перехода к первому желтому значку ViewController, с которого вы хотите отправить информацию, как на скриншоте ниже. Xcode автоматически распознает имя IBSegueAction, которое вы создали в коде.

Вот и все.

Вы должны получить такой результат, когда коснетесь любой из ячеек в первом контроллере просмотра.

Вот пример проекта GitHub

Я также нашел дополнительную информацию о IBSegueAction здесь

У меня была такая же проблема, но мне удалось заставить ее работать. Вместо того, чтобы устанавливать селектор действий перехода на переходе Показать подробности, установите его на переходе отношений контроллера навигации, как показано на следующем снимке экрана:

Думаю, это не лучший способ.

Но я не хочу создавать

  1. Пользовательский класс навигации

  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
     }
}
Другие вопросы по тегам