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, и вы хотите переопределить инициализатор, вы должны подумать о том, какой инициализатор будет вызываться (в зависимости от того, как будет создан экземпляр представления).

Другие вопросы по тегам