Правильна практика підкласифікації UIView?


158

Я працюю над деякими спеціальними елементами введення на основі UIView, і намагаюся встановити належну практику налаштування представлення даних. При роботі з UIViewController, це досить просто використовувати loadViewі пов'язані з ними viewWill, viewDidметодами, але коли підкласи в UIView, найближчі methosds у мене є `awakeFromNib, drawRectі layoutSubviews. (Я думаю про налаштування та відкликання викликів.) У моєму випадку я налаштовую рамки та внутрішні погляди layoutSubviews, але нічого не бачу на екрані.

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

Відповіді:


298

Apple досить чітко визначила, як підкласи UIViewв doc.

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

Пам'ятайте також, що initWithFrame:виклик проводиться лише в тому випадку, якщо ви створюєте UIViewпрограмну копію програми. Якщо ви завантажуєте його з файлу nib (або раскадровки), initWithCoder:буде використано. А в initWithCoder:кадрі ще не було обчислено, тому ви не можете змінити кадр, який ви створили в Interface Builder. Як було запропоновано в цій відповіді ви можете думати про виклик initWithFrame:від initWithCoder:того , щоб налаштувати рамку.

Нарешті, якщо ви завантажуєте свій UIViewінструмент (або дошка розкадрів), у вас також є awakeFromNibможливість виконати власні ініціалізації кадру та макета, оскільки коли awakeFromNibвиклик гарантується, що кожен перегляд в ієрархії був неархівованим та ініціалізованим.

З doc of NSNibAwaking(тепер замінено doc of awakeFromNib):

Повідомлення іншим об’єктам можна безпечно надсилати з місця wakekeromromrombb - до цього часу буде впевнено, що всі об’єкти неархівовані та ініціалізовані (хоча, не обов'язково, прокидаються, звичайно)

Також варто зауважити, що при автоматичному розкладі ви не повинні чітко встановлювати рамку свого перегляду. Натомість вам слід вказати набір достатніх обмежень, щоб кадр автоматично обчислювався механізмом компонування.

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

Методи перекриття

Ініціалізація

  • initWithFrame:Рекомендується реалізувати цей метод. Ви також можете реалізувати власні методи ініціалізації на додаток до цього способу або замість цього.

  • initWithCoder: Реалізуйте цей метод, якщо ви завантажуєте свій погляд із файлу віконця Interface Builder, а для перегляду потрібна спеціальна ініціалізація.

  • layerClassРеалізуйте цей метод лише в тому випадку, якщо ви хочете, щоб ваш погляд використовував інший шар анімації Core для його резервного сховища. Наприклад, якщо для малювання ви використовуєте 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) кадр?
pfrank

ну ви точно можете це перекрити, але з якою метою?
Габріеле Петронелла

щоб змінити макет / креслення в будь-який час, коли розмір кадру чи місце розташування змінюються
pfrank

1
Про що layoutSubviews?
Габріеле Петронелла

З сайту stackoverflow.com/questions/4000664/… , проблема в тому, що підзагляди можуть не тільки змінити свій розмір, але й анімувати цю зміну. ​​Коли UIView запускає анімацію, вона не викликає layoutSubviews кожен раз. " Не перевіряв це особисто, проте
pfrank

38

Це все ще залишається високо в 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
  }
}

Чи можете ви пояснити, коли / чому ви розділили код обмеження макета між методом, викликаним з вашого процесу init та layoutSubviews та updateConstraints? Схоже, що у всіх трьох можливих місцях розташування кандидата розмістити код макета. Тож як ви знаєте, коли / що / навіщо розділити код макета між трьома?
Клей Елліс

3
Я ніколи не використовую updateConstraints; updateConstraints може бути приємним, оскільки ви знаєте, що ваша ієрархія перегляду була повністю налаштована в init, тому ви не можете підняти виняток, додавши обмеження між двома переглядами, не в ієрархії :) layoutSubviews ніколи не повинні змінювати обмеження; це може легко викликати нескінченну рекурсію, як виклик layoutSubviews, якщо обмеження "недійсні" під час пропуску макета. Налаштування макета вручну (як при встановленні кадрів безпосередньо, що рідко більше не потрібно робити, за винятком причин продуктивності), проходить у layoutSubviews. Особисто я розміщую створення обмежень в init
seo

Чи слід переглядати drawметод користувальницького коду візуалізації ?
Петрус Терон

14

У документації Apple є гідний підсумок , і це добре висвітлено у безкоштовному курсі Stanford, доступному на iTunes. Я представляю тут свою TL; DR версію:

Якщо ваш клас здебільшого складається з підпереглядів, правильне місце для їх виділення - у initметодах. Для переглядів є два різних initспособи, за допомогою яких можна викликати, залежно від того, чи використовується ваш погляд із коду чи з кнопки / розгортки. Що я роблю, це написати власний setupметод, а потім викликати його з обох методів initWithFrame:та initWithCoder:методів.

Якщо ви робите власні креслення, ви дійсно хочете перекрити drawRect:свій погляд. Якщо ваш власний вид переважно є контейнером для перегляду, вам, мабуть, цього не потрібно буде робити.

layoutSubViewsВідміняйте лише те, якщо ви хочете зробити щось на зразок додати або видалити підперегляд, залежно від того, чи ви маєте портретну чи пейзажну орієнтацію. В іншому випадку ви повинні мати можливість залишити його в спокої.


Я використовую вашу відповідь, щоб змінити кадр subView подання (який прокинувся) layoutSubViews, він спрацював.
літак

1

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

Тому UIView, що призначений конструктор зазвичай є, initWithFrame:(CGRect)frameі вам слід встановити кадр там (або в initWithCoder:), можливо, ігноруючи передане значення фрейму. Ви також можете надати інший конструктор і встановити там кадр.


Ви можете взяти це детальніше? Я не знав вашого середнього способу встановлення кадру subView подання? видawakeFromNib
літак

Перемотаючи вперед до 2016 року, ви, мабуть, взагалі не повинні встановлювати кадри та використовувати автовипуск (обмеження). Якщо подання перебуває з XIB (або дошки розкадрів), субпідгляд вже має бути налаштований.
проксі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.