ios: вопросы относительно init(frame:) и init?(кодер:)
Учебник Apple описывает разницу между init(frame:)
а также init?(coder:)
как
Обычно вы создаете представление одним из двух способов: путем программной инициализации представления или путем предоставления возможности загрузки представления раскадровкой. Для каждого подхода есть соответствующий инициализатор:
init(frame:)
для программной инициализации представления иinit?(coder:)
для загрузки вида из раскадровки. Вам нужно будет реализовать оба этих метода в вашем пользовательском элементе управления. При разработке приложения Интерфейсный Разработчик программно создает экземпляр представления, когда вы добавляете его на холст. Во время выполнения ваше приложение загружает представление из раскадровки.
Я так растерялся из-за описания "программная инициализация" и "загрузка раскадровкой". Скажем, у меня есть подкласс UIView
называется MyView
, означает ли "программная инициализация", что я пишу код для добавления экземпляра MyView
где-то вроде:
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyView() // init(frame:) get invoked here??
}
в то время как init?(coder:)
позвонить, когда в Main.storyboard
Я тащу 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)
2 ответа
Сначала ваше разграничение между init?(coder:)
а также init(frame:)
в принципе правильно. Первый используется при создании экземпляра сцены раскадровки, когда вы фактически запускаете приложение, но последний используется, когда вы программно создаете экземпляр с помощью либо let foo = RecordView()
или же let bar = RecordView(frame: ...)
, Также, init(frame:)
используется при предварительном просмотре @IBDesignable
просмотров в ИБ.
Во-вторых, что касается вашей проблемы, я бы посоветовал вам удалить настройку center
из fillView
(а также материал углового радиуса) из setupFillView
, Проблема в том, что когда init
называется, вы вообще не знаете, что bounds
в конечном итоге будет. Вы должны установить center
в layoutSubviews
, который вызывается каждый раз, когда вид меняет размер.
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)
}
}
Я так растерялся из-за описания "программная инициализация" и "загрузка раскадровкой".
Объектно-ориентированное программирование - это классы и экземпляры. Вам нужно сделать экземпляр класса. В XCode есть два совершенно разных способа получить экземпляр класса:
ваш код создает экземпляр
вы загружаете перо (такое представление контроллера представления в раскадровке), и процесс загрузки кончика создает экземпляр и передает его вам
Инициализатор, который вызывается в этих двух обстоятельствах, отличается. Если ваш код создает экземпляр UIView, назначенный инициализатор, который вы должны вызвать init(frame:)
, Но если перо создает представление, назначенный инициализатор, который вызывает процесс загрузки кончика, init(coder:)
,
Поэтому, если у вас есть подкласс UIView, и вы хотите переопределить инициализатор, вы должны подумать о том, какой инициализатор будет вызываться (в зависимости от того, как будет создан экземпляр представления).