Film şeridinde arayüz kurulumuyla Swift'de UIViewController için özel init


89

UIViewController alt sınıfı için özel init yazmakla ilgili sorun yaşıyorum, temelde özelliği doğrudan aşağıdaki gibi ayarlamak yerine, bağımlılığı viewController için init yöntemiyle geçirmek istiyorum. viewControllerB.property = value

Bu yüzden, viewController'ım için özel bir init yaptım ve süper belirlenmiş init'i çağırdım

init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

Görünüm denetleyici arabirimi film şeridinde bulunur, ayrıca özel sınıf için arabirimi görünüm denetleyicim olacak şekilde yaptım. Ve Swift, bu yöntem dahilinde hiçbir şey yapmasanız bile bu init yöntemini çağırmayı gerektirir. Aksi takdirde derleyici şikayet edecek ...

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

Sorun, özel initimi çağırmaya çalıştığımda MyViewController(meme: meme) initimi çağırmaya çalıştığımda, viewController'imdeki özellikleri hiç başlatmıyor ...

Hata ayıklamaya çalışıyordum, viewController'imde buldum, init(coder aDecoder: NSCoder)önce çağrıldı, sonra özel init'im daha sonra çağrıldı. Ancak bu iki init yöntemi farklıself bellek adresleri .

ViewController'ımın init'inde bir sorun olduğundan şüpheleniyorum ve her zaman , uygulaması olmayan olan selfile geri dönecektir init?(coder aDecoder: NSCoder).

ViewController'ınız için özel init yapmayı doğru bir şekilde bilen var mı? Not: viewController arayüzüm film şeridinde ayarlandı

İşte viewController kodum:

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)
    }

}

bunun için bir çözüm buldun mu?
user481610

Yanıtlar:


50

Yukarıdaki cevaplardan birinde belirtildiği gibi, hem özel init yöntemini hem de storyboard'u kullanamazsınız.

Ancak yine de ViewControllerbir film şeridinden örnek oluşturmak ve üzerinde ek kurulum yapmak için statik bir yöntem kullanabilirsiniz .

Bunun gibi görünecek:

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
    }
}

Storyboard'unuzda görüntü denetleyici tanımlayıcısı olarak IdentifierOfYouViewController'ı belirtmeyi unutmayın. Yukarıdaki kodda yer alan film şeridinin adını da değiştirmeniz gerekebilir.


MemeDetailVC başlatıldıktan sonra, "meme" özelliği var olur. Ardından değeri "mem" e atamak için bağımlılık enjeksiyonu devreye girer. Şu anda, loadView () ve viewDidLoad çağrılmadı. Bu iki yöntem MemeDetailVC hiyerarşiyi görüntülemek için ekleme / itme işleminden sonra çağrılacaktır.
Jim Yu

En iyi cevap burada!
PascalS

Yine de başlatma yöntemi gereklidir ve derleyici mem depolanan mülkün başlatılmadığını söyleyecektir
Surendra Kumar

27

Bir Storyboard'dan başlattığınızda özel bir başlatıcı kullanamazsınız, init?(coder aDecoder: NSCoder)Apple'ın bir denetleyiciyi başlatmak için storyboard'u nasıl tasarladığını kullanarak . Ancak, verileri bir UIViewController.

Görüntü denetleyicinizin adı detail, bu yüzden oraya farklı bir denetleyiciden ulaştığınızı düşünüyorum. Bu durumda prepareForSegueayrıntıya veri göndermek için yöntemi kullanabilirsiniz (Bu Swift 3):

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "identifier" {
        if let controller = segue.destinationViewController as? MemeDetailVC {
            controller.meme = "Meme"
        }
    }
}

Test amaçlı Stringkullanmak yerine sadece bir tür özelliği kullandım Meme. Ayrıca, doğru segment tanımlayıcısını ( "identifier"yalnızca bir yer tutucuydu) geçirdiğinizden emin olun .


1
Merhaba, ama isteğe bağlı olarak belirtmekten nasıl kurtulabiliriz ve atamamakla da hata yapmak istemiyoruz. Biz sadece denetleyici için anlamlı olan katı bağımlılığın ilk etapta var olmasını istiyoruz.
Amber K

1
@AmberK Arayüz Oluşturucu kullanmak yerine arayüzünüzü programlamaya bakmak isteyebilirsiniz.
Caleb Kleveter

Tamam, ben de referans için kickstarter ios projesine bakıyorum, henüz görüş modellerini nasıl gönderdiklerini söyleyemiyorum.
Amber K

23

@Caleb Kleveter'in belirttiği gibi, Storyboard'dan başlatırken özel bir başlatıcı kullanamayız.

Ancak, sorunu Storyboard'dan view controller nesnesini başlatan ve view controller nesnesini döndüren factory / class yöntemini kullanarak çözebiliriz. Bence bu oldukça havalı bir yol.

Not: Bu, sorunun cevabı değil, sorunu çözmek için bir geçici çözümdür.

MemeDetailVC sınıfında aşağıdaki gibi sınıf yöntemini yapın:

// 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
}

Kullanım:

let memeDetailVC = MemeDetailVC.init(meme: Meme())

5
Ancak yine de dezavantajı var, mülkiyet isteğe bağlı olmalı.
Amber K

kullanırken class func init (meme: Meme) -> MemeDetailVChatası Xcode 10.2 kafası karışık ve verirIncorrect argument label in call (have 'meme:', expected 'coder:')
Samuel

21

Bunu yapmanın bir yolu, kolaylık başlatıcıdır.

class MemeDetailVC : UIViewController {

    convenience init(meme: Meme) {
        self.init()
        self.meme = meme
    }
}

Sonra MemeDetailVC'nizi let memeDetailVC = MemeDetailVC(theMeme)

Apple'ın başlatıcılarla ilgili dokümantasyonu oldukça iyi, ancak benim kişisel favorim Ray Wenderlich: Çeşitli başlatma seçenekleriniz ve işleri yapmanın "uygun" yolu hakkında size bol miktarda açıklama / örnek verecek olan Derinlikte Başlatma öğretici serisidir.


DÜZENLEME : Özel görünüm denetleyicilerinde kullanışlı bir başlatıcı kullanabilirsiniz, ancak herkes film şeridinden veya bir film şeridi bölümünden başlatırken özel başlatıcıları kullanamayacağınızı söylerken haklıdır.

Arayüzünüz film şeridinde ayarlanmışsa ve denetleyiciyi tamamen programlı bir şekilde oluşturuyorsanız, bir kolaylık başlatıcı muhtemelen yapmaya çalıştığınız şeyi yapmanın en kolay yoludur çünkü gerekli init ile uğraşmak zorunda değilsiniz. NSCoder (hala gerçekten anlamadığım).

Görüntü denetleyicinizi film şeridi aracılığıyla alıyorsanız, @Caleb Kleveter'in cevabını takip etmeniz ve görünüm denetleyicisini istediğiniz alt sınıfa atmanız ve ardından özelliği manuel olarak ayarlamanız gerekir.


1
Anlamıyorum, arayüzüm film şeridinde kuruluysa ve programlı olarak oluşturuyorsanız, o zaman arayüzü yüklemek için film şeridini kullanarak örnekleme yöntemini çağırmalıyım değil mi? convenienceBurada yardım nasıl olacak ?
Amber K

Swift'teki sınıflar init, sınıfın başka bir işlevi çağrılarak başlatılamaz (yapılar olabilir). Bu yüzden aramak için self.init(), başlatıcı init(meme: Meme)olarak işaretlemelisiniz convenience. Aksi takdirde, UIViewControllerbaşlatıcıdaki a'nın gerekli tüm özelliklerini kendiniz ayarlamanız gerekir ve tüm bu özelliklerin ne olduğundan emin değilim.
Ponyboy47

bu, UIViewController'ın alt sınıflarına ayrılmaz, çünkü super.init () değil self.init () 'i çağırıyorsunuz. Hala MemeDetailVC'yi varsayılan init gibi kullanarak başlatabilirsiniz MemeDetail()ve bu durumda kod çökecektir
Ali

Halen alt sınıflama olarak kabul edilir çünkü self.init () uygulamasında super.init () 'i çağırmak zorundadır veya belki de self.init () doğrudan üst öğeden miras alınır, bu durumda bunlar işlevsel olarak eşdeğerdir.
Ponyboy47

6

Başlangıçta, temelde doğru olsalar bile ineklerin oylanıp silinen birkaç cevap vardı. Cevap, yapamazsınız.

Bir film şeridi tanımından çalışırken, görünüm denetleyici örneklerinizin tümü arşivlenir. Yani, onları başlatmak init?(coder...için kullanılması gerekiyor. coderTüm ayarlar / görünüm bilgisi nereden geldiğini olduğunu.

Bu nedenle, bu durumda, özel bir parametreyle başka bir init işlevini de çağırmak mümkün değildir. Bölüm hazırlanırken bir özellik olarak ayarlanmalıdır veya bölümleri kaldırıp örnekleri doğrudan film şeridinden yükleyebilir ve bunları yapılandırabilirsiniz (temelde bir film şeridi kullanan bir fabrika modeli).

Her durumda, SDK gerekli başlatma işlevini kullanır ve daha sonra ek parametreleri iletirsiniz.


4

Swift 5

Bunun gibi özel başlatıcı yazabilirsiniz ->

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
    }
}

3

UIViewControllersınıf NSCodingaşağıdaki gibi tanımlanan protokole uygundur :

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

   public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}    

Böylece UIViewControlleriki atanmış başlatıcı init?(coder aDecoder: NSCoder)ve init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?).

Storyborad init?(coder aDecoder: NSCoder)doğrudan init'i çağırır UIViewControllerveUIView yer yoktur.

Zor bir çözüm, geçici bir önbellek kullanmaktır:

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
}


1

Doğru akış, bu durumda nibName ile init olan atanmış başlatıcıyı çağırın,

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

}

0

Feragatname: Bunu savunmuyorum ve dayanıklılığını tam olarak test etmedim , ancak oyun oynarken keşfettiğim potansiyel bir çözüm.

Teknik olarak, özel başlatma, görünüm denetleyicisini iki kez başlatarak film şeridi ile yapılandırılmış arabirimi korurken gerçekleştirilebilir: ilk kez özel cihazınız aracılığıyla initve ikinci kez içinde loadView()görüntüyü film şeridinden aldığınız yer.

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)
  }
}

Artık uygulamanızın başka bir yerinde, özel başlatıcınızı gibi arayabilir CustomViewController(someParameter: foo)ve yine de film şeridinden görünüm yapılandırmasını alabilirsiniz.

Bunun birkaç nedenden dolayı harika bir çözüm olduğunu düşünmüyorum:

  • Başlatma öncesi özellikler dahil olmak üzere nesne başlatma yinelenir
  • Müşteriye aktarılan parametreler init, isteğe bağlı özellikler olarak depolanmalıdır
  • Çıkışlar / özellikler değiştikçe bakımı yapılması gereken standart şablon ekler

Belki bu değiş tokuşları kabul edebilirsiniz, ancak riski size ait olacak şekilde kullanın .


0

Bununla birlikte, artık storyboard'daki varsayılan denetleyiciler instantiateInitialViewController(creator:)için ve ilişki ve şov içeren segmentler için özel init yapabiliriz .

Bu özellik Xcode 11'e eklenmiştir ve aşağıda Xcode 11 Sürüm Notlarından bir alıntı verilmiştir :

Yeni @IBSegueActionöznitelikle açıklanmış bir görünüm denetleyici yöntemi , herhangi bir gerekli değerle özel bir başlatıcı kullanarak kodda bir segmentin hedef görünüm denetleyicisini oluşturmak için kullanılabilir. Bu, görsel senaryo taslaklarında isteğe bağlı olmayan başlatma gereksinimleri olan görünüm denetleyicilerini kullanmayı mümkün kılar. @IBSegueActionKaynak görünüm denetleyicisindeki bir segmentten bir yönteme bağlantı oluşturun . Segue Eylemlerini destekleyen yeni işletim sistemi sürümlerinde, bu yöntem çağrılacak ve döndürdüğü değer, iletilen segment destinationViewControllernesnesinin değeri olacaktır prepareForSegue:sender:. Birden çok @IBSegueActionyöntem, tek bir kaynak görünüm denetleyicisinde tanımlanabilir ve bu, içindeki segment tanımlayıcı dizgilerinin kontrol edilmesi ihtiyacını azaltabilir prepareForSegue:sender:. (47091566)

Bir IBSegueActionyöntem en fazla üç parametre alır: bir kodlayıcı, gönderen ve segmentin tanımlayıcısı. İlk parametre gereklidir ve istenirse diğer parametreler yönteminizin imzasından çıkarılabilir. NSCoderO Film şeridinde yapılandırılan değerlerle özelleştirilmiş oluyor sağlamak için, hedef görünümü kontrolörün başlatıcısı kadar geçirilmelidir. Yöntem, film şeridinde tanımlanan hedef denetleyici türüyle eşleşen veya nilbir hedef denetleyicinin standart init(coder:)yöntemle başlatılmasına neden olan bir görünüm denetleyicisi döndürür . Geri dönmeniz gerekmediğini biliyorsanız nil, iade türü isteğe bağlı olmayabilir.

Swift'de şu @IBSegueActionözelliği ekleyin :

@IBSegueAction
func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? {
    PetController(
        coder: coder,
        petName:  self.selectedPetName, type: .dog
    )
}

Objective-C'de, IBSegueActiondönüş türünün önüne ekleyin :

- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder
               sender:(id)sender
      segueIdentifier:(NSString *)segueIdentifier
{
   return [PetController initWithCoder:coder
                               petName:self.selectedPetName
                                  type:@"dog"];
}

-1

// Görünüm denetleyicisi Main.storyboard'da ve tanımlayıcı kümesine sahip

B sınıfı

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!
}

A sınıfı

let objB = customInit(carType:"Any String")

 navigationController?.pushViewController(objB,animated: true)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.