Как мне написать собственный init для подкласса UIView в Swift?

125

Скажем, я хочу initсоздать UIViewподкласс с a Stringи an Int.

Как мне сделать это в Swift, если я просто создаю подклассы UIView? Если я просто init()создаю настраиваемую функцию, но параметрами являются String и Int, он сообщает мне, что «super.init () не вызывается перед возвратом из инициализатора».

И если я звоню, super.init()мне говорят, что я должен использовать назначенный инициализатор. Что мне там использовать? Каркасная версия? Версия кодера? Обе? Зачем?

Дуг Смит
источник

Ответы:

207

init(frame:)Версия инициализатор по умолчанию. Вы должны вызывать его только после инициализации переменных вашего экземпляра. Если это представление воссоздается из Nib, ваш пользовательский инициализатор не будет вызываться, а вместо этого init?(coder:)будет вызываться версия. Поскольку Swift теперь требует реализации необходимого init?(coder:), я обновил приведенный ниже пример и изменил letобъявления переменных на varи optional. В этом случае вы должны инициализировать их awakeFromNib()позже или позже.

class TestView : UIView {
    var s: String?
    var i: Int?
    init(s: String, i: Int) {
        self.s = s
        self.i = i
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
Вольф МакНелли
источник
5
Тогда непременно сделайте их var. Но лучшая практика по умолчанию в Swift - объявлять переменные, letесли нет причины для их объявления var. Следовательно, в моем примере кода выше не было такой причины let.
Wolf McNally
2
Этот код не компилируется. Вам необходимо реализовать требуемый инициализатор init(coder:).
Decade Moon
3
интересно, как это скомпилировано много лет назад. В настоящее время он жалуется под init (coder :), что «Свойство self.s не инициализируется при вызове super.init»
mafiOSo
Исправленный пример для Swift 3.1. Компилируется под игровую площадку, импортирующую UIKit.
Вольф МакНалли
1
@LightNight Я сделал sи iНеобязательно, чтобы здесь было проще. Если бы они не были необязательными, их также нужно было бы инициализировать в требуемом инициализаторе. Сделать их необязательными означает, что они будут существовать nilпри super.init()вызове. Если бы они не были необязательными, их действительно нужно было бы назначить перед вызовом super.init ().
Wolf McNally
33

Я создаю общий init для обозначенных и требуемых. Для удобства я передаю его init(frame:)с нулевым кадром.

Наличие нулевого кадра не является проблемой, потому что обычно представление находится внутри представления ViewController; ваше настраиваемое представление получит хороший и безопасный шанс разместить свои подвиды, когда его супервизор вызывает layoutSubviews()или updateConstraints(). Эти две функции вызываются системой рекурсивно по всей иерархии представлений. Вы можете использовать либо, updateContstraints()либо layoutSubviews(). updateContstraints()сначала вызывается, затем layoutSubviews(). В updateConstraints()Обязательно позовите супер последний . Во- первыхlayoutSubviews() , позвони супер .

Вот что я делаю:

@IBDesignable
class MyView: UIView {

      convenience init(args: Whatever) {
          self.init(frame: CGRect.zero)
          //assign custom vars
      }

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

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

      override func prepareForInterfaceBuilder() {
           super.prepareForInterfaceBuilder()
           commonInit()
      }

      private func commonInit() {
           //custom initialization
      }

      override func updateConstraints() {
           //set subview constraints here
           super.updateConstraints()
      }

      override func layoutSubviews() {
           super.layoutSubviews()
           //manually set subview frames here
      }

}
MH175
источник
1
Это не должно работать: использование self в вызове метода commonInit перед инициализацией super.init self
surfrider
1
Инициализировать настраиваемые аргументы после вызова self.init. Обновил свой ответ.
MH175,
1
Но что, если вы хотите инициализировать некоторые свойства в commonInitметоде, но superв этом случае вы не можете разместить его после, потому что вы должны инициализировать все свойства ПЕРЕД superвызовом. Lol это похоже на мертвую петлю.
surfrider
1
Вот так часто бывает инициализация Swift: ищите «двухфазная инициализация». Вы можете использовать неявно развернутые опции, но я не рекомендую этого делать. Ваша архитектура, особенно при работе с представлениями, должна инициализировать все локальные свойства. Я использовал этот метод commonInit () для сотен просмотров. Это работает
MH175
17

Вот как я это делаю на iOS 9 в Swift:

import UIKit

class CustomView : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        //for debug validation
        self.backgroundColor = UIColor.blueColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}

Вот полный проект с примером:

J-Dizzle
источник
2
это будет использовать весь дисплей для просмотра
RaptoX
1
Ага! Если вы заинтересованы в частичном дополнительном обзоре, дайте мне знать, и я тоже
опубликую
1
Мне больше всего нравится этот ответ, потому что наличие fatalError означает, что мне не нужно помещать какой-либо код в требуемый файл init.
Картер Медлин,
1
@ J-Dizzle, я бы хотел увидеть решение для частичных просмотров.
Ари Лаценски,
разве ваш ответ не противоречит принятому ответу? Я имею в виду, что вы делаете настройку после super.init, но он сказал, что это должно быть сделано до super.init...
Дорогая,
11

Вот как я делаю Subview на iOS в Swift -

class CustomSubview : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        let windowHeight : CGFloat = 150;
        let windowWidth  : CGFloat = 360;

        self.backgroundColor = UIColor.whiteColor();
        self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
        self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);

        //for debug validation
        self.backgroundColor = UIColor.grayColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}
J-Dizzle
источник
4
Хорошее использование вызова fatalError (). Мне приходилось использовать дополнительные опции, чтобы отключить предупреждения от инициализатора, который даже не использовался. Это заткнись! Спасибо.
Майк Кричли