Инициализация UIControl с использованием программных ограничений VFL (iOS Swift)

Я работаю над несколькими пользовательскими объектами UIControl, которые нужно создавать программно. Обычно при использовании AutoLayout я просто передаю CGRect. При использовании языка визуального формата:

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

в классе UIControl не вызывается методом ограничений в ViewDidLoad(), и CAShapeLayers не обновляются. Пользовательские кнопка / ползунок / ручка и т. Д. Работают, но невидимы, и я могу обновить форму, как только к ней прикоснутся. Не очень полезно. Как сделать так, чтобы пользовательский UIControl с использованием VFL появлялся на экране сразу после загрузки представления? Вот что я хотел бы сделать:

import UIKit

class ViewController: UIViewController {

   let knob = Knob()
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.addSubview(knob)
        knob.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "slider": knob
        ]

        let formatV = "V:|-[knob(300)]-|"
        let formatH = "H:|-[knob(300)]-|"

        let VC = NSLayoutConstraint.constraints(withVisualFormat: formatV, options: NSLayoutFormatOptions(), metrics: nil, views: views)
        let HC = NSLayoutConstraint.constraints(withVisualFormat: formatH, options: NSLayoutFormatOptions(), metrics: nil, views: views)

        self.view.addConstraints(VC)
        self.view.addConstraints(HC)
        knob.addTarget(self, action: #selector(knobRotated(_:)), for: .touchDown)
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @objc func knobRotated(_ sender: Knob) {
        print(sender.value)
    }
}

Или каков наилучший подход для создания UIControl, который работает как с AutoLayout, так и с VFL?

1 ответ

Решение

UIControl это подкласс UIView так относитесь к ним одинаково с точки зрения их внешнего вида. Вы можете визуализировать управление, используя CALayer или же UIView; Я использую их оба в зависимости от того, насколько вовлечен объект. Когда я делаю пользовательские переключатели, я использую исключительно слои, а когда я делаю кнопки подклассов, я использую исключительно виды, например.

Следующий пример простой кнопки демонстрирует общую концепцию.

class MenuButton: UIControl {


    private let button = UIView()
    private let buttonLabel = UILabel()
    private let buttonCaption = UILabel()
    var menuName = ""


    // MARK: Lifecycle


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

        config()
        addButton()
        addButtonLabel()
        addButtonCaption()

    }


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


    override func updateConstraints() {
        super.updateConstraints()

        button.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        button.topAnchor.constraint(equalTo: topAnchor).isActive = true
        button.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
        button.heightAnchor.constraint(equalToConstant: 48).isActive = true

        buttonLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        buttonLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
        buttonLabel.widthAnchor.constraint(equalTo: widthAnchor, constant: -48).isActive = true
        buttonLabel.heightAnchor.constraint(equalToConstant: 48).isActive = true

        // if you have a label with varying height, for example,
        // adding the text where the label was created will not allow
        // autolayout to account for it so you must add it later in
        // the object's lifecycle, like here
        buttonCaption.text = menuName

        buttonCaption.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        buttonCaption.topAnchor.constraint(equalTo: button.bottomAnchor, constant: 4).isActive = true
        buttonCaption.widthAnchor.constraint(equalTo: widthAnchor, constant: -16).isActive = true
        buttonCaption.sizeToFit()

        // i don't know if this is necessary but I always do it, but
        // the key to making autolayout work for you (i.e. scroll view
        // content size, table view cells, collection view cells) is 
        // to chain constraints together so that the top-most view is
        // touching the top of its container and the bottom-most view
        // is touching the bottom of its container and autolayout will
        // (or should) always auto size the container
        buttonCaption.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

        // or replace with your visually-formatted autolayout constraints

    }


    // layout subviews
    override func layoutSubviews() {
        super.layoutSubviews()

        setButtonLabelText(selectedOptions: selectedOptions)

    }


    // MARK: Methods


    private func config() {

        isOpaque = false
        isExclusiveTouch = true

    }


    private func addButton() {

        button.isUserInteractionEnabled = false
        button.backgroundColor = UIColor.blue
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        addSubview(button)

    }


    private func addButtonLabel() {

        buttonLabel.numberOfLines = 1
        buttonLabel.lineBreakMode = .byTruncatingTail
        buttonLabel.font = UIFont.menuLabel
        buttonLabel.minimumScaleFactor = (20/23)
        buttonLabel.textColor = UIColor.menuLabel
        buttonLabel.textAlignment = .center
        buttonLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(buttonLabel)

    }


    private func addButtonCaption() {

        buttonCaption.numberOfLines = 0
        buttonCaption.font = UIFont.displayBold(size: 16)
        buttonCaption.textColor = UIColor.text.withAlphaComponent(0.25)
        buttonCaption.textAlignment = .left
        buttonCaption.translatesAutoresizingMaskIntoConstraints = false
        addSubview(buttonCaption)

    }


    func setButtonLabelText(selectedOptions: [Int]) {

        // add logic

    }


}

Когда я подкласс UIControl для переключения, например, там меня не волнует автопоставка, поэтому я буду использовать исключительно слои и объявить их размер явно:

override open var intrinsicContentSize: CGSize {
    return CGSize(width: 60, height: 30)
}

А потом, после того как представление в основном готово, используйте layoutSubviews установить состояние коммутатора:

override open func layoutSubviews() {
    super.layoutSubviews()

    if isOn {

        thumbLayer.position = onPosition
        thumbLayer.backgroundColor = onColor

    } else {

        thumbLayer.position = offPosition
        thumbLayer.backgroundColor = offColor

    }

}

Подклассы объектов это все о знании жизненного цикла. Печатайте на консоли в каждом методе жизненного цикла, чтобы увидеть, когда они запускаются.

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