Как: Self-Sizing Custom-View от XIB с представлением StackView в iOS

недавно я снова начал погружаться в разработку iOS с помощью Swift и теперь изучаю пользовательский UIControl. Для компоновки и гибкости это настраиваемое представление построено с использованием StackView.

чего я хочу добиться ...

... в основном просто иметь ту же функциональность с StackView, что и обычно, когда я перетаскиваю его прямо в раскадровку - где он изменяется, чтобы не было проблем. Это по существу то же самое, что и самоизмерение tableview ячейки.

витрина

я могу уменьшить свой CustomView до простого примера: это StackView с тремя метками в нем, Alignment значение Center, Distribution Equal Centering. StackView будет закреплен слева, справа и сверху направляющих макета (или трейлинг, ведущий и верхний). Это я могу легко воссоздать с помощью CustomView, загруженного из XIB перевести в CustomView с теми же параметрами - я прикрепил оба как скриншот.

скриншот простой StackView

скриншот 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, как я описал выше, хотя, вам не нужно делать ни то, ни другое.

в резюме

если вы:

  1. создайте свой CustomView как подкласс UIStackView, как в примере кода выше

  2. создайте представление на раскадровке, настроенное на пользовательский класс CustomView. Если вы хотите, чтобы CustomView имел правильный размер, вам нужно дать ему ограничения и не использовать автоматически созданный ограничения из маски изменения размера.

  3. измените StackView в вашем перо, чтобы либо иметь alignment значение .fill или иметь дополнительные ограничения между верхней и нижней частью по крайней мере одной из меток (скорее всего, Большой Центральной) и верхней и нижней частью представления стека

затем автоматический макет должен работать правильно, и ваш пользовательский вид с StackView должен макет, как ожидалось.