Правильная практика для подкласса UIView?

Я работаю над некоторыми настраиваемыми элементами управления на основе UIView, и я пытаюсь установить правильную практику для настройки представления. При работе с UIViewController довольно просто использовать методы loadView и связанные с ними методы viewWill, viewDid, но при подклассификации UIView самые близкие методы: `awakeFromNib, drawRect и layoutSubviews, (Я думаю о настройках и обратных вызовах). В моем случае я настраиваю рамки и внутренние представления в layoutSubviews, но я ничего не вижу на экране.

Каков наилучший способ убедиться, что мой вид имеет правильную высоту и ширину, которые я хочу иметь? (Мой вопрос применяется независимо от того, использую ли я автозапуск, хотя могут быть два ответа.) Какая надлежащая "лучшая практика"?

Ответ 1

Apple четко определила, как подкласс UIView в документе.

Ознакомьтесь с приведенным ниже списком, особенно посмотрите initWithFrame: и layoutSubviews. Первый предназначен для настройки фрейма вашего UIView, тогда как последний предназначен для установки фрейма и макета его подзонов.

Также помните, что initWithFrame: вызывается только в том случае, если вы программно создаете свой UIView. Если вы загрузите его из файла nib (или раскадровки), будет использоваться initWithCoder:. И в initWithCoder: кадр еще не рассчитан, поэтому вы не можете изменить кадр, который вы создали в Interface Builder. Как было предложено в этом ответе, вы можете придумать вызов initWithFrame: из initWithCoder: для настройки фрейма.

Наконец, если вы загрузите UIView из nib (или раскадровки), у вас также есть возможность awakeFromNib выполнить пользовательские инициализации фрейма и макета, так как при вызове awakeFromNib гарантируется, что каждое представление в иерархия была распакована и инициализирована.

Из документа NSNibAwaking

Сообщения для других объектов могут быть отправлены безопасно из awakeFromNib, и к этому времени он гарантирует, что все объекты будут разрхивированы и инициализированы (хотя и не обязательно пробуждены, конечно)

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

Прямо от documentation:

Методы переопределения

Инициализация

  • initWithFrame: Рекомендуется реализовать этот метод. Вы также можете реализовать пользовательские методы инициализации в дополнение к, или вместо этого метода.

  • initWithCoder: Внедрите этот метод, если вы загружаете свой вид из файла nib файла Interface Builder, и ваше представление требует пользовательских инициализации.

  • layerClass Внедрите этот метод, только если вы хотите, чтобы ваше представление использовало другой уровень Core Animation для своего хранилища. Например, если вы используете OpenGL ES для рисования, вам нужно будет переопределить этот метод и вернуть класс CAEAGLLayer.

Рисование и печать

  • drawRect: Внедрите этот метод, если в вашем представлении создается пользовательский контент. Если ваш вид не выполняет какой-либо пользовательский чертеж, избегайте переопределения этого Метод.

  • drawRect:forViewPrintFormatter: Реализовать этот метод только в том случае, если вы хотите по-разному рисовать свой контент во время печати. ​​

Ограничения

  • requiresConstraintBasedLayout Внедрить этот метод класса, если ваш класс представления требует, чтобы ограничения работали правильно.

  • updateConstraints Внедрите этот метод, если вашему представлению необходимо создать собственные ограничения между вашими областями.

  • alignmentRectForFrame:, frameForAlignmentRect: Внедрите эти методы, чтобы переопределить, как ваши представления выравниваются с другими представлениями.

Разметка

  • sizeThatFits: Внедрите этот метод, если хотите, чтобы ваш вид имел другой размер по умолчанию, чем обычно, при изменении размера операции. Например, вы можете использовать этот метод, чтобы просмотр от сжимания до точки, где невозможно отобразить подпункты правильно.

  • layoutSubviews Внедрите этот метод, если вам нужен более точный контроль над макетом ваших подзонов, чем ограничение или авторезистивное поведение.

  • didAddSubview:, willRemoveSubview: Внедрите эти методы по мере необходимости, чтобы отслеживать добавления и удаления субвью.

  • willMoveToSuperview:, didMoveToSuperview Внедрите эти методы по мере необходимости, чтобы отслеживать движение текущего вида в вашем представлении иерархия.

  • willMoveToWindow:, didMoveToWindow Внедрите эти методы по мере необходимости, чтобы отслеживать перемещение вашего представления в другое окно.

Обработка событий:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent: Реализация эти методы, если вам нужно напрямую обрабатывать события касания. (Для на основе жестов, используйте распознаватели жестов.)

  • gestureRecognizerShouldBegin:Внедрите этот метод, если ваш взгляд обрабатывает события касания напрямую и может распознаватели жестов запускают дополнительные действия.

Ответ 2

В Google все еще очень много. Ниже приведен обновленный пример для быстрого.

Функция didLoad позволяет поместить весь ваш код инициализации. Как уже упоминалось, didLoad будет вызываться, когда представление создается программным путем через init(frame:) или когда десериализатор XIB объединяет шаблон XIB в ваш вид через init(coder:)

Кроме того: layoutSubviews и updateConstraints вызываются несколько раз для большинства просмотров. Это предназначено для расширенных многопроходных макетов и настроек, когда представление ограничивает изменения. Лично я избегаю многопроходных макетов, когда это возможно, потому что они сжигают циклы процессора и делают все головной болью. Кроме того, я вставляю код ограничения в самих инициализаторах, так как я редко делаю их недействительными.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

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

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}

Ответ 3

В Apple есть достойное резюме documentation, и это хорошо описано в свободном курсе Стэнфорда доступно в iTunes. Я предлагаю версию TL, DR здесь:

Если ваш класс в основном состоит из подзонов, то правильное место для их размещения находится в методах init. Для представлений существует два разных метода init, которые могут быть вызваны, в зависимости от того, создается ли ваше представление из кода или из nib/раскадровки. Я пишу свой собственный метод setup, а затем вызываю его из методов initWithFrame: и initWithCoder:.

Если вы выполняете собственный чертеж, вы действительно хотите переопределить drawRect: в своем представлении. Если ваш пользовательский вид в основном является контейнером для подзаголовков, вам, вероятно, это не понадобится.

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

Ответ 4

layoutSubviews предназначен для установки фрейма на дочерние представления, а не на представление.

Для UIView назначенный конструктор обычно initWithFrame:(CGRect)frame, и вы должны установить его там (или в initWithCoder:), возможно, игнорируя пройденное в кадре значение. Вы также можете предоставить другой конструктор и установить там фрейм.