Как: Self-Sizing Custom-View от XIB с представлением StackView в iOS
недавно я снова начал погружаться в разработку iOS с помощью Swift и теперь изучаю пользовательский UIControl. Для компоновки и гибкости это настраиваемое представление построено с использованием StackView.
чего я хочу добиться ...
... в основном просто иметь ту же функциональность с StackView, что и обычно, когда я перетаскиваю его прямо в раскадровку - где он изменяется, чтобы не было проблем. Это по существу то же самое, что и самоизмерение tableview ячейки.
витрина
я могу уменьшить свой CustomView до простого примера: это StackView с тремя метками в нем, Alignment
значение Center
, Distribution
Equal Centering
. StackView будет закреплен слева, справа и сверху направляющих макета (или трейлинг, ведущий и верхний). Это я могу легко воссоздать с помощью CustomView, загруженного из XIB перевести в CustomView с теми же параметрами - я прикрепил оба как скриншот.
загрузка XIB-файла и его настройка.
import UIKit
@IBDesignable
class CustomView: UIView {
// loaded from NIB
private weak var view: UIView!
convenience init() {
self.init(frame: CGRect())
}
override init(frame: CGRect) {
super.init(frame: frame)
self.loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.loadNib()
}
override func layoutSubviews() {
super.layoutSubviews()
// we need to adjust the frame of the subview to no longer match the size used
// in the XIB file BUT the actual frame we got assinged from the superview
self.view.frame = bounds
}
private func loadNib () {
self.view = Bundle (for: type(of: self)).loadNibNamed(
"CustomView", owner: self, options: nil)! [0] as! UIView
self.addSubview(self.view)
}
}
что вы можете увидеть здесь, так это то, что я просто загружаю файл XIB, переопределяю подвиды макета, чтобы адаптировать кадр к фактическому виду, и все.
вопросы
Итак, мои вопросы связаны с тем, как это обычно подошел. Метки в моем StackView имеют внутренний размер содержимого, т. е. обычно StackView просто приспосабливается к высоте ярлыков без жалоб - если вы конструируете его в раскадровке. Однако это не так, если вы поместите все это в XIB и оставите макет как есть - IB будет жаловаться на неизвестную высоту, если вид закреплен тремя способами, как на скриншоте (верхний, ведущий, трейлинг).
я прочитал руководство по автоматической компоновке и посмотрел видео WWDC "тайны автозапуска", но тема все еще довольно новая для меня, и я не думаю, что я получил право подход еще. Так вот где мне нужна помощь
(1) Должен ли я установить ограничения и отключить translatesAutoresizingMaskIntoConstraints
?
то, что я сначала мог и должен сделать, это установить translatesAutoresizingMaskIntoConstraints
false
такие, что я не получаю автоматически генерируемые ограничения и определить свои собственные ограничения. Чтобы я мог измениться!--10--> в:
private func loadNib () {
self.view = Bundle (for: type(of: self)).loadNibNamed(
"CustomView", owner: self, options: nil)! [0] as! UIView
self.view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.view)
self.addConstraint(NSLayoutConstraint (
item: self
, attribute: .bottom
, relatedBy: .equal
, toItem: self.view
, attribute: .bottom
, multiplier: 1.0
, constant: 0))
self.addConstraint(NSLayoutConstraint (
item: self
, attribute: .top
, relatedBy: .equal
, toItem: self.view
, attribute: .top
, multiplier: 1.0
, constant: 0))
self.addConstraint(NSLayoutConstraint (
item: self.view
, attribute: .leading
, relatedBy: .equal
, toItem: self
, attribute: .leading
, multiplier: 1.0
, constant: 0))
self.addConstraint(NSLayoutConstraint (
item: self.view
, attribute: .trailing
, relatedBy: .equal
, toItem: self
, attribute: .trailing
, multiplier: 1.0
, constant: 0))
}
в основном я растянул вид, чтобы соответствовать полной ширине моего UIView в раскадровке и сдерживал его сверху и снизу вверх и снизу StackView.
у меня было много проблем с Xcode 8 сбой на меня при использовании этого, поэтому я не совсем уверен, что с этим делать.
(2) или я должен переопределить intrinsicContentSize
?
я также мог бы просто переопределить intrinsicContentSize
проверив высоту всех моих arrangedSubviews
и возьмите тот, который является самым высоким, установите высоту моего зрения на него:
override var intrinsicContentSize: CGSize {
var size = CGSize.zero
for (_ , aSubView) in (self.view as! UIStackView).arrangedSubviews.enumerated() {
let subViewHeight = aSubView.intrinsicContentSize.height
if subViewHeight > size.height {
size.height = aSubView.intrinsicContentSize.height
}
}
// add some padding
size.height += 0
size.width = UIViewNoIntrinsicMetric
return size
}
хотя это, безусловно, дает мне больше гибкости, мне также придется аннулировать внутренний размер контента, проверьте динамический тип и множество других вещей, которых я бы предпочел избежать.
(3) я даже загружаю XIB "предлагаемым" способом ?
или есть лучший способ, например, с помощью UINib.instantiate
? Я действительно не мог найти лучший способ сделать это.
(4) Почему я должен это делать в первую очередь?
действительно, почему? Я только переместил StackView в CustomView. Так почему же StackView перестает адаптироваться сейчас, почему я должен установить верхний и Нижний ограничения?
что помогает больше всего, так далеко, что ключевая фраза в https://stackoverflow.com/a/24441368 :
" в общем, автоматическая компоновка выполняется сверху вниз. В других слова, Родительский макет представления выполняется сначала, а затем любой дочерний выполняются макеты вида."
.. но я в этом не уверен.
(5) и почему я должен добавить ограничения в код?
предполагая, что я отключу translatesAutoresizingMaskIntoConstraints
тогда я не могу установить ограничения, как для ячеек tableview В IB? Это всегда будет переводиться во что-то вроде label.top = top и, таким образом, метка будет растянута вместо StackView, наследующего размер/высоту. Также я не могу прикрепить StackView к его контейнерному представлению в IB для файла XIB.
надеюсь, кто-то может мне помочь. Ура!
1 ответов
вот некоторые ответы на ваши вопросы, хотя и в другом порядке:
(5) и почему я должен добавлять ограничения в коде?
если я отключить translatesAutoresizingMaskIntoConstraints тогда я не удается установить ограничения, как для ячеек tableview В IB? Так будет всегда. перевести на что-то вроде метки.top = top и таким образом ярлык растягиваться вместо StackView, наследующего размер / высоту. Также Я действительно не могу закрепить StackView для просмотра контейнер в МБ для файл XIB.
вы должны добавить ограничения в коде, в этом примере, потому что вы загружаете произвольное представление из пера, а затем добавляете его в свой закодированный CustomView
. Представление в nib не имеет предварительного знания о том, в какой контекст или иерархию оно будет помещено, поэтому оно не может заранее определить, как ограничить себя неизвестным будущим супервизором. В случае с StackView на раскадровке, или внутри ячейки прототипа этот контекст известен так же, как и его родительское представление.
одна хорошая альтернатива, которую вы можете рассмотреть для этого случая, если вы не хотите вручную писать ограничения, - это сделайте свой подкласс CustomView UIStackView вместо UIView. Поскольку UIStackView автоматически генерирует соответствующие ограничения, это сэкономит вам ручное кодирование. См. следующий ответ для некоторого примера кода.
(3) я даже загружаю XIB в "предложенный" способ ?
или есть лучший способ, например, с помощью UINib.инстанцировать? Я действительно не мог найти лучший способ сделать это.
Я не знаю "стандартного" или "правильного" способа обработки загрузки пера в пользовательское представление. Я видел множество подходов, которые все работают. Тем не менее, я отмечу, что вы делаете больше работы, чем необходимо в вашем примере кода. Вот более минимальный способ достижения того же результата, но также с помощью UIStackView подкласс вместо подкласса UIView (как упоминалось выше):
class CustomView: UIStackView {
required init(coder: NSCoder) {
super.init(coder: coder)
distribution = .fill
alignment = .fill
if let nibView = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as? UIView {
addArrangedSubview(nibView)
}
}
}
обратите внимание, что поскольку CustomView теперь является подклассом UIStackView, он автоматически создаст ограничения, необходимые для любых подвидов. В этом случае, потому что distribution
и alignment
установлены .fill
, он будет прикреплять края подвида к его собственным краям, как вы хотите.
(4) Почему я должен это делать в первую очередь?
действительно, почему? Я только переместил StackView в пользовательский вид. Так почему StackView перестает адаптироваться сейчас, почему я должен установить верхнюю а нижние ограничения?
на самом деле, ключевой проблемой здесь является ваш StackView alignment
собственность .center
. Это означает, что метки в StackView будут центрированы по вертикали,но это не ограничивает их в верхней или нижней части StackView. Это похоже на добавление ограничения centerY к подвиду внутри представления. Вы можете установить внешний вид на любую высоту, которую вы хотите, и подвид будет центрирован, но он не "выталкивает" верхнюю и нижнюю части внешнего вида, потому что ограничения центра ограничивают только центры, а не верхний и нижний края.
в вашей версии раскадровки StackView известна иерархия представлений выше этого StackView, а также есть опции для автоматического создания ограничений из масок изменения размера. В любом случае, окончательный StackView heigh известен UIKit, и метки будут центрирован вертикально в пределах этой высоты, но на самом деле не будет влиять на нее. вам нужно либо установить выравнивание вашего StackView, чтобы быть .заполните или добавьте дополнительные ограничения сверху и снизу меток в верхнюю и нижнюю части StackView, чтобы автоматический макет определял высоту StackView.
(2) или я должен переопределить intrinsicContentSize ?
это не помогло бы много, потому что внутреннеприсущее размер содержимого сам по себе не будет выталкивать края родительского представления, если края подвида также не ограничены краями родительского представления. В любом случае, вы получите все правильное поведение внутреннего размера контента, которое вам нужно в этом случае автоматически, если вы обратитесь к .center
проблема выравнивания упомянутых выше.
(1) Должен ли я установить ограничения и отключить translatesAutoresizingMaskIntoConstraints ?
это было бы нормально будьте разумным подходом. Если вы подкласс UIStackView, как я описал выше, хотя, вам не нужно делать ни то, ни другое.
в резюме
если вы:
создайте свой CustomView как подкласс UIStackView, как в примере кода выше
создайте представление на раскадровке, настроенное на пользовательский класс
CustomView
. Если вы хотите, чтобы CustomView имел правильный размер, вам нужно дать ему ограничения и не использовать автоматически созданный ограничения из маски изменения размера.измените StackView в вашем перо, чтобы либо иметь
alignment
значение.fill
или иметь дополнительные ограничения между верхней и нижней частью по крайней мере одной из меток (скорее всего, Большой Центральной) и верхней и нижней частью представления стека
затем автоматический макет должен работать правильно, и ваш пользовательский вид с StackView должен макет, как ожидалось.