Швидкий підклас UIView


95

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

Але перша проблема полягає в тому, що я повинен реалізувати 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")
}

Моє питання полягає в тому, де я повинен створити своє текстове поле тощо ... і якщо я ніколи не реалізую фрейм і кодер, як я можу "приховати" це?

Відповіді:


174

Зазвичай я роблю щось подібне, трохи багатослівно.

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. Якщо ви запускаєте мій код, друкується перший кадр init, тоді за замовчуванням init
Haagenti

6
Гарні речі, дякую. Замість використання CGRectZeroя вважаю, що рекомендується використовувати CGRect.zeroRect.
Містер Роджерс

57
Цей матеріал ініціатора є божевільно складним.
Ян Уорбертон,

3
Чи можна це зробити для автоматичного макетування? Рамка настільки застаріла.
devios1

8
Це не повна відповідь. UIView підтримує initWithCoding. Будь-який вигляд, завантажений з нибу чи дошки, викличе метод initWithCoding і завершиться збоєм.
Іен Делані

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для приховування небажаних ініціалізаторів. Я детально обговорив цей підхід у своїй відповіді на тему "Як приховати розкадрування та конкретні ініціалізатори Nib в підкласах інтерфейсу користувача" . Примітка. Цей підхід передбачає, що ви не будете використовувати 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().

Поле UITextFi не буде видимим, якщо ви не додасте його до списку підвидного перегляду спеціального вигляду, зателефонувавши addSubview(_:). Це продемонстровано на прикладі нижче.

 

Програматичне макетування без автоматичного макетування

UITextField не буде видимим, якщо ви не встановите його розмір та положення. Я часто роблю макет у коді (не використовуючи автоматичний макет) в межах методу 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, оскільки ми виявили, що автоматичне макетування недостатньо ефективне для складних ієрархій подань у прокручуваних поданнях."

 

Навіщо ставити fatalErrorвinit(coder:)

Створюючи підкласи UIView, які ніколи не будуть використовуватися в дошці розкадрування або вказівці, ви можете ввести ініціалізатори з різними параметрами та вимогами ініціалізації, які неможливо викликати init(coder:)методом. Якщо ви не вийшли з ладу init (coder :) з a 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 або дошки, як прийнята відповідь і питання. У запитанні зазначається: "Я не використовую розклади, тому я створюю весь свій інтерфейс у коді".
Мобільний

наступного разу, коли ви fatalErrorнапишете всередині методу dealloc та скажете нам, що він не працює, тому що цей клас повинен бути сингтом. Якщо ви віддаєте перевагу створенню елементів інтерфейсу в коді, тоді вам не слід забороняти вручну всі інші способи. Нарешті, питання полягає у тому, як створити "програмно без дощок", але xibs / nibs не згадуються. У моєму випадку мені потрібно створити масив комірок із програмно + xib і передати їх, DropDownMenuKitі цей спосіб не працює, оскільки автор цієї бібліотеки теж забороняє xibs.
Вячаслав Герчичев

@VyachaslavGerchicov Схоже, відповідь Джеффа Гу Канга - це те, що ви шукаєте, оскільки вона вміщує Раскадровки / Xibs
Мобільний Дан

1
@VyachaslavGerchicov У запитанні також говорилося: "і якщо я ніколи не впроваджую фрейм і кодер, як я можу" приховати "це?" Створюючи підкласи UIView, які ніколи не будуть використовуватися в Xib / Storyboard, ви можете ввести ініціалізатори з різними параметрами, які неможливо викликати методом init (coder :). Якщо ви не провалили init (coder :) з fatalError, це може призвести до дуже заплутаних проблем, якщо випадково використовуватись у Xib / Storyboard. Фатальний Ерор заявляє про ці наміри. Це навмисна і поширена практика, як видно з прийнятої відповіді.
Мобільний

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)
}
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.