Как загрузить файл XIB в качестве пользовательского класса? - PullRequest
0 голосов
/ 20 июня 2019

Я пытаюсь использовать файл xib для создания повторно используемого компонента (несколько раз в одном и том же представлении), и все было в порядке, пока я не захотел обновить некоторые элементы управления из свойства @IBInspectable.Я обнаружил, что @IBOutlet не установлены в этот момент, поэтому я выполнил поиск и нашел что-то

http://justabeech.com/2014/07/27/xcode-6-live-rendering-from-nib/

Он сохраняет загруженный вид в proxyView, чтобы вы моглииспользуйте его в @IBInspectable.К сожалению, этот код немного стар и не работает как есть.Но моя проблема в том, что когда я пытаюсь загрузить перо как класс, оно не работает.Это работает, только если я загружаю его как UIView.

Эта строка не работает как эта

return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? ValidationTextField

Она работает только тогда, когда это так

return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? UIView

Я думаю,проблема в том, что владелец файла xib помечен как ValidationTextField, но основной вид - UIView.Поэтому, когда вы загружаете перо, оно приносит UIView, которое, очевидно, не имеет пользовательских свойств или выходов.

Во многих примерах о загрузке файлов XIB говорится, что класс должен находиться в владельце файла.Так что я не знаю, как получить пользовательский класс, используя это.

import UIKit

@IBDesignable class ValidationTextField: UIView {
    @IBOutlet var lblError: UILabel!
    @IBOutlet var txtField: XTextField!
    @IBOutlet var imgWarning: UIImageView!
    private var proxyView: ValidationTextField?

    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    required init(coder: NSCoder) {
        super.init(coder: coder)!
    }
    override func awakeFromNib() {
        super.awakeFromNib()
        xibSetup()
    }
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        xibSetup()
        self.proxyView?.prepareForInterfaceBuilder()
    }
    func xibSetup() {
        guard let view = loadNib() else { return }
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(view)
        // Saving the view in a variable
        self.proxyView = view
    }
    func loadNib() -> ValidationTextField? {
        let bundle = Bundle(for: type(of: self))
        return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? ValidationTextField
    }
    @IBInspectable var fontSize: CGFloat = 14.0 {
        didSet {
            let font = UIFont(name: "System", size: self.fontSize)
            self.proxyView!.txtField.font = font
            self.proxyView!.lblError.font = font
        }
    }
}

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

Запуск этого кода завершится ошибкой в ​​этой строке

guard let view = loadNib() else { return }

Я думаю, что это можетНе конвертируйте UIView в класс, чтобы он возвращал ноль, а затем завершал работу.

Моя цель - создать компонент многократного использования, который можно много раз разместить в одном контроллере.И сможете увидеть его дизайн в раскадровке.

1 Ответ

1 голос
/ 20 июня 2019

Переместите xibSetup() к вашим инициализаторам. awakeFromNib вызывается слишком поздно и не будет вызываться, если представление создается программно. Нет необходимости звонить по номеру prepareForInterfaceBuilder.

Короче говоря, это можно обобщить до:

open class NibLoadableView: UIView {
    public override init(frame: CGRect) {
        super.init(frame: frame)
        loadNibContentView()
        commonInit()
    }

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

    public func commonInit() {
       // to be overriden
    }
}

public extension UIView {
    // @objc makes it possible to override the property
    @objc
    var nibBundle: Bundle {
        return Bundle(for: type(of: self))
    }

    // @objc makes it possible to override the property
    @objc
    var nibName: String {
        return String(describing: type(of: self))
    }

    @discardableResult
    func loadNibContentView() -> UIView? {
        guard
            // note that owner = self !!!
            let views = nibBundle.loadNibNamed(nibName, owner: self, options: nil),
            let contentView = views.first as? UIView
        else {
            return nil
        }

        addSubview(contentView)
        contentView.translatesAutoresizingMaskIntoConstraints = true
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = self.bounds

        return contentView
    }
}

Обратите внимание, что представление, загружающее перо, должно быть владельцем представления.

Тогда ваш класс станет:

@IBDesignable
class ValidationTextField: NibLoadableView {
    @IBOutlet var lblError: UILabel!
    @IBOutlet var txtField: XTextField!
    @IBOutlet var imgWarning: UIImageView!

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

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

    @IBInspectable var fontSize: CGFloat = 14.0 {
        didSet {
            updateFont()
        }
    }

    private func updateFont() {
       let font = UIFont.systemFont(ofSize: fontSize)
       txtField.font = font
       lblError.font = font
    }
}

Полагаю, что вся идея прокси-объекта возникла из-за неправильного использования владельца Nib. С прокси-объектом иерархия должна выглядеть примерно так:

ValidationTextField
    -> ValidationTextField (root view of the nib)
          -> txtField, lblError, imgWarning

что не имеет особого смысла. То, что мы действительно хотим, это:

ValidationTextField (nib owner)
    -> UIView (root view of the nib)
          -> txtField, lblError, imgWarning
...