Пользовательский init для UIViewController в Swift с настройкой интерфейса в раскадровке
У меня возникла проблема с написанием пользовательского init для подкласса UIViewController, в основном я хочу передать зависимость через метод init для viewController, а не устанавливать свойство непосредственно как viewControllerB.property = value
Итак, я создал пользовательский init для моего диспетчера viewController и вызвал супер назначенный init
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
Интерфейс контроллера представления находится в раскадровке, я также делаю интерфейс для пользовательского класса моим контроллером. И Swift требует вызвать этот метод init, даже если вы ничего не делаете в этом методе. В противном случае компилятор будет жаловаться...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Проблема в том, что когда я пытаюсь вызвать свой пользовательский init с помощью MyViewController(meme: meme)
, он не запускает свойства в моем viewController вообще...
Я пытался отлаживать, я нашел в своем viewController, init(coder aDecoder: NSCoder)
сначала вызывается, затем мой пользовательский init получает вызов позже. Однако эти два метода init возвращают разные адреса памяти self
.
Я подозреваю что-то не так с init для моего viewController, и он всегда будет возвращать self
с помощью init?(coder aDecoder: NSCoder)
, который не имеет реализации.
Кто-нибудь знает, как правильно настроить пользовательский init для вашего контроля над представлением?
Примечание: мой интерфейс viewController настроен в раскадровке
вот мой код viewController:
class MemeDetailVC : UIViewController {
var meme : Meme!
@IBOutlet weak var editedImage: UIImageView!
// TODO: incorrect init
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
/// setup nav title
title = "Detail Meme"
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
editedImage = UIImageView(image: meme.editedImage)
}
}
Ответы
Ответ 1
Как было указано в одном из ответов выше, вы не можете использовать и пользовательский метод init, и раскадровку.
Но вы все еще можете использовать статический метод для создания экземпляра ViewController из раскадровки и для его дополнительной настройки.
Это будет выглядеть так:
class MemeDetailVC : UIViewController {
var meme : Meme!
static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("IdentifierOfYouViewController") as! MemeDetailVC
newViewController.meme = meme
return newViewController
}
}
Не забудьте указать IdentifierOfYouViewController в качестве идентификатора контроллера представления в вашей раскадровке.
Вам также может понадобиться изменить название раскадровки в приведенном выше коде.
Ответ 2
Вы не можете использовать пользовательский инициализатор при инициализации из раскадровки, используя init?(coder aDecoder: NSCoder)
, как Apple разработала раскадровку для инициализации контроллера. Однако есть способы отправить данные в UIViewController
.
В вашем контроллере имен есть detail
, поэтому я предполагаю, что вы попали туда с другого контроллера. В этом случае вы можете использовать метод prepareForSegue
для отправки данных в детали (это Swift 3):
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
if let controller = segue.destinationViewController as? MemeDetailVC {
controller.meme = "Meme"
}
}
}
Я использовал свойство типа String
вместо Meme
для целей тестирования. Кроме того, убедитесь, что вы передали правильный идентификатор segue ("identifier"
был просто заполнителем).
Ответ 3
Один из способов, которым я это сделал, - это инициализатор удобства.
class MemeDetailVC : UIViewController {
convenience init(meme: Meme) {
self.init()
self.meme = meme
}
}
Затем вы инициализируете свой MemeDetailVC с помощью let memeDetailVC = MemeDetailVC(theMeme)
Документация Apple на инициализаторы довольно хороша, но моим личным фаворитом является Ray Wenderlich: Инициализация в глубине серии, которая должна дать вам много объяснений/примеров по вашим различным параметрам init и "правильному" способу делать что-то.
EDIT. Хотя вы можете использовать инициализатор удобства в пользовательских контроллерах представлений, все правильно утверждают, что вы не можете использовать пользовательские инициализаторы при инициализации из раскадровки или в режиме раскадровки.
Если ваш интерфейс настроен в раскадровке, и вы создаете контроллер полностью программно, то инициализатор удобства, вероятно, самый простой способ сделать то, что вы пытаетесь сделать, поскольку вам не нужно иметь дело с требуется init с NSCoder (которого я до сих пор не понимаю).
Если вы получаете свой контроллер просмотра через раскадровку, тогда вам нужно будет следовать @Caleb Kleveter answer и затем направить контроллер вида в ваш желаемый подкласс установите свойство вручную.
Ответ 4
Как отметил @Caleb Kleveter, мы не можем использовать пользовательский инициализатор при инициализации из раскадровки.
Но мы можем решить эту проблему, используя метод фабрики/класса, который создает экземпляр объекта контроллера представления из Storyboard и возвращает объект контроллера представления.
Я думаю, что это довольно крутой способ.
Примечание. Это не точный ответ на вопрос, а обходной путь для решения проблемы.
Создайте метод класса в классе MemeDetailVC следующим образом:
// Considering your view controller resides in Main.storyboard and it identifier is set to "MemeDetailVC"
class func 'init'(meme: Meme) -> MemeDetailVC {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
vc.meme = meme
return vc
}
Использование:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
Ответ 5
Первоначально было несколько ответов, которые были проголосованы и уничтожены коровами, хотя они были в основном правильными. Ответ в том, что вы не можете.
При работе с определением раскадровки все экземпляры контроллера просмотра архивируются. Таким образом, для их запуска потребовалось использовать init?(coder...
. coder
- это информация о всех настройках/представлениях.
Таким образом, в этом случае нельзя также вызвать некоторую другую функцию init с помощью настраиваемого параметра. Он должен либо быть установлен как свойство при подготовке segue, либо вы можете вырезать segues и загружать экземпляры непосредственно из раскадровки и настраивать их (в основном шаблон factory с помощью раскадровки).
Во всех случаях вы используете требуемую функцию инициализации SDK и после этого передаете дополнительные параметры.
Ответ 6
UIViewController
соответствует протоколу NSCoding
, который определяется как:
public protocol NSCoding {
public func encode(with aCoder: NSCoder)
public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}
Итак, UIViewController
имеет два назначенных инициализатора init?(coder aDecoder: NSCoder)
и init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
.
Storyborad вызывает init?(coder aDecoder: NSCoder)
непосредственно в init UIViewController
и UIView
. Вам не место для передачи параметров.
Одно громоздкое обходное решение заключается в использовании временного кеша:
class TempCache{
static let sharedInstance = TempCache()
var meme: Meme?
}
TempCache.sharedInstance.meme = meme // call this before init your ViewController
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
self.meme = TempCache.sharedInstance.meme
}
Ответ 7
Swift 5
Вы можете написать собственный инициализатор только для свойств типа Optional
.
class MyFooClass: UIViewController {
var foo: Foo?
init(with foo: Foo) {
self.foo = foo
super.init(nibName: nil, bundle: nil)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.foo = nil
}
}
Ответ 8
//Контроллер вида находится в Main.storyboard и имеет установленный идентификатор
Класс B
class func customInit(carType:String) -> BViewController
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let objClassB = storyboard.instantiateViewController(withIdentifier: "BViewController") as? BViewController
print(carType)
return objClassB!
}
Класс А
let objB = customInit(carType:"Any String")
navigationController?.pushViewController(objB,animated: true)
Ответ 9
Swift 5 - Инициализация сохраненных свойств в подклассе UIViewController
class SomeClass: UIViewController {
var storedProperty: String
required init?(coder aDecoder: NSCoder) {
self.storedProperty = "Stored Prop"
super.init(coder: aDecoder);
}
}