Apple's tutorial describes the difference between init(frame:)
and init?(coder:)
as
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 andinit?(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.
I feel so confused by the description "programtically initializing" and "loaded by the storyboard". Say I have a subclass of UIView
called MyView
, does "programtically initialization" mean I write code to add an instance of MyView
to somewhere like:
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyView() // init(frame:) get invoked here??
}
while init?(coder:)
get called when in Main.storyboard
I drag a UIView
from object library and then in the identity inspector I set its class to MyView
?
Besides, in my xcode project, these two methods end up with different layout for simulator and Main.storyboard
with the same code:
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()
}
}
}
Why do they behave differently? (I drag a UIView
from object library and set its class to RecordView)
First your delineation between
init?(coder:)
andinit(frame:)
is basically correct. The former is used when instantiating a storyboard scene when you actually run the app, but the latter is used when you programmatically instantiate it with eitherlet foo = RecordView()
orlet bar = RecordView(frame: ...)
. Also,init(frame:)
is used when previewing@IBDesignable
views in IB.Second, regarding your problem, I'd suggest you remove the setting of the
center
offillView
(as well as the corner radius stuff) fromsetupFillView
. The problem is that wheninit
is called, you generally don't know whatbounds
will eventually be. You should set thecenter
inlayoutSubviews
, which is called every time the view changes size.