Niestandardowy init dla UIViewController w Swift z dostosowaniem interfejsu użytkownika w scenorysie


Mam problem z napisaniem niestandardowego init do podklasy UIViewController, w zasadzie chcę przekazać zależność przez metodę init do viewController, zamiast ustawiać właściwość bezpośrednio, jak
viewControllerB.property = value
Zrobiłem więc niestandardowy init dla mojego viewController i wywołałem lokalizacje super init
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}

Interfejs kontrolera widoku znajduje się w scenorysie, zrobiłem również interfejs dla niestandardowej klasy mojego kontrolera widoku. Swift wymaga wywołania tej metody init, nawet jeśli nie robisz nic wewnątrz tej metody. W przeciwnym razie kompilator będzie narzekał ...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

Problem polega na tym, że kiedy próbuję wywołać mój niestandardowy init za pomocą
MyViewController (meme: meme)
, w ogóle nie inicjuje on właściwości w moim viewController ...
Próbowałem debugować, znalazłem w moim viewController,
init (koder aDecoder: NSCoder)
jest wywoływany jako pierwszy, a następnie mój niestandardowy init jest wywoływany później. Jednak te dwie metody init zwracają różne adresy pamięci
self
.
Podejrzewam, że coś jest nie tak z inicjalizacją mojego viewController i zawsze zwróci
self
z
init? (Coder aDecoder: NSCoder)
, który nie ma implementacji.
Czy ktoś wie, jak poprawnie utworzyć niestandardowy init dla twojego viewController?
Uwaga: interfejs my viewController jest skonfigurowany w scenorysie
oto mój kod 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)
}}

Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jak stwierdzono w jednej z powyższych odpowiedzi, nie można używać zarówno niestandardowej metody inicjowania, jak i scenorysu.
Ale nadal możesz użyć metody statycznej, aby utworzyć wystąpienie ViewController z serii ujęć i wykonać dodatkowe dostosowywanie.
Będzie to wyglądać tak:
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
}
}

Pamiętaj, aby dołączyć IdentifierOfYouViewController jako identyfikator kontrolera widoku w serii ujęć.
Może być również konieczna zmiana nazwy serii ujęć w powyższym kodzie.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie można użyć niestandardowego inicjatora podczas inicjowania z serii ujęć przy użyciu
init? (Coder aDecoder: NSCoder)
to sposób, w jaki firma Apple zaprojektowała scenorys do zainicjowania kontrolera. Istnieją jednak sposoby wysyłania danych do
UIViewController
.
Twój kontroler widoku ma w nazwie
detail
, więc zakładam, że dostałeś się tam z innego kontrolera. W takim przypadku możesz użyć metody
readyForSegue
, aby wysłać dane do części (jest to Swift 3):
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
if let controller = segue.destinationViewController as? MemeDetailVC {
controller.meme = "Meme"
}
}
}

Po prostu użyłem właściwości takiej jak
String
zamiast
Meme
do celów testowych. Upewnij się również, że przekazujesz prawidłowy identyfikator segmentu (
„identyfikator”
był tylko symbolem zastępczym).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jak zauważył @Caleb Slanderer, nie możemy użyć niestandardowego inicjatora podczas inicjalizacji z storyboardu.
Ale możemy rozwiązać ten problem za pomocą metody fabryki/klasy, która tworzy wystąpienie obiektu kontrolera widoku z serii ujęć i zwraca obiekt kontrolera widoku.
Myślę, że to całkiem fajny sposób.

Uwaga:

nie jest to dokładna odpowiedź na pytanie, ale raczej obejście umożliwiające rozwiązanie problemu.
Utwórz metodę klasy w klasie MemeDetailVC w następujący sposób:
// 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
}

Za pomocą:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jednym ze sposobów jest użycie wygodnego inicjatora.
class MemeDetailVC : UIViewController { convenience init(meme: Meme) {
self.init()
self.meme = meme
}
}

Następnie inicjalizujesz MemeDetailVC za pomocą
let memeDetailVC = MemeDetailVC (theMeme)
Dokumentacja firmy Apple dotycząca inicjatorów
https://developer.apple.com/li ... .html
całkiem nieźle, ale moim ulubionym jest seria samouczków

Ray Wenderlich: Initialization In Depth
https://www.raywenderlich.com/ ... art-1
samouczek, który powinien dać ci wiele wyjaśnień/przykładów na temat różnych opcji inicjalizacji i jak „poprawnie” robić rzeczy.

EDIT
: Chociaż możesz użyć wygodnego inicjatora na niestandardowych kontrolerach widoku, wszyscy mają rację argumentując, że nie możesz używać niestandardowych inicjatorów podczas inicjowania z storyboardu lub przez segment storyboardu.
Jeśli twój interfejs jest skonfigurowany w scenorysie i tworzysz kontroler całkowicie programowo, to poręczny inicjator jest prawdopodobnie najłatwiejszym sposobem zrobienia tego, co próbujesz zrobić, ponieważ nie musisz zajmować się wymaganym początkiem w NSCoder ( którego nadal nie bardzo rozumiem).
Jeśli jednak otrzymujesz kontroler widoku przez scenorys, musisz postępować

odpowiedź
https://stackoverflow.com/a/39400793/1478580
@Caleb Kleveter i prześlij kontroler widoku do żądanej podklasy, a następnie ustaw właściwość ręcznie.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Początkowo głosowano za i usunięto kilka odpowiedzi, chociaż w większości były one poprawne. Odpowiedź brzmi: nie możesz.
Podczas pracy z definicją scenorysu wszystkie wystąpienia kontrolera widoku są archiwizowane. Dlatego, aby je zainicjować, musisz użyć
init? (Coder ...
.
coder
jest miejscem, z którego pochodzą wszystkie ustawienia/informacje o widoku.
Dlatego w tym przypadku nie jest również możliwe wywołanie żadnej innej funkcji init z parametrem niestandardowym. Musi być albo ustawiona jako właściwość podczas przygotowywania płynności, albo można zrezygnować z segue i załadować wystąpienia bezpośrednio z serii ujęć i dostosować je (w zasadzie szablon fabryki wykorzystujący scenorys).
We wszystkich przypadkach należy użyć funkcji init wymaganej przez zestaw SDK, a następnie przekazać dodatkowe parametry.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:


Swift 5

Możesz napisać taki niestandardowy inicjator - & >
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
}
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Klasa
UIViewController
jest zgodna z protokołem
NSCoding
, który jest zdefiniowany jako:
public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder)// NS_DESIGNATED_INITIALIZER
}

Zatem
UIViewController
ma dwa wyznaczone inicjatory
init? (Coder aDecoder: NSCoder)
and
init (nibName nibNameOrNil: String?, Bundle nibBundleOrNil: Bundle?)
.
Storyborad wywołuje
init? (Coder aDecoder: NSCoder)
bezpośrednio w init
UIViewController
i
UIView
, nie ma miejsca na przekazywanie parametrów.
Jednym uciążliwym obejściem jest użycie tymczasowej pamięci podręcznej:
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
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Od iOS 13 możesz zainicjować kontroler widoku, który znajduje się w serii ujęć, używając:
Metoda
instantiateViewController (identifier: creator :)
w instancji
UIStoryboard
.
przewodnik:

https://sarunw.com/posts/bette ... ios13
https://sarunw.com/posts/bette ... os13/
/
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:


Zastrzeżenie: nie opowiadam się za tym i nie przetestowałem dokładnie jego stabilności, ale tak jest

potencjał

rozwiązanie, które znalazłem podczas gry.
>
Technicznie rzecz biorąc, niestandardową inicjalizację można osiągnąć, zachowując interfejs dostosowany do scenorysu, inicjując dwukrotnie kontroler widoku: pierwszy raz za pomocą niestandardowego
init
, a drugi raz w
loadView ()
gdzie bierzesz widok z serii ujęć.
<pre class="lang-swift prettyprint-override">
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)
}
}

Teraz w innym miejscu aplikacji możesz wywołać swój niestandardowy inicjator, taki jak
CustomViewController (someParameter: foo)
, i nadal uzyskać konfigurację widoku z serii ujęć.
Nie sądzę, żeby to było świetne rozwiązanie z kilku powodów:
  • Inicjalizacja obiektu jest zduplikowana, w tym wszelkie wstępnie zainicjowane właściwości
  • Parametry przekazane do niestandardowego
    init
    powinny być przechowywane jako właściwości opcjonalne
  • Dodaje szablon szablonów, który ma być obsługiwany podczas zmiany punktów sprzedaży/właściwości

Być może możesz zaakceptować te kompromisy,

ale używaj na własne ryzyko

.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Chociaż możemy teraz wykonać niestandardową inicjalizację dla domyślnych kontrolerów w scenorysie przy użyciu
instantiateInitialViewController (twórca :)
oraz dla segmentów, w tym relacji i pokazów.
Ta funkcja została dodana w Xcode 11, a poniżej znajduje się fragment z

Uwagi do wydania Xcode 11
https://developer.apple.com/do ... Dobjc
:
Metoda kontrolera widoku z adnotacją z nowym atrybutem
@IBSegueAction
może służyć do tworzenia docelowego kontrolera widoku płynnego w kodzie przy użyciu niestandardowego inicjatora z dowolnymi żądanymi wartościami. Pozwala to na używanie kontrolerów widoku z opcjonalnymi wymaganiami inicjalizacyjnymi w scenorysach. Utwórz połączenie z segue do metody
@IBSegueAction
na kontrolerze widoku źródła. W nowszych wersjach systemu operacyjnego, które obsługują akcje Segue, ta metoda będzie wywoływana, a jej wartością zwracaną będzie
destinationViewController
obiektu segue przekazanego do
readyForSegue: sender:
. Wiele metod
@IBSegueAction
można zdefiniować na jednym kontrolerze widoku źródła, co może ułatwić sprawdzanie ciągów identyfikatorów segue w
PreparForSegue: sender:
. (47091566)
Metoda
IBSegueAction
akceptuje maksymalnie trzy parametry: koder, nadawcę i identyfikator segmentu. Pierwszy parametr jest wymagany, a pozostałe parametry można w razie potrzeby pominąć w sygnaturze metody. Element
NSCoder
należy przekazać do inicjatora kontrolera widoku docelowego, aby upewnić się, że jest skonfigurowany z wartościami skonfigurowane w scenorysie. Metoda zwraca kontroler widoku, który pasuje do typu kontrolera docelowego zdefiniowanego w scenorysie lub
nil
, aby zainicjować kontroler docelowy za pomocą domyślnej metody
init (coder :)
. Jeśli wiesz, że nie musisz zwracać
nil
, typ zwrotu może być opcjonalny.
W polu Swift dodaj atrybut
@IBSegueAction
:
@IBSegueAction
func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? {
PetController( coder: coder,
petName: self.selectedPetName, type: .dog
)
}

W Objective-C dodaj
IBSegueAction
przed zwracanym typem:
- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder
sender:(id)sender
segueIdentifier:(NSString *)segueIdentifier
{
return [PetController initWithCoder:coder
petName:self.selectedPetName
type:@"dog"];
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Prawidłowym przepływem jest wywołanie wyznaczonego inicjatora, którym w tym przypadku jest inicjalizacja z 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}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Kontroler widoku// znajduje się w Main.storyboard i ma zestaw identyfikatorów
Klasa 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!
}

Klasa A
let objB = customInit(carType:"Any String") navigationController?.pushViewController(objB,animated: true)

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się