ios: 关于 init(frame:) 和 init?(coder:) 的问题

ios: questions regarding init(frame:) and init?(coder:)

Apple 的教程将 init(frame:)init?(coder:) 之间的区别描述为

You typically create a view in one of two ways: by programatically initializing the view, or by allowing the view to be loaded by the storyboard. There’s a corresponding initializer for each approach: init(frame:) for programatically initializing the view and init?(coder:) for loading the view from the storyboard. You will need to implement both of these methods in your custom control. While designing the app, Interface Builder programatically instantiates the view when you add it to the canvas. At runtime, your app loads the view from the storyboard.

我对 "programtically initializing" 和 "loaded by the storyboard" 的描述感到很困惑。假设我有一个名为 MyViewUIView 的子 class,"programtically initialization" 是否意味着我编写代码以将 MyView 的实例添加到某个地方,例如:

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

init?(coder:)Main.storyboard 时被调用 我从对象库中拖出一个 UIView 然后在身份检查器中我设置它class 到 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 并将其 class 设置为 RecordView)

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

其次,关于您的问题,我建议您从 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)
    }
}

I feel so confused by the description "programtically initializing" and "loaded by the storyboard".

基于对象的编程是关于 classes 和实例的。您需要创建一个 class 的实例。对于 Xcode,有两种截然不同的方法来获取 class 的实例:

  • 您的代码创建实例

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

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

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