У меня проблема с написанием пользовательского init для подкласса UIViewController, в основном я хочу передать зависимость через метод init для viewController, а не устанавливать свойство напрямую, например viewControllerB.property = value
Итак, я сделал собственный init для моего viewController и вызвал супер назначенный init
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
Интерфейс контроллера представления находится в раскадровке, я также сделал интерфейс для настраиваемого класса моим контроллером представления. Swift требует вызова этого метода инициализации, даже если вы ничего не делаете в этом методе. Иначе компилятор пожалуется ...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Проблема в том, что когда я пытаюсь вызвать свой собственный init, MyViewController(meme: meme)
он вообще не запускает свойства в моем viewController ...
Я пытался отладить, я обнаружил в своем viewController, init(coder aDecoder: NSCoder)
сначала вызывается , а затем мой собственный init вызывается позже. Однако эти два метода инициализации возвращают разные self
адреса памяти.
Я заподозрив что - то не так с моей инициализации для ViewController, и он всегда будет возвращаться self
с init?(coder aDecoder: NSCoder)
, который не имеет реализации.
Кто-нибудь знает, как правильно сделать собственный init для вашего viewController? Примечание: мой интерфейс 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)
}
}
Ответы:
Как было указано в одном из ответов выше, вы не можете использовать как собственный метод инициализации, так и раскадровку. Но вы по-прежнему можете использовать статический метод для создания экземпляра 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 в качестве идентификатора контроллера представления в вашей раскадровке. Вам также может потребоваться изменить имя раскадровки в приведенном выше коде.
источник
Вы не можете использовать настраиваемый инициализатор при инициализации из раскадровки, используя
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
для целей тестирования. Кроме того, убедитесь, что вы передаете правильный идентификатор перехода ("identifier"
был просто заполнителем).источник
Как отметил @Caleb Kleveter, мы не можем использовать настраиваемый инициализатор при инициализации из раскадровки.
Но мы можем решить проблему, используя метод фабрики / класса, который создает экземпляр объекта контроллера представления из раскадровки и возвращает объект контроллера представления. Думаю, это довольно крутой способ.
Примечание. Это не точный ответ на вопрос, а скорее способ решения проблемы.
Создайте метод класса в классе MemeDetailVC следующим образом:
// Considering your view controller resides in Main.storyboard and it's 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())
источник
class func
init(meme: Meme) -> MemeDetailVC
Xcode 10.2 путается и выдает ошибкуIncorrect argument label in call (have 'meme:', expected 'coder:')
Я сделал это одним из способов - с помощью удобного инициализатора.
class MemeDetailVC : UIViewController { convenience init(meme: Meme) { self.init() self.meme = meme } }
Затем вы инициализируете свой MemeDetailVC с помощью
let memeDetailVC = MemeDetailVC(theMeme)
Документация Apple по инициализаторам довольно хороша, но мне больше всего нравится серия руководств Ray Wenderlich: Initialization in Depth, которая должна дать вам множество объяснений / примеров по вашим различным параметрам инициализации и «правильному» способу работы.
РЕДАКТИРОВАТЬ : хотя вы можете использовать удобный инициализатор на настраиваемых контроллерах представления, все правы, заявляя, что вы не можете использовать настраиваемые инициализаторы при инициализации из раскадровки или через переход раскадровки.
Если ваш интерфейс настроен в раскадровке, и вы создаете контроллер полностью программно, то инициализатор удобства, вероятно, самый простой способ сделать то, что вы пытаетесь сделать, поскольку вам не нужно иметь дело с требуемым init с NSCoder (который я до сих пор не очень понимаю).
Если вы получаете свой контроллер представления через раскадровку, тогда вам нужно будет следовать ответу @Caleb Kleveter и передать контроллер представления в нужный подкласс, а затем установить свойство вручную.
источник
convenience
здесь поможет.init
функции класса (хотя структуры могут). Итак, чтобы вызватьself.init()
, вы должны пометитьinit(meme: Meme)
какconvenience
инициализатор. В противном случае вам придется вручную установить все необходимые свойства aUIViewController
в инициализаторе, и я не уверен, что это за свойства.MemeDetail()
и в этом случае код выйдет изИзначально было несколько ответов, которые были проголосованы и удалены, хотя в основном они были правильными. Ответ: вы не можете.
При работе с определением раскадровки все экземпляры контроллера представления архивируются. Итак, для их инициализации требуется, чтобы
init?(coder...
их использовали.coder
Где вся информация настройки / вид приходит.Таким образом, в этом случае невозможно вызвать другую функцию инициализации с настраиваемым параметром. Его следует либо установить как свойство при подготовке перехода, либо вы можете отказаться от переходов и загрузить экземпляры прямо из раскадровки и настроить их (в основном фабричный шаблон с использованием раскадровки).
Во всех случаях вы используете обязательную функцию инициализации SDK и после этого передаете дополнительные параметры.
источник
Swift 5
Вы можете написать собственный инициализатор следующим образом ->
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 } }
источник
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)
непосредственно initUIViewController
и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 }
источник
Начиная с iOS 13, вы можете инициализировать контроллер представления, который находится в раскадровке, используя
instantiateViewController(identifier:creator:)
метод : вUIStoryboard
экземпляре.руководство: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
источник
Отказ от ответственности: я не сторонник этого и не тщательно проверял его устойчивость, но это потенциальное решение, которое я обнаружил во время экспериментов.
Технически настраиваемая инициализация может быть достигнута при сохранении интерфейса, настроенного для раскадровки, путем двойной инициализации контроллера представления: в первый раз с помощью пользовательского интерфейса
init
, а второй раз - внутри,loadView()
когда вы берете представление из раскадровки.final class CustomViewController: UIViewController { @IBOutlet private weak var label: UILabel! @IBOutlet private weak var textField: UITextField! private let foo: Foo! init(someParameter: Foo) { self.foo = someParameter super.init(nibName: nil, bundle: nil) } override func loadView() { //Only proceed if we are not the storyboard instance guard self.nibName == nil else { return super.loadView() } //Initialize from storyboard let storyboard = UIStoryboard(name: "Main", bundle: nil) let storyboardInstance = storyboard.instantiateViewController(withIdentifier: "CustomVC") as! CustomViewController //Remove view from storyboard instance before assigning to us let storyboardView = storyboardInstance.view storyboardInstance.view.removeFromSuperview() storyboardInstance.view = nil self.view = storyboardView //Receive outlet references from storyboard instance self.label = storyboardInstance.label self.textField = storyboardInstance.textField } required init?(coder: NSCoder) { //Must set all properties intended for custom init to nil here (or make them `var`s) self.foo = nil //Storyboard initialization requires the super implementation super.init(coder: coder) } }
Теперь в другом месте вашего приложения вы можете вызвать свой собственный инициализатор, например,
CustomViewController(someParameter: foo)
и по-прежнему получать конфигурацию представления из раскадровки.Я не считаю это отличным решением по нескольким причинам:
init
должны быть сохранены как необязательные свойства.Возможно, вы можете пойти на эти уступки, но используйте их на свой страх и риск .
источник
Хотя теперь мы можем выполнять настраиваемую инициализацию для контроллеров по умолчанию в раскадровке, используя
instantiateInitialViewController(creator:)
и для сегментов, включая отношения и шоу.Эта возможность была добавлена в Xcode 11, и ниже приводится выдержка из примечаний к выпуску Xcode 11 :
Метод контроллера представления, аннотированный новым
@IBSegueAction
атрибутом, может использоваться для создания целевого контроллера представления перехода в коде с использованием настраиваемого инициализатора с любыми необходимыми значениями. Это позволяет использовать контроллеры представления с необязательными требованиями к инициализации в раскадровках. Создайте соединение от перехода к@IBSegueAction
методу на его контроллере исходного представления. В новых версиях ОС, поддерживающих действияdestinationViewController
перехода , этот метод будет вызываться, и возвращаемое им значение будет соответствовать переданному объекту переходаprepareForSegue:sender:
.@IBSegueAction
На одном контроллере представления исходного кода может быть определено несколько методов, что может избавить от необходимости проверять строки идентификатора перехода вprepareForSegue:sender:
. (47091566)IBSegueAction
Метод принимает до трех параметров: кодер, отправитель и идентификаторы SEGUE в. Первый параметр является обязательным, а остальные параметры при желании можно не указывать в сигнатуре метода.NSCoder
Должен быть передан на инициализаторе По мнению контроллера - адресата, чтобы убедиться в правильности его настроить с помощью значений , настроенных в раскадровке. Метод возвращает контроллер представления, который соответствует типу целевого контроллера, определенному в раскадровке, илиnil
инициирует инициализацию целевого контроллера стандартнымinit(coder:)
методом. Если вы знаете, что вам не нужно возвращатьсяnil
, тип возврата может быть необязательным.В Swift добавьте
@IBSegueAction
атрибут:@IBSegueAction func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? { PetController( coder: coder, petName: self.selectedPetName, type: .dog ) }
В Objective-C добавьте
IBSegueAction
перед типом возвращаемого значения:- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder sender:(id)sender segueIdentifier:(NSString *)segueIdentifier { return [PetController initWithCoder:coder petName:self.selectedPetName type:@"dog"]; }
источник
Правильный поток: вызовите назначенный инициализатор, который в данном случае является init с nibName,
init(tap: UITapGestureRecognizer) { // Initialise the variables here // Call the designated init of ViewController super.init(nibName: nil, bundle: nil) // Call your Viewcontroller custom methods here }
источник
// Контроллер представления находится в 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)
источник