Лекция 4 CS193P Spring 2016 — Views

Лекция 4  полностью посвящена Views. Views — это прямоугольные области на экране, в которых мы рисуем и получаем от пользователя так называемые “жесты прикосновения” (touch gestures), как одинарные, так и множественные. Но на Лекции 4 ведется разговор только о рисовании, управление жестами перенесено в Лекцию 5. Никогда раньше в своих курсах Пол Хэгарти не уделял столько внимания рисованию Views. Возможно, это связано с тем, что компактность кода и богатство синтаксических конструкций Swift позволяют представить рисование как увлекательный и очень интеллектуальный процесс.

В теоретической части рассматривается иерархичность Views, superview, subviews и связанные с ними системы координат, способы инициализации Views в зависимости от того, где они создаются: в коде или на storyboard, «ранняя» установка некоторых свойств в методе awakeFromNib.
Views используют специальные структуры данных для рисования: точка CGPoint, размер CGSize и прямоугольник CGRect, а также множество удобных методов для работы с ними. Дается четкое различие между frame и bounds. Вводится понятие единицы измерения points с различным числом пикселей в зависимости от используемого прибора и соответствующее свойство contentScaleFactor.

Представлена подробная концепция рисование в drawRect с использованием Core Graphics и UIBezierPath. Рассказывается обо всем: рисование фигур, «вырезание»  по UIBezierPath траектории с использованием addClip(), управление цветами с помощью класса UIColor, управление прозрачностью  с помощью значения alpha, «cкрытие» view, не убирая его из иерархии.
Рассматриваются трудности использования класса NSAttributedString в Swift для «рисования» текстов в drawRect. Правильное использование фонтов preferredFontForTextStyle для пользовательского контента увеличивает привлекательность вашего приложения. На основе режима «перерисовки» сontentMode определяется, что будет происходить при изменении границ  bounds вашего view. Дается представление о кривых Безье и показано их использование для построения сложных траекторий.
В конце лекции профессор представляет демонстрационный пример FaceIt не просто как иллюстрацию теоретических частей лекции, а как возможность показать простоту рисования с использования UIBezierPath в Swift. Ни одна строчка кода не остается без подробных объяснений профессора.

Лекции и слайды на английском языке представлены в iTunes название “4.Views”. Код демонстрационного примера «FaceIt L4» для Swift 3.0  и Xcode 8 код находится на Github, а для Swift 3 .2  и Xcode 9 — также на Github.

Русскоязычный неавторизованный конспект лекции 4, хронометрированный через каждые 5 минут, и представленный в виде PDF-файла, который можно скачать и использовать offline, а также Google Doc доступны на платной основе.

Лекция 4 CS193P Spring 2016 — Views: 9 комментариев

  1. Всем привет. После просмотра данной лекции возникло пару вопросов. Был бы очень признателен пояснениям по данным пунктам:

    1) «НИКОГДА НЕ вызывайте этот метод.
    НИКОГДА, ни при каких обстоятельствах, ни при каких исключениях вы НE должны вызывать этот метод. Система вызывает drawRect, а вы не вызываете его никогда. Как же вам получить свое “лицо” нарисованным, если вы хотите его все-таки нарисовать?
    Например, вы нарисовали, что “глаза” закрыты и вам нужно это отобразить, Тогда вы вызываете метод setNeedsDisplay (). Этот метод говорит системе: ”Слушай, мне нужно перерисовать мой view.” И система когда-нибудь в будущем, когда ей будет удобно, вызовет ваш drawRect. »

    Как понять данную трактовку в контексте того, что в demo мы используем drawRect? Видимо имелось ввиду, что переопределение делается, если мы рисуем что то, а если нет, то не uncomment метод drawRect. Но нельзя к примеру в методе getEyeCenter(eye: Eye) вызывать метод drawRect(), а вместо этого использовать setNeedsDisplay ().» setNeedsDisplay () — И система когда-нибудь в будущем, когда ей будет удобно, вызовет ваш drawRect.» — яркий пример тому когда мы в ContentMode ставим redraw, так? И преимущества setNeedsDisplay (), что при вызове drawRect перерисует не целое view, а то которое обозначена в setNeedsDisplay() или какой смысл этого метода?

    2) Методы подобные drawRect идут уже после инициализации об объекта поэтому мы в них можем обращаться к member of instance? Есть ли методы которые идут до инициализации или это всё будет в лекции о lifecycles?

    3) Почему computed properties не могут быть let?

    4) Професор в лекции упоминает utilities fucntion можно по подробнее об этом определение и какие функции попадают в данную категорию?

    • 1) У все перепуталось по первому вопросу.
      Во-первых, определение функции и вызов функции — это разные вещи. Извините, но это азы программирования.
      Когда мы определяем функцию drawRect это выглядит так:
      func drawRect(rect: CGRect)
      Если мы ее вызываем, это выглядит так:
      drawRect (....)
      Так вот, профессор говорит о том, что никогда не следует делать ВЫЗОВ функции drawRect.
      И, конечно,в демонстрационном примере нет никакого ВЫЗОВА функции drawRect.
      Кстати, переопределять функцию drawRect в subclass UIView, конечно же можно:
      override func drawRect(rect: CGRect)
      Но, опять, это не ВЫЗОВ функции.
      Далее профессор говорит, что если вам нужно что-то перерисовать в вашем приложении, например, вы изменили цвет или ширину линии, то ВЫЗЫВАЕТСЯ НЕ drawRect, а setNeedsDisplay ().
      setNeedsDisplay () никак не связана с ContentMode.
      setNeedsDisplay () — это действия пользователя. ContentMode — это когда действует система iOS, и у НЕЕ возникает необходимость отразить ваш image в других обстоятельствах, например, при смене ориентации прибора. Вы можете влиять на это, задавая ContentMode, имея ввиду производительность воспроизведения image. Если вы задаете redraw, то это самый затратный по времени способ.

      2) В данном контексте речь идет об инициализации, видимо UIView, если используется drawRect. В процессе инициализации нельзя использовать свои собственные методы и указывать self. Инициализация вообще никак не связана с «жизненным циклом» View Controllers, ведь у вас UIView, а у него нет «жизненного цикла».
      Так вот, метод drawRect вызывается после инициализации и, следовательно, в нем можно использовать все ваши собственные методы.

      3) Вычисляемые свойства должны быть переменными var, потому что они все время меняются при вычислениях.

      • Спасибо большое Вам за ответы- теперь всё стало ясно. И извиняюсь за некорректный первый вопрос.
        1) В принципе меня в заблуждения по первому вопросу ввели слова профессора:

        «setNeedsDisplay () Этот метод говорит системе: ”Слушай, мне нужно перерисовать мой view.” И система когда-нибудь в будущем, когда ей будет удобно, вызовет ваш drawRect. »»

        Интересует каким образом система или при каких обстоятельствах система решает всё-таки вызвать drawREct после вызова setNeedsDisplay()?
        Если ContentMode — это система iOS, пример того смена ориентации устройств, то верно ли предполагать, что примером setNeedsDisplay () можно считать когда к примеру пользователь делает определенный жест и тогда срабатывает setNeedsDisplay() ?

        4) С 4 вопросом разобрался сам utility — полезность собственно ответ в самом значение слова. Функция которая предотвратит написания одних и тех же строк кода несколько раз.

        • 1) Я вам отвечу словами поэта:»Нам не дано предугадать, как наше setNeedsDisplay()? отзовется…» Не пытайтесь выпытать, как что-то делает iOS, никто точно этого не знает. И не соревнуйтесь с iOS.
          В отношении использования setNeedsDisplay() пользователем вы привели хороший пример с жестами, но это вторично, а первично изменение public vars API вашего класса, который скорее всего является subclass UIView.
          Посмотрите на класс FaceView:
          class FaceView: UIView {
          // MARK: Public API

          @IBInspectable
          var scale: CGFloat = 0.90 { didSet { setNeedsDisplay() } }
          @IBInspectable
          var mouthCurvature: Double = 1.0 { didSet { setNeedsDisplay() } }
          @IBInspectable
          var eyesOpen: Bool = false { didSet { setNeedsDisplay() } }
          @IBInspectable
          var eyeBrowTilt: Double = -0.5 { didSet { setNeedsDisplay() } }
          @IBInspectable
          var color: UIColor = UIColor.blueColor() { didSet { setNeedsDisplay() } }
          @IBInspectable
          var lineWidth: CGFloat = 5.0 { didSet { setNeedsDisplay() } }


          Видите? Любое изменение масштаба scale (с помощью жеста или другим способом) приводит к вызову setNeedsDisplay().
          То же самое с другими public vars FaceView.

  2. В Xcode 8,
    данный метод ругается и выдает ошибку. Как можно обойти проблему

    private var skullRadius: CGFloat {
    return min(bounds.size.width, bounds.size.height) / 2 * scale
    }

Обсуждение закрыто.