Подкласс Swift UIView

95

Я хочу UIViewсоздать подкласс и показать вид входа в систему. Я создал это в Objective-C, но теперь хочу перенести его на Swift. Я не использую раскадровки, поэтому весь свой UI создаю в коде.

Но первая проблема в том, что я должен реализовать initWithCoder. Я дал ему реализацию по умолчанию, так как она не будет вызываться. Теперь, когда я запускаю программу, она вылетает, потому что я тоже должен ее реализовать initWithFrame. Вот что получилось:

override init() {
    super.init()
    println("Default init")
}

override init(frame: CGRect) {
    super.init(frame: frame)
    println("Frame init")
}

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    println("Coder init")
}

Мой вопрос в том, где мне создать текстовое поле и т. Д., И если я никогда не использую фрейм и кодер, как я могу «скрыть» это?

Haagenti
источник

Ответы:

175

Я обычно делаю что-то подобное, это немного многословно.

class MyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        addBehavior()
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("This class does not support NSCoding")
    }

    func addBehavior() {
        print("Add all the behavior here")
    }
}



let u = MyView(frame: CGRect.zero)
let v = MyView()

(Изменить: я отредактировал свой ответ, чтобы связь между инициализаторами была более ясной)

Раймонд
источник
4
Но addBehavior вызывается дважды, поскольку вызывается initFrame и init. Если вы запустите мой код, будет напечатан первый кадр инициализации, а затем инициализация по умолчанию
Haagenti
6
Хороший материал, спасибо. Вместо использования CGRectZeroя считаю, что рекомендуется использовать CGRect.zeroRect.
Мистер Роджерс
57
Этот инициализатор безумно сложен.
Ян Уорбертон,
3
Есть ли способ сделать это для Auto Layout? Рамка так устарела.
devios1 01
8
Это не полный ответ. UIView поддерживает initWithCoding. Любое представление, загруженное из пера или раскадровки, вызовет метод initWithCoding и завершится аварийно.
Iain Delaney
17

Это проще.

override init (frame : CGRect) {
    super.init(frame : frame)
    // Do what you want.
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}
Джефф Гу Канг
источник
10

Пример настраиваемого подкласса UIView

Обычно я создаю приложения для iOS без раскадровок и перьев. Я поделюсь некоторыми методами, которые я научился отвечать на ваши вопросы.

 

Скрытие нежелательных initметодов

Мое первое предложение - объявить базу UIViewдля скрытия нежелательных инициализаторов. Я подробно обсуждал этот подход в своем ответе на вопрос «Как скрыть инициализаторы, специфичные для раскадровки и перьев, в подклассах пользовательского интерфейса» . Примечание. Этот подход предполагает, что вы не будете использовать BaseViewего потомки в раскадровках или перьях, поскольку это намеренно приведет к сбою приложения.

class BaseView: UIView {

    // This initializer hides init(frame:) from subclasses
    init() {
        super.init(frame: CGRect.zero)
    }

    // This attribute hides `init(coder:)` from subclasses
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

Ваш собственный подкласс UIView должен наследовать от BaseView. Он должен вызывать super.init () в своем инициализаторе. Это не нужно реализовывать init(coder:). Это показано в примере ниже.

 

Добавление UITextField

Я создаю сохраненные свойства для подпредставлений, на которые есть ссылки вне initметода. Я обычно делал это для UITextField. Я предпочитаю , чтобы создать экземпляр подвиды в декларации имущества подвида , как это: let textField = UITextField().

UITextField не будет отображаться, если вы не добавите его в список подпредставлений пользовательского представления, вызвав addSubview(_:). Это показано в примере ниже.

 

Программный макет без автоматического макета

UITextField не будет отображаться, пока вы не установите его размер и положение. Я часто делаю макет в коде (не используя Auto Layout) в методе layoutSubviews . layoutSubviews()вызывается изначально и всякий раз, когда происходит событие изменения размера. Это позволяет настраивать макет в зависимости от размера CustomView. Например, если CustomView отображается во всю ширину на iPhone и iPad разных размеров и регулируется для поворота, ему необходимо приспособиться к множеству исходных размеров и динамически изменять размер.

Вы можете обратиться к frame.heightи frame.widthвнутри, layoutSubviews()чтобы получить размеры CustomView для справки. Это показано в примере ниже.

 

Пример подкласса UIView

Пользовательский подкласс UIView, содержащий UITextField, который не нужно реализовывать init?(coder:).

class CustomView: BaseView {

    let textField = UITextField()

    override init() {
        super.init()

        // configure and add textField as subview
        textField.placeholder = "placeholder text"
        textField.font = UIFont.systemFont(ofSize: 12)
        addSubview(textField)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // Set textField size and position
        textField.frame.size = CGSize(width: frame.width - 20, height: 30)
        textField.frame.origin = CGPoint(x: 10, y: 10)
    }
}

 

Программный макет с автоматическим макетом

Вы также можете реализовать макет с помощью автоматического макета в коде. Поскольку я делаю это не часто, не буду приводить пример. Вы можете найти примеры реализации Auto Layout в коде на Stack Overflow и в других местах в Интернете.

 

Программные макеты

Существуют платформы с открытым исходным кодом, которые реализуют макет в коде. Меня интересует LayoutKit, но я не пробовал . Его написала команда разработчиков LinkedIn. Из репозитория Github: «LinkedIn создал LayoutKit, потому что мы обнаружили, что Auto Layout недостаточно эффективен для сложных иерархий представлений в прокручиваемых представлениях».

 

Зачем ставить fatalErrorвinit(coder:)

При создании подклассов UIView, которые никогда не будут использоваться в раскадровке или пере, вы можете ввести инициализаторы с другими параметрами и требованиями инициализации, которые не могут быть вызваны init(coder:)методом. Если вы не завершили init (coder :) с ошибкой fatalError, это может привести к очень запутанным проблемам в будущем, если вы случайно будете использовать его в раскадровке / пере. FatalError подтверждает эти намерения.

required init?(coder aDecoder: NSCoder) {
    fatalError("NSCoding not supported")
}

Если вы хотите запустить какой-либо код при создании подкласса независимо от того, создан ли он в коде или раскадровке / пере, вы можете сделать что-то вроде следующего (на основе ответа Джеффа Гу Канга )

class CustomView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        initCommon()
    }

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

    func initCommon() {
        // Your custom initialization code
    }
}
Мобильный Дэн
источник
а также? добавив, fatalErrorвы запрещаете запускать это представление с помощью файлов xib
Вячеслав Герчиков
@VyachaslavGerchicov, в моем ответе говорится о предположении, что вы не используете xibs или раскадровки, как принятый ответ и вопрос. В вопросе указано: «Я не использую раскадровки, поэтому я создаю весь свой пользовательский интерфейс в коде».
Mobile Dan
в следующий раз, когда вы напишете fatalErrorвнутри метода dealloc и сообщите нам, что он не работает, потому что этот класс должен быть одноэлементным. Если вы предпочитаете создавать элементы пользовательского интерфейса в коде, не следует вручную запрещать все остальные способы. Наконец, вопрос в том, как создать «программно без раскадровки», но xibs / nibs не упоминаются. В моем случае мне нужно создать массив ячеек с помощью + xib программно и передать их, DropDownMenuKitи этот способ не работает, потому что автор этой библиотеки также запрещает xibs.
Вячеслав Герчиков
@VyachaslavGerchicov Похоже, что ответ Джеффа Гу Канга - это то, что вы ищете, поскольку он поддерживает раскадровки / Xibs
Mobile Dan
1
@VyachaslavGerchicov В вопросе также говорилось: «А если я никогда не использую фрейм и кодер, как я могу« скрыть »это?» При создании подклассов UIView, которые никогда не будут использоваться в Xib / Storyboard, вы можете ввести инициализаторы с другими параметрами, которые не могут быть вызваны методом init (coder :). Если вы не завершили init (coder :) с ошибкой fatalError, это может привести к очень запутанным проблемам в дальнейшем, если вы случайно используете его в Xib / Storyboard. Фатальная ошибка заявляет об этих намерениях. Как видно из принятого ответа, это намеренная и распространенная практика.
Mobile Dan
4

Важно, чтобы ваш UIView можно было создать с помощью конструктора интерфейсов / раскадровок или из кода. Я считаю полезным иметь setupспособ уменьшить дублирование любого установочного кода. например

class RedView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        setup()
    }

    func setup () {
        backgroundColor = .red
    }
}
Джейсон Мур
источник
4

Swift 4.0, если вы хотите использовать представление из файла xib, то это для вас. Я создал класс CustomCalloutView Подкласс UIView. Я создал файл xib, и в IB просто выберите владельца файла, затем выберите инспектор атрибутов, установите имя класса на CustomCalloutView, затем создайте розетку в своем классе.

    import UIKit
    class CustomCalloutView: UIView {

        @IBOutlet var viewCallout: UIView! // This is main view

        @IBOutlet weak var btnCall: UIButton! // subview of viewCallout
        @IBOutlet weak var btnDirection: UIButton! // subview of viewCallout
        @IBOutlet weak var btnFavourite: UIButton! // subview of viewCallout 

       // let nibName = "CustomCalloutView" this is name of xib file

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

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

        func nibSetup() {
            Bundle.main.loadNibNamed(String(describing: CustomCalloutView.self), owner: self, options: nil)
            guard let contentView = viewCallout else { return } // adding main view 
            contentView.frame = self.bounds //Comment this line it take default frame of nib view
           // custom your view properties here
            self.addSubview(contentView)
        }
    }

// Теперь добавляем

    let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
    self.view.addSubview(viewCustom)
Гурджиндер Сингх
источник
-1

Вот пример того, как я обычно создаю свои подклассы (UIView). У меня есть содержимое в виде переменных, поэтому к ним можно будет получить доступ и настроить, возможно, позже в каком-то другом классе. Я также показал, как использую автоматический макет и добавляю контент.

Например, в ViewController я инициализировал это представление в ViewDidLoad (), поскольку оно вызывается только один раз, когда представление отображается. Затем я использую эти функции, которые создаю здесь, addContentToView()а затем activateConstraints()создаю контент и устанавливаю ограничения. Если позже в ViewController я хочу, чтобы цвет кнопки был красным, я просто делаю это в этой конкретной функции в этом ViewController. Что-то типа:func tweaksome(){ self.customView.someButton.color = UIColor.red}

class SomeView: UIView {


var leading: NSLayoutConstraint!
var trailing: NSLayoutConstraint!
var bottom: NSLayoutConstraint!
var height: NSLayoutConstraint!


var someButton: UIButton = {
    var btn: UIButton = UIButton(type: UIButtonType.system)
    btn.setImage(UIImage(named: "someImage"), for: .normal)
    btn.translatesAutoresizingMaskIntoConstraints = false
    return btn
}()

var btnLeading: NSLayoutConstraint!
var btnBottom: NSLayoutConstraint!
var btnTop: NSLayoutConstraint!
var btnWidth: NSLayoutConstraint!

var textfield: UITextField = {
    var tf: UITextField = UITextField()
    tf.adjustsFontSizeToFitWidth = true
    tf.placeholder = "Cool placeholder"
    tf.translatesAutoresizingMaskIntoConstraints = false
    tf.backgroundColor = UIColor.white
    tf.textColor = UIColor.black
    return tf
}()
var txtfieldLeading: NSLayoutConstraint!
var txtfieldTrailing: NSLayoutConstraint!
var txtfieldCenterY: NSLayoutConstraint!

override init(frame: CGRect){
    super.init(frame: frame)
    self.translatesAutoresizingMaskIntoConstraints = false
}

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



/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code

}
*/
func activateConstraints(){
    NSLayoutConstraint.activate([self.btnLeading, self.btnBottom, self.btnTop, self.btnWidth])
    NSLayoutConstraint.activate([self.txtfieldCenterY, self.txtfieldLeading, self.txtfieldTrailing])
}

func addContentToView(){
    //setting the sizes
    self.addSubview(self.userLocationBtn)

    self.btnLeading = NSLayoutConstraint(
        item: someButton,
        attribute: .leading,
        relatedBy: .equal,
        toItem: self,
        attribute: .leading,
        multiplier: 1.0,
        constant: 5.0)
    self.btnBottom = NSLayoutConstraint(
        item: someButton,
        attribute: .bottom,
        relatedBy: .equal,
        toItem: self,
        attribute: .bottom,
        multiplier: 1.0,
        constant: 0.0)
    self.btnTop = NSLayoutConstraint(
        item: someButton,
        attribute: .top,
        relatedBy: .equal,
        toItem: self,
        attribute: .top,
        multiplier: 1.0,
        constant: 0.0)
    self.btnWidth = NSLayoutConstraint(
        item: someButton,
        attribute: .width,
        relatedBy: .equal,
        toItem: self,
        attribute: .height,
        multiplier: 1.0,
        constant: 0.0)        


    self.addSubview(self.textfield)
    self.txtfieldLeading = NSLayoutConstraint(
        item: self.textfield,
        attribute: .leading,
        relatedBy: .equal,
        toItem: someButton,
        attribute: .trailing,
        multiplier: 1.0,
        constant: 5)
    self.txtfieldTrailing = NSLayoutConstraint(
        item: self.textfield,
        attribute: .trailing,
        relatedBy: .equal,
        toItem: self.doneButton,
        attribute: .leading,
        multiplier: 1.0,
        constant: -5)
    self.txtfieldCenterY = NSLayoutConstraint(
        item: self.textfield,
        attribute: .centerY,
        relatedBy: .equal,
        toItem: self,
        attribute: .centerY,
        multiplier: 1.0,
        constant: 0.0)
}
}
Starlord
источник