【问题标题】:ios: questions regarding init(frame:) and init?(coder:)ios: 关于 init(frame:) 和 init?(coder:) 的问题
【发布时间】:2018-05-17 03:46:14
【问题描述】:

苹果的教程将init(frame:)init?(coder:)的区别描述为

您通常通过以下两种方式之一创建视图:以编程方式 初始化视图,或者允许视图被 故事板。每种方法都有一个对应的初始化器: init(frame:) 用于以编程方式初始化视图和 init?(coder:) 用于从情节提要加载视图。你会需要 在您的自定义控件中实现这两种方法。尽管 设计应用程序时,Interface Builder 以编程方式实例化 将其添加到画布时查看。在运行时,您的应用会加载 从情节提要中查看。

我对“以编程方式初始化”和“由情节提要加载”的描述感到非常困惑。假设我有一个名为 MyViewUIView 的子类,“以编程方式初始化”是否意味着我编写代码以将 MyView 的实例添加到类似的地方:

override func viewDidLoad() {      
        super.viewDidLoad()
        let myView = MyView()  // init(frame:) get invoked here??
}

Main.storyboard 中调用 init?(coder:) 时,我从对象库中拖出一个 UIView,然后在身份检查器中将其类设置为 MyView?

此外,在我的 xcode 项目中,这两种方法最终会得到不同的模拟器布局,而 Main.storyboard 具有相同的代码:

import UIKit

@IBDesignable
class RecordView: UIView {

    @IBInspectable
    var borderColor: UIColor = UIColor.clear {
        didSet {
            self.layer.borderColor = borderColor.cgColor
        }
    }

    @IBInspectable
    var borderWidth: CGFloat = 20 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }

    @IBInspectable
    var cornerRadius: CGFloat = 100 {
        didSet {
            layer.cornerRadius = cornerRadius
        }
    }

    private var fillView = UIView()

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

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupFillView()
    }



    private func setupFillView() {
        let radius = (self.cornerRadius - self.borderWidth) * 0.95
        fillView.frame = CGRect(origin: CGPoint.zero, size: CGSize(width: radius * 2, height: radius * 2))
        fillView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        fillView.layer.cornerRadius = radius
        fillView.backgroundColor = UIColor.red
        self.addSubview(fillView)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
    }

    func didClick() {
        UIView.animate(withDuration: 1.0, animations: {
            self.fillView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
        }) { (true) in
            print()
        }
    }
}

为什么他们的行为不同? (我从对象库中拖出一个UIView 并将其类设置为RecordView)

【问题讨论】:

    标签: ios xcode uiview initwithcoder initwithframe


    【解决方案1】:

    我对“以编程方式初始化”和“由情节提要加载”的描述感到很困惑。

    基于对象的编程是关于类和实例的。您需要创建一个类的实例。在 Xcode 中,有两种大体上不同的方式来获取类的实例:

    • 您的代码创建实例

    • 您加载一个 nib(例如故事板中的视图控制器视图),然后 nib 加载过程创建实例并将其交给您

    在这两种情况下调用的初始化程序是不同的。如果您的代码创建了一个 UIView 实例,那么您必须调用的指定初始化程序是 init(frame:)。但是如果 nib 创建视图,则 nib 加载过程调用的指定初始化程序是 init(coder:)

    因此,如果你有一个 UIView 子类并且你想重写初始化器,你必须考虑将调用哪个初始化器(基于视图实例的创建方式)。

    【讨论】:

    • 我注意到如果我将init?(coder:)中的setupFillView方法注释掉并保留init(frame:)中的方法,运行模拟器时将不会添加红色圆圈。我可能认为这是因为我在情节提要中创建了 RecordView 的这个实例。但是在 Main.storyboard 我仍然可以看到渲染的红色圆圈,我不知道为什么会这样。另外,如果我删除 init(frame:) 中的方法,但将其保留在 init?(coder:) 中,它在模拟器中运行良好,但在 Main.storyboard 中没有红色圆圈。
    • 如果您已将视图添加到情节提要场景中,则在运行应用程序时将使用 init?(coder:)。但是,如果该视图是IBDesignable,当您在 IB 中预览情节提要场景时,它将使用 init(frame:)
    • @Rob 是的,IBDesignable 意味着我们在代码中进行实例化,毕竟(尽管它是幕后的 Objective-C 代码,而不是我们自己的显式代码),所以它是有意义的。涵盖所有案例并不是我的目标,只是为了回应引用句子中普遍要求理解的呼声。
    • Matt,我的评论是给 Yonliang 的,他想知道为什么当 init(coder:) 被注释掉时他会在 IB 中看到它,但是当 init(frame:) 被注释掉时它就消失了。他一定是在做@IBDesignable 意见。
    • 这是我的错。它确实是一个@IBDesignable 视图。我曾经认为这是一个无关紧要的部分,并没有发布。我已经编辑了这个问题。对此感到抱歉。
    【解决方案2】:

    首先,您在init?(coder:)init(frame:) 之间的划分基本上是正确的。前者用于在实际运行应用程序时实例化情节提要场景,而后者用于以编程方式使用let foo = RecordView()let bar = RecordView(frame: ...) 实例化它。此外,init(frame:) 在 IB 中预览 @IBDesignable 视图时使用。

    其次,关于您的问题,我建议您从setupFillView 中删除fillViewcenter 的设置(以及角半径的东西)。问题是当init 被调用时,你一般不知道bounds 最终会是什么。您应该在layoutSubviews 中设置center,每次视图更改大小时都会调用它。

    class RecordView: UIView {  // this is the black circle with a white border
    
        private var fillView = UIView() // this is the inner red circle
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupFillView()
        }
    
        override init(frame: CGRect = .zero) {
            super.init(frame: frame)
            setupFillView()
        }
    
        private func setupFillView() {
            fillView.backgroundColor = .red
            self.addSubview(fillView)
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            let radius = (cornerRadius - borderWidth) * 0.95       // these are not defined in this snippet, but I simply assume you omitted them for the sake of brevity?
            fillView.frame = CGRect(origin: .zero, size: CGSize(width: radius * 2, height: radius * 2))
            fillView.layer.cornerRadius = radius
            fillView.center = CGPoint(x: bounds.midX, y: bounds.midY)
        }
    }
    

    【讨论】:

    • 以前我确实将这些代码放在 layoutSubViews() 中。然后,当我尝试实现使用 UIView.animateWithScale 缩小红色圆圈的动画时,我得到了意想不到的结果。现在不知道在哪里写初始化代码...I also asked about this issue here
    • @YongliangHe - 基本配置和添加子视图属于init 方法(或从那里调用的方法)。您将取决于视图大小的代码放在layoutSubviews 中。在确定大小之前,会调用一次init 方法。 layoutSubviews 将在大小确定后被调用(并且可能被调用多次)。
    • 您能否解释一下视图在其生命周期中何时会获得其边界值?
    • @YongliangHe - 当视图第一次布局时(以及每次更新),layoutSubviews 被调用。 bounds 在调用 init 时是不可靠的,但在调用 layoutSubviews 时是可靠的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-18
    • 2015-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多