Надлежащая практика для создания подклассов UIView?

158

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

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

Моше
источник

Ответы:

298

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

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

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

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

Из документа NSNibAwaking(теперь заменен документом awakeFromNib):

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

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

Прямо из документации :

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

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

  • 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: Реализуйте этот метод, если ваше представление напрямую обрабатывает сенсорные события и может потребовать, чтобы присоединенные распознаватели жестов не запускали дополнительные действия.

Габриэле Петронелла
источник
как насчет - (void) setFrame: (CGRect) фрейм?
2013 года
ну, вы можете определенно переопределить это, но с какой целью?
Габриэле Петронелла
изменить макет / рисунок в любое время, когда изменяется размер кадра или местоположение
pfrank
1
Как насчет layoutSubviews?
Габриэле Петронелла,
От stackoverflow.com/questions/4000664/… , «проблема в том, что подпредставления могут не только изменять свой размер, но и анимировать это изменение размера. Когда UIView запускает анимацию, он не вызывает layoutSubviews каждый раз». Лично я не проверял это лично
pfrank
38

Это все еще высоко в Google. Ниже приведен обновленный пример для swift.

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
  }
}
поисковая оптимизация
источник
Можете ли вы объяснить, когда и почему вы должны разделить код ограничения макета между методом, вызываемым из процесса инициализации, и layoutSubviews и updateConstraints? Кажется, что они все три возможных местоположения кандидата, чтобы поместить код расположения. Итак, как вы знаете, когда / что / почему разделить код макета между тремя?
Клэй Эллис
3
Я никогда не использую updateConstraints; updateConstraints могут быть полезны, потому что вы знаете, что ваша иерархия представлений была полностью настроена в init, поэтому вы не можете вызвать исключение, добавив ограничение между двумя представлениями, не входящими в иерархию :) layoutSubviews никогда не должно иметь изменений ограничения; это может легко вызвать бесконечную рекурсию, так как layoutSubviews вызывается, если ограничения «аннулированы» во время прохода макета. Ручная настройка макета (как при настройке фреймов напрямую, что вам редко требуется больше, если только не из соображений производительности) идет в layoutSubviews. Лично я помещаю создание ограничений в init
seo
Для пользовательского кода рендеринга, мы должны переопределить drawметод?
Петрус Терон
14

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

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

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

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

dpassage
источник
Я использую ваш ответ, чтобы изменить фрейм subView представления (который является awakeFromNib) layoutSubViews, это сработало.
самолет
1

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

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

Proxi
источник
не могли бы вы взять это более подробно? Я не знал, что вы имеете в виду. Как установить фрейм subView представления? видawakeFromNib
самолет
Перейдя к 2016 году, вы, вероятно, вообще не должны устанавливать фреймы и использовать autolayout (ограничения). Если представление происходит из XIB (или раскадровки), подпредставление должно быть уже настроено.
Прокси