ios-使用情节提要中的界面设置,在Swift中对UIViewController进行自定义初始化

我在为UIViewController的子类编写自定义init时遇到问题,基本上我想将依赖关系通过viewController的init方法传递,而不是像viewControllerB.property = value那样直接设置属性

所以我为viewController做了一个自定义的初始化,并调用了超级指定的初始化

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

视图控制器界面位于情节提要中,我也将自定义类的界面设置为我的视图控制器。 即使您未在此方法中执行任何操作,Swift也需要调用此init方法。 否则编译器会抱怨...

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

问题是当我尝试使用self调用自定义init时,它根本没有在viewController中初始化属性...

我正在尝试调试,我在viewController中发现先调用self,然后再调用我的自定义init。 但是,这两个init方法返回不同的init?(coder aDecoder: NSCoder)内存地址。

我怀疑viewController的init出了问题,它将始终返回selfinit?(coder aDecoder: NSCoder)(没有实现)。

有谁知道如何正确地为您的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)
    }

}
Pigfly asked 2019-10-09T16:33:51Z
6个解决方案
28 votes

正如上面答案之一中指定的那样,您不能同时使用和自定义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
    }
}

不要忘记在故事板上指定标识符ViewController作为视图控制器标识符。您可能还需要在上面的代码中更改情节提要的名称。

Alexander Grushevoy answered 2019-10-09T16:34:16Z
20 votes

从情节提要板进行初始化时,不能使用自定义初始化程序,使用String是Apple设计情节提要板以初始化控制器的方式。 但是,有一些方法可以将数据发送到Meme

您的视图控制器名称中包含String,因此我想您是从另一个控制器到达的。 在这种情况下,您可以使用Meme方法将数据发送到详细信息(这是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"只是一个占位符)。

Caleb Kleveter answered 2019-10-09T16:35:09Z
17 votes

我执行此操作的一种方法是使用便捷初始化程序。

class MemeDetailVC : UIViewController {

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

然后用let memeDetailVC = MemeDetailVC(theMeme)初始化MemeDetailVC

Apple的有关初始化程序的文档非常不错,但我个人最喜欢的是Ray Wenderlich:《深度初始化》教程系列,该教程应该为您提供有关各种init选项和“正确”操作方式的大量解释/示例。


编辑:虽然可以在自定义视图控制器上使用便捷初始化程序,但是每个人都正确地说,从情节提要或通过情节提要进行初始化时不能使用自定义初始化程序。

如果您的界面是在情节提要板上设置的,并且您是完全通过编程方式创建控制器的,那么便捷的初始化程序可能是您要尝试执行的操作的最简单方法,因为您不必使用必需的init进行处理。 NSCoder(我仍然不太了解)。

如果通过情节提要获取视图控制器,则需要遵循@Caleb Kleveter的回答,并将视图控制器转换为所需的子类,然后手动设置属性。

Ponyboy47 answered 2019-10-09T16:36:19Z
12 votes

正如@Caleb Kleveter指出的那样,我们从情节提要进行初始化时不能使用自定义初始化程序。

但是,我们可以使用工厂/类方法解决问题,该方法从Storyboard实例化视图控制器对象并返回视图控制器对象。我认为这是一种非常酷的方法。

注意:这不是问题的确切答案,而是解决问题的解决方法。

MemeDetailVC类中的Make类方法如下:

// 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())
Nitesh Borad answered 2019-10-09T16:37:19Z
7 votes

最初有几个答案,尽管基本上是正确的,但还是被牛票否决了。 答案是,你不能。

使用情节提要板定义工作时,所有视图控制器实例均已存档。 因此,要初始化它们,需要使用init?(coder...coder是所有设置/视图信息的来源。

因此,在这种情况下,无法同时使用自定义参数调用其他init函数。 在准备序列时应将其设置为属性,也可以放弃序列并直接从情节提要中加载实例并进行配置(基本上是使用情节提要的工厂模式)。

在所有情况下,您都需要使用SDK所需的init函数,然后再传递其他参数。

Wain answered 2019-10-09T16:38:13Z
1 votes

init?(coder aDecoder: NSCoder)类符合UIViewController协议,该协议定义为:

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

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

因此init?(coder aDecoder: NSCoder)具有两个指定的初始化程序UIViewControllerUIView

Storyborad直接调用init?(coder aDecoder: NSCoder)到init UIViewControllerUIView,没有空间可以传递参数。

一种麻烦的解决方法是使用临时缓存:

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
}
wj2061 answered 2019-10-09T16:39:02Z
translate from https://stackoverflow.com:/questions/35315404/custom-init-for-uiviewcontroller-in-swift-with-interface-setup-in-storyboard