【问题标题】:How do I write a custom init for a UIView subclass in Swift?如何在 Swift 中为 UIView 子类编写自定义初始化?
【发布时间】:2014-08-11 21:54:07
【问题描述】:

假设我想 init 一个带有 StringIntUIView 子类。

如果我只是继承 UIView,我将如何在 Swift 中执行此操作?如果我只是创建了一个自定义 init() 函数但参数是一个字符串和一个 Int,它会告诉我“在从初始化程序返回之前不会调用 super.init()”。

如果我打电话给super.init(),我被告知我必须使用指定的初始化程序。我应该在那里使用什么?框架版本?编码器版本?两个都?为什么?

【问题讨论】:

    标签: ios swift cocoa-touch uiview instantiation


    【解决方案1】:

    init(frame:) 版本是默认初始化程序。只有在初始化实例变量后才能调用它。如果这个视图是从一个 Nib 重构的,那么你的自定义初始化器将不会被调用,而是会调用 init?(coder:) 版本。由于 Swift 现在需要实现所需的 init?(coder:),因此我更新了下面的示例并将 let 变量声明更改为 var 和可选的。在这种情况下,您可以在 awakeFromNib() 或稍后的某个时间初始化它们。

    class TestView : UIView {
        var s: String?
        var i: Int?
        init(s: String, i: Int) {
            self.s = s
            self.i = i
            super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    }
    

    【讨论】:

    • 那么一定要让他们var。但 Swift 中默认的最佳实践是声明变量 let,除非有理由声明它们 var。在我上面的代码示例中没有这样的理由这样做,因此let
    • 此代码无法编译。您需要实现所需的初始化程序init(coder:)
    • 有趣的是几年前这是如何编译的。现在它在 init(coder:) 下抱怨“属性 self.s 未在 super.init 调用时初始化”
    • 修复了 Swift 3.1 的示例。在导入 UIKit 的操场下编译。
    • @LightNight 我将si 设为可选,以使这里的事情变得简单。如果它们不是可选的,则还需要在所需的初始化程序中对其进行初始化。使它们成为可选意味着当调用super.init() 时它们将成为nil。如果它们不是可选的,则确实需要在调用 super.init() 之前对其进行分配。
    【解决方案2】:

    我为指定的和需要的创建一个通用的 init。为方便起见,我委托init(frame:),框架为零。

    零帧不是问题,因为视图通常位于 ViewController 的视图中;当其父视图调用layoutSubviews()updateConstraints() 时,您的自定义视图将获得一个很好、安全的机会来布局其子视图。这两个函数由系统在整个视图层次结构中递归调用。您可以使用updateContstraints()layoutSubviews()。首先调用updateContstraints(),然后调用layoutSubviews()。在updateConstraints() 中确保调用超级last。在layoutSubviews(),调用superfirst

    这是我的工作:

    @IBDesignable
    class MyView: UIView {
    
          convenience init(args: Whatever) {
              self.init(frame: CGRect.zero)
              //assign custom vars
          }
    
          override init(frame: CGRect) {
               super.init(frame: frame)
               commonInit()
          }
    
          required init?(coder aDecoder: NSCoder) {
               super.init(coder: aDecoder)
               commonInit()
          }
    
          override func prepareForInterfaceBuilder() {
               super.prepareForInterfaceBuilder()
               commonInit()
          }
    
          private func commonInit() {
               //custom initialization
          }
    
          override func updateConstraints() {
               //set subview constraints here
               super.updateConstraints()
          }
    
          override func layoutSubviews() {
               super.layoutSubviews()
               //manually set subview frames here
          }
    
    }
    

    【讨论】:

    • 它不应该工作:在super.init初始化self之前在方法调用'commonInit'中使用'self'
    • 在 self.init 调用后初始化自定义参数。更新了我的答案。
    • 但是如果你想在commonInit方法中初始化一些属性,但是在这种情况下你不能把它放在super之后,因为你应该在super调用之前初始化所有属性。哈哈,这似乎是死循环。
    • 这就是 Swift 初始化通常的工作方式:搜索“两阶段初始化”。您可以使用隐式展开的选项,但我建议不要这样做。您的架构,尤其是在处理视图时,应该初始化所有本地属性。我现在已经对数百个视图使用了这个 commonInit() 方法。它有效
    【解决方案3】:

    Swift 5 解决方案

    你可以试试这个实现在 XCode 11 上运行 Swift 5

    
    class CustomView: UIView {
    
        var customParam: customType
        
        var container = UIView()
        
        required init(customParamArg: customType) {
            self.customParam = customParamArg
            super.init(frame: .zero)
            // Setting up the view can be done here
            setupView()
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
    
        func setupView() {
            // Can do the setup of the view, including adding subviews
    
            setupConstraints()
        }
        
        func setupConstraints() {
            // setup custom constraints as you wish
        }
        
        
    }
    
    
    

    【讨论】:

    • 您也可以添加private init() { fatalError() } 以确保必须使用您的自定义初始化
    • 如果将uiview放到storyboard中会导致崩溃,用coder调用init
    【解决方案4】:

    这是我在 iOS 9 上使用 Swift 的方法 -

    import UIKit
    
    class CustomView : UIView {
    
        init() {
            super.init(frame: UIScreen.mainScreen().bounds);
    
            //for debug validation
            self.backgroundColor = UIColor.blueColor();
            print("My Custom Init");
    
            return;
        }
    
        required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
    }
    

    这是一个完整的项目示例:

    【讨论】:

    • 这会将整个显示用于视图
    • 是的!如果对部分子视图感兴趣,请告诉我,我也会发布此内容
    • 我最喜欢这个答案,因为有 fatalError 意味着我不必在所需的 init 中放入任何代码。
    • @J-Dizzle,我想看看部分视图的解决方案。
    • 您的答案不是与接受的答案相反吗?我的意思是您在super.init 之后进行自定义,但他说应该在super.init 之前完成...
    【解决方案5】:

    这是我在 Swift 中在 iOS 上创建子视图的方法 -

    class CustomSubview : UIView {
    
        init() {
            super.init(frame: UIScreen.mainScreen().bounds);
    
            let windowHeight : CGFloat = 150;
            let windowWidth  : CGFloat = 360;
    
            self.backgroundColor = UIColor.whiteColor();
            self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
            self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);
    
            //for debug validation
            self.backgroundColor = UIColor.grayColor();
            print("My Custom Init");
    
            return;
        }
    
        required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
    }
    

    【讨论】:

    • 很好地使用了 fatalError() 调用。我不得不使用选项来消除来自初始化程序的警告,而该初始化程序甚至没有被使用。这就直接闭嘴了!谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    • 2020-06-26
    相关资源
    最近更新 更多