Инициализация 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
}
}
Подклассы объектов это все о знании жизненного цикла. Печатайте на консоли в каждом методе жизненного цикла, чтобы увидеть, когда они запускаются.