Использование @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 здесь
У меня была такая же проблема, но мне удалось заставить ее работать. Вместо того, чтобы устанавливать селектор действий перехода на переходе Показать подробности, установите его на переходе отношений контроллера навигации, как показано на следующем снимке экрана:
Думаю, это не лучший способ.
Но я не хочу создавать
Пользовательский класс навигации
Инициализатор в целевом 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
}
}