Saya mengalami masalah untuk menulis init kustom untuk subclass UIViewController, pada dasarnya saya ingin meneruskan ketergantungan melalui metode init untuk viewController daripada mengatur properti secara langsung seperti viewControllerB.property = value
Jadi saya membuat init khusus untuk viewController saya dan memanggil init yang ditunjuk super
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
Antarmuka pengontrol tampilan berada di papan cerita, saya juga membuat antarmuka untuk kelas khusus menjadi pengontrol tampilan saya. Dan Swift perlu memanggil metode init ini meskipun Anda tidak melakukan apa pun dalam metode ini. Jika tidak, kompilator akan mengeluh ...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Masalahnya adalah ketika saya mencoba memanggil init kustom saya dengan MyViewController(meme: meme)
itu tidak init properti di viewController saya sama sekali ...
Saya mencoba men-debug, saya menemukan di viewController saya, init(coder aDecoder: NSCoder)
dipanggil terlebih dahulu, kemudian init kustom saya dipanggil nanti. Namun kedua metode init ini mengembalikan self
alamat memori yang berbeda .
Saya mencurigai ada yang salah dengan init untuk viewController saya, dan itu akan selalu kembali self
dengan init?(coder aDecoder: NSCoder)
, yang, tidak memiliki implementasi.
Apakah ada yang tahu cara membuat init kustom untuk viewController Anda dengan benar? Catatan: antarmuka viewController saya diatur di storyboard
berikut adalah kode viewController saya:
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)
}
}
sumber
Jawaban:
Seperti yang ditentukan dalam salah satu jawaban di atas, Anda tidak dapat menggunakan metode init kustom dan storyboard.
Tetapi Anda masih dapat menggunakan metode statis untuk membuat instance
ViewController
dari storyboard dan melakukan penyiapan tambahan di atasnya.Ini akan terlihat seperti ini:
class MemeDetailVC : UIViewController { var meme : Meme! static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC { let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "IdentifierOfYouViewController") as! MemeDetailVC newViewController.meme = meme return newViewController } }
Jangan lupa untuk menentukan IdentifierOfYouViewController sebagai pengenal pengontrol tampilan di storyboard Anda. Anda mungkin juga perlu mengubah nama storyboard pada kode di atas.
sumber
Anda tidak dapat menggunakan penginisialisasi khusus saat Anda menginisialisasi dari Storyboard, menggunakan
init?(coder aDecoder: NSCoder)
cara Apple merancang storyboard untuk menginisialisasi pengontrol. Namun, ada cara untuk mengirim data ke fileUIViewController
.Nama pengontrol tampilan Anda ada
detail
di dalamnya, jadi saya kira Anda sampai di sana dari pengontrol yang berbeda. Dalam hal ini Anda dapat menggunakanprepareForSegue
metode untuk mengirim data ke detail (Ini Swift 3):override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "identifier" { if let controller = segue.destinationViewController as? MemeDetailVC { controller.meme = "Meme" } } }
Saya baru saja menggunakan properti tipe
String
alih-alihMeme
untuk tujuan pengujian. Selain itu, pastikan Anda memasukkan pengenal segue yang benar ("identifier"
hanya sebagai placeholder).sumber
Seperti yang ditunjukkan oleh @Caleb Kleveter, kita tidak dapat menggunakan penginisialisasi khusus saat memulai dari Storyboard.
Namun, kita dapat mengatasi masalah tersebut dengan menggunakan metode factory / class yang membuat instance objek view controller dari Storyboard dan mengembalikan objek view controller. Saya pikir ini cara yang cukup keren.
Catatan: Ini bukan jawaban yang tepat untuk pertanyaan melainkan solusi untuk memecahkan masalah.
Buat metode kelas, di kelas MemeDetailVC, sebagai berikut:
// 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 }
Pemakaian:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
sumber
class func
init(meme: Meme) -> MemeDetailVC
Xcode 10.2 bingung dan memberikan kesalahanIncorrect argument label in call (have 'meme:', expected 'coder:')
Salah satu cara yang saya lakukan adalah dengan penginisialisasi praktis.
class MemeDetailVC : UIViewController { convenience init(meme: Meme) { self.init() self.meme = meme } }
Kemudian Anda menginisialisasi MemeDetailVC Anda dengan
let memeDetailVC = MemeDetailVC(theMeme)
Dokumentasi Apple tentang penginisialisasi cukup bagus, tetapi favorit pribadi saya adalah seri tutorial Ray Wenderlich: Inisialisasi dalam Kedalaman yang akan memberi Anda banyak penjelasan / contoh tentang berbagai pilihan init dan cara yang "tepat" untuk melakukan sesuatu.
EDIT : Meskipun Anda dapat menggunakan penginisialisasi praktis pada pengontrol tampilan kustom, semua orang benar dalam menyatakan bahwa Anda tidak dapat menggunakan penginisialisasi kustom saat menginisialisasi dari storyboard atau melalui segmen storyboard.
Jika antarmuka Anda diatur di storyboard dan Anda membuat pengontrol sepenuhnya secara terprogram, maka penginisialisasi praktis mungkin adalah cara termudah untuk melakukan apa yang Anda coba lakukan karena Anda tidak harus berurusan dengan init yang diperlukan NSCoder (yang masih belum saya mengerti).
Jika Anda mendapatkan pengontrol tampilan melalui storyboard, Anda harus mengikuti jawaban @Caleb Kleveter dan memasukkan pengontrol tampilan ke dalam subkelas yang Anda inginkan, lalu atur propertinya secara manual.
sumber
convenience
bantuannya di sini.init
fungsi lain dari kelas (meskipun struct dapat). Jadi untuk memanggilself.init()
, Anda harus menandaiinit(meme: Meme)
sebagaiconvenience
penginisialisasi. Jika tidak, Anda harus menyetel sendiri semua properti yang diperlukan dari aUIViewController
di penginisialisasi, dan saya tidak yakin apa semua properti itu.MemeDetail()
dan dalam hal ini kode akan macetAwalnya ada beberapa jawaban, yaitu sapi dipilih dan dihapus meskipun pada dasarnya benar. Jawabannya adalah, Anda tidak bisa.
Saat bekerja dari definisi storyboard, semua instance pengontrol tampilan Anda diarsipkan. Jadi, untuk memulai mereka diperlukan yang
init?(coder...
digunakan. Daricoder
sinilah semua informasi pengaturan / tampilan berasal.Jadi, dalam kasus ini, tidak mungkin juga memanggil beberapa fungsi init lainnya dengan parameter khusus. Ini harus ditetapkan sebagai properti saat menyiapkan segue, atau Anda dapat membuang segue dan memuat instance langsung dari storyboard dan mengonfigurasinya (pada dasarnya pola pabrik menggunakan storyboard).
Dalam semua kasus, Anda menggunakan fungsi init SDK yang diperlukan dan meneruskan parameter tambahan setelahnya.
sumber
Cepat 5
Anda dapat menulis penginisialisasi khusus seperti ini ->
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 } }
sumber
UIViewController
kelas sesuai denganNSCoding
protokol yang didefinisikan sebagai:public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER }
Jadi
UIViewController
memiliki dua penginisialisasi yang ditunjukinit?(coder aDecoder: NSCoder)
daninit(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
.Storyborad memanggil
init?(coder aDecoder: NSCoder)
langsung ke initUIViewController
danUIView
, Tidak ada ruang bagi Anda untuk menyampaikan parameter.Salah satu solusi yang rumit adalah dengan menggunakan cache sementara:
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 }
sumber
Mulai iOS 13, Anda dapat menginisialisasi pengontrol tampilan yang berada di storyboard menggunakan
instantiateViewController(identifier:creator:)
metode : padaUIStoryboard
instance.tutorial: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
sumber
Alur yang benar adalah, panggil penginisialisasi yang ditunjuk yang dalam hal ini adalah init dengan 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 }
sumber
Penafian: Saya tidak menganjurkan hal ini dan belum menguji ketahanannya secara menyeluruh, tetapi ini adalah solusi potensial yang saya temukan saat bermain-main.
Secara teknis, inisialisasi kustom dapat dicapai sambil mempertahankan antarmuka yang dikonfigurasi storyboard dengan menginisialisasi pengontrol tampilan dua kali: pertama kali melalui kustom Anda
init
, dan kedua kalinya di dalamloadView()
tempat Anda mengambil tampilan dari storyboard.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) } }
Sekarang di tempat lain di aplikasi Anda, Anda dapat memanggil penginisialisasi kustom Anda seperti
CustomViewController(someParameter: foo)
dan masih menerima konfigurasi tampilan dari storyboard.Saya tidak menganggap ini solusi yang bagus karena beberapa alasan:
init
harus disimpan sebagai properti opsionalMungkin Anda dapat menerima pengorbanan ini, tetapi gunakan dengan risiko Anda sendiri .
sumber
Meskipun sekarang kita dapat melakukan init khusus untuk pengontrol default di storyboard menggunakan
instantiateInitialViewController(creator:)
dan untuk segues termasuk hubungan dan pertunjukan.Kemampuan ini ditambahkan di Xcode 11 dan berikut ini adalah kutipan dari Catatan Rilis Xcode 11 :
Metode pengontrol tampilan yang dianotasi dengan
@IBSegueAction
atribut baru dapat digunakan untuk membuat pengontrol tampilan tujuan segue dalam kode, menggunakan penginisialisasi kustom dengan nilai yang diperlukan. Ini memungkinkan untuk menggunakan pengontrol tampilan dengan persyaratan inisialisasi non-opsional di storyboard. Buat koneksi dari segue ke@IBSegueAction
metode pada pengontrol tampilan sumbernya. Pada versi OS baru yang mendukung Segue Actions, metode itu akan dipanggil dan nilai yang dikembalikannya akan menjadidestinationViewController
objek segue yang diteruskanprepareForSegue:sender:
. Beberapa@IBSegueAction
metode dapat didefinisikan pada pengontrol tampilan sumber tunggal, yang dapat mengurangi kebutuhan untuk memeriksa string pengenal segue diprepareForSegue:sender:
. (47091566)Sebuah
IBSegueAction
metode membutuhkan hingga tiga parameter: pembuat kode, pengirim, dan pengenal segue. Parameter pertama diperlukan, dan parameter lainnya dapat dihilangkan dari tanda tangan metode Anda jika diinginkan. TheNSCoder
harus melewati ke initializer tujuan pandangan controller, untuk memastikan itu disesuaikan dengan nilai-nilai dikonfigurasi dalam storyboard. Metode ini mengembalikan pengontrol tampilan yang cocok dengan jenis pengontrol tujuan yang ditentukan di papan cerita, ataunil
menyebabkan pengontrol tujuan diinisialisasi denganinit(coder:)
metode standar . Jika Anda tahu Anda tidak perlu mengembalikannil
, jenis pengembalian bisa non-opsional.Di Swift, tambahkan
@IBSegueAction
atribut:@IBSegueAction func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? { PetController( coder: coder, petName: self.selectedPetName, type: .dog ) }
Di Objective-C, tambahkan
IBSegueAction
di depan tipe pengembalian:- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder sender:(id)sender segueIdentifier:(NSString *)segueIdentifier { return [PetController initWithCoder:coder petName:self.selectedPetName type:@"dog"]; }
sumber
// View controller ada di Main.storyboard dan memiliki identifier set
Kelas 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! }
Kelas A
let objB = customInit(carType:"Any String") navigationController?.pushViewController(objB,animated: true)
sumber