Задание 3. Решение — обязательные пункты

Screen Shot 2015-05-16 at 9.24.07 AM

Текст Домашнего задания на английском языке доступен на  iTunes в пункте “Developing iOS 8 app: Programming: Project 3″.  Текст Домашнего задания на русском  языке доступен на 

Задание 3 iOS 8_new.pdf


Для выполнения этого Задания необходимо освоение  Лекции 5Лекции 6Лекции 7 и Лекции 8.
В подсказке 4 Задания 3 предлагается некоторая методика разработки данного приложения, но каждый волен сам выбирать, какую часть приложения создавать в первую очередь. Поэтому обязательные пункты Задания 3 могут выполняться не в строгом порядке.

Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункт 1 обязательный

Вы должны начать это Задание с кода вашего Задания 2, а не с какой-то демонстрации, находящейся на сайте. Изучение создания новых MVCs и segues требует опыта, поэтому не используйте copy / paste или редактирование уже существующей storyboard, на которой уже есть segues.

Это задание выполнено.

Пункт 2 обязательный

Переименуйте класс ViewController, c которым вы работали в Задании 1 и 2 в CalculatorViewController.

Переименование нужно выполнить в 3-х местах:

1. переименовать сам файл в Навигаторе с ViewController.swift на CalculatorViewController.swift
2. переименовать сам класс в файле CalculatorViewController.swift

Screen Shot 2015-05-06 at 7.49.08 PM

3. заменить класс на storyboard c ViewController на CalculatorViewController в Инспекторе Идентичности (Identity Inspector)

Screen Shot 2015-05-06 at 7.55.28 PM

Пункт 3 (только кнопка) обязательный

Добавьте новую кнопку к вашему пользовательскому интерфейсу калькулятора. Если вы ее нажимаете, то вы segues (“переезжаете”) на новый MVC (его вы должны будете написать), который строит график программы program в CalculatorBrain , сформированной во время нажатия кнопки с использованием размещенной в стэке M как независимой переменной. Например, если CalculatorBrain содержит sin(M), то вы рисуете синусоиду. Последующий ввод информации в калькулятор не должен оказывать эффект на график (до тех пор, пока графическая кнопка не будет снова нажата).

В данном пункте задания нам не надо точно выстраивать пользовательский интерфейс, поэтому поместим кнопку в верхнем левом углу, отодвинув немного в сторону наш дисплей. Это потребует некоторой дополнительной работы с системой Autolayout. Создадим зазор (Trailing Space to: 0) между кнопкой «Graph» и нашем дисплеем, в котором установлено значение 0. Величина  зазора равна Standard Value. Для кнопки «Graph» создадим повышенный Hugging приоритет ( равный 300), чтобы она не растягивалась по горизонтали, а позволила это сделать дисплею.
Screen Shot 2015-05-06 at 8.27.03 PM
Ограничения (constraints) для дисплея также не содержат «магических» чисел.
Screen Shot 2015-05-06 at 8.50.08 PM
Далее я буду добавлять новый ViewController для моего графического MVC и устанавливать на него пользовательский View.
Следуем демонстрационному примеру, приведенному Полом Хэгарти в Лекции 5, применяя все его приемы к нашей задачи.
Идем в Палитру Объектов и первым же элементом этой палитры будет View Controller. Перетягиваем его на storyboard и с помощью цепочки File > New > File > Cocoa Touch Class создаем subclass  и называем его GraphViewController. Устанавливаем класс для View Controller  в Инспекторе Идентичности (Identity Inspector) в Xcode.
Screen Shot 2015-05-06 at 9.20.15 PM

Пункты 7 (частично), 10  обязательные

7.  Как часть вашей реализации (implementation), вам нужно написать обощенный (generic)  y (x) графический UIView. Другими словами, UIView, который рисует графики, должен быть сконструирован таким способом, что он является полностью независимым от калькулятора (и мог бы использоваться в других совершенно различных приложениях для рисования графика y (x) ).

10. Ваш графический View должен быть @IBDesignable и его масштаб должен быть  @IBInspectable. Оси на графическом View должны появиться на storyboard в инспектируемом (inspected) масштабе.

Теперь нам нужно добавить custom (пользовательское, далее я буду использовать слово custom для обозначения “пользовательское”, так как оно короче) View на наш Graph View Сontroller.
Как я буду это делать? Я иду в Палитру Объектов и вытягиваю оттуда обобщенный (generic) UIView.
Располагаем его на экране, так, чтобы появились пунктирные голубые направляющие линии и мой View заполнил бы собой весь мой экранный фрагмент.
Screen Shot 2015-05-06 at 9.38.40 PM
Далее использую опцию “Reset to Suggested Constraints” (установка предлагаемых ограничений) системы Autolayout. Ограничения установились без «магических» чисел и теперь нам нужен custom (пользовательский) subclass UIView. Давайте сделаем это с помощью меню File > NewFile … и создаем subclass класса UIView. Мы назовем его GraphView.

Screen Shot 2016-06-28 at 11.29.36 AM
Устанавливаем класс  GraphView для пользовательского View в Инспекторе Идентичности (Identity Inspector) в Xcode.
Screen Shot 2015-05-06 at 9.51.35 PM
Загружаем для этого проекта класс AxesDraw, который рисует оси графика, с сайта Stanford.
В новом классе GraphView, который является subclass UIView, рисуем пока только оси, используя contentScaleFactor данного прибора и координаты центра (graphCenter) нашего пользовательского  View, а также другие свойства, которые определены ниже:
Screen Shot 2016-06-27 at 3.18.15 PM
Стараемся максимально возможно вынести код за пределы функции drawRect, которая все время будет участвовать в «перерисовке»: создаем экземпляр axesDrawer класса AxesDraw c заданным цветом осей, вычисляем центр graphCenter нашего View. Свойство contentScaleFactor можно установить только в drawRect, когда будет известно на каком приборе мы запускаем наше приложение.
Специфицируем наш класс GraphView с помощью директивы @IBDesignable, и Xcode автоматически замечает это и рисует прямо на storyboard наши оси. Директивы @IBInspectable дают возможность появится свойствам scalelineWidth, color непосредственно в Инспекторе Атрибутов. Теперь мы можем устанавливать нами изобретенные свойства прямо на storyboard.
Устанавливаем режим перерисовке нашего пользовательского View в Redraw.

Screen Shot 2015-05-07 at 9.09.33 PM

Создаем с помощью CTRL-перетягивания segue типа Show с идентификатором «Show Graph» от кнопки «Graph»  на Calculator View Controller до Graph View Controller.
Screen Shot 2015-05-07 at 7.40.48 AM
На Graph View Controller появился заголовок, который частично закрыл наш график. Нам необходимо скорректировать сам GraphView и его установки Autolayout. Для этого «оттягиваем» верхнюю границу GraphView вниз до голубой пунктирной линии и используем Reset to Suggested Contstraints для выбранного элемента.
Screen Shot 2015-05-07 at 9.11.44 AM
Выделяем наш Calculator View Controller и вставляем его в Navigation Controller c помощью меню Editor -> Embed in -> Navigation Controller
Screen Shot 2015-05-07 at 9.23.23 AM
Теперь на Calculator View Controller появился заголовок, который частично закрыл наш интерфейс. Необходимо привлечь для корректировки настроек Autolayout нашего старого друга — Схему UI (Document Outline)
Screen Shot 2015-05-07 at 9.33.30 AM
Теперь получим приложение, в котором дальше можно наращивать функциональные возможности Графического калькулятора

Screen Shot 2015-05-07 at 9.40.05 AM
Запускаем приложение, нажимаем кнопку «Graph«, получаем график, на котором только одни только оси.
Screen Shot 2015-05-07 at 9.41.58 AM
Код для этого этапа для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункт 11 обязательный

Ваш графический View должен поддерживать следующие жесты:

Pinching (изменение масштаба, zooming, целого графика, включая оси, в / за пределами графика)

Panning (перемещение целого графика, включая оси, вслед за передвижениями пальцев по экрану)

Double-tapping (перемещение origin графика в точку, где вы дважды “тапнули”)

Будем добавлять «жесты» в GraphViewController, а функции обработки жестов будут в GraphView, так как жесты связаны с установкой его UI элементов. Но прежде создадим  outlet для GraphView с помощью  CTRL-перетягивания от GraphView в код.

Screen Shot 2015-05-07 at 11.24.13 AM
Добавлять жесты будем в setter, в Наблюдателе Свойства (Property Observer) didSet { } нашего вновь созданного свойства graphView.
Screen Shot 2015-05-07 at 11.49.29 AM
Заметим, что жесты можно добавить и на storyboard. Этот вариант рассмотрен в Лекции 6. Поместим обработки жестов scale, originMove, origin в класс GraphView, оставив их non-private.
Screen Shot 2015-05-07 at 12.15.01 PM
Код для Swift 1.2 находится на GitHub. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункты 7 (полностью), 8, 9

 7. Как часть вашей реализации (implementation), вам нужно написать обощенный (generic)  y (x) графический UIView. Другими словами, UIView, который рисует графики, должен быть сконструирован таким способом, что он является полностью независимым от калькулятора (и мог бы использоваться в других совершенно различных приложениях для рисования графика y (x) ).

8. Графический View не должен владеть (то есть запоминать) данные, представляемые графически. Он должен использовать делегирование для получения данных в случае необходимости.

9. Ваш графический калькулятор должен обладать способностью графически представлять разрывные свойства функций (то есть он должен рисовать линии только от/ к точкам, которые для фиксированного  значения M program, задающая рисование графиков, оценивает как Double (то есть не nil) что соответствует .isNormal или .isZero).

Мы должны научить наш GraphView рисовать графики в отсутствии данных. Будем использовать для этого делегирование и действовать по плану, изложенному в Лекции 6.

Это план 5 шагов :

  1. Создаем  protocol, который по существу является типом
  2. Добавляем public weak свойство delegate (или dataSourceDelegate), тип которого протокол
  3. Используем свойство  delegate (или dataSourceDelegate) внутри класса, который делегирует кому-то то, что указано в протоколе
  4. Класс, который вызвался быть делегатом, объявляет, что подтверждает протокол и устанавливает у себя свойство  delegate (или dataSourceDelegate)
  5. Класс-Делегат реализует методы и свойства протокола

Итак, нам необходим протокол, единственной целью которого является получение данных для рисования внутри View, потому что оно не может иметь собственные данные. Называем протокол GraphViewDataSource, и в нем будет всего одна функция зависимости y (x).
Screen Shot 2015-05-07 at 1.18.44 PM
Создаем в GraphView свойство dataSource, тип которого будет протокол делегирования
Screen Shot 2015-05-07 at 1.23.28 PM
Используем свойство dataSource для построения зависимости y = f(x)
Screen Shot 2015-05-07 at 1.27.55 PM
GraphViewController объявляет, что он реализует протокол GraphViewDataSource и устанавливает самого себя (self) как делегата этого протокола. Затем он реализует этот протокол.
Screen Shot 2015-05-07 at 1.36.54 PM
Мы задали функцию общего вида y = cos (x) *x.
запускаем приложение и получаем общий график. Жесты для изменения масштаба и перемещения начала координат работают.
Screen Shot 2015-05-07 at 1.51.51 PM
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункты 3 (полностью), 4  и 6 обязательные

3. Добавьте новую кнопку к вашему пользовательскому интерфейсу калькулятора. Если вы ее нажимаете, то вы segues (“переезжаете”) на новый MVC (его вы должны будете написать), который строит график программы program в CalculatorBrain , сформированной во время нажатия кнопки с использованием размещенной в стэке M как независимой переменной. Например, если CalculatorBrain содержит sin(M), то вы рисуете синусоиду. Последующий ввод информации в калькулятор не должен оказывать эффект на график (до тех пор, пока графическая кнопка не будет снова нажата).

4. Никакому из ваших MVCs в этом Задании не разрешается появляться в non-private APICalculatorBrain.

 

6. В любое время, пока график находится на экране, должно быть также показано описание (description) того, что рисуется на графике, например, если строится график sin(M), то строка “sin(M)” должна показываться где-то на экране.

Моделью для графического MVC будет программа для калькулятора, которая будет передана при нажатии кнопки «Graph».  В GraphViewController Модель представлена свойством program
Screen Shot 2015-05-07 at 4.27.53 PM
 Интерпретировать эту программу будет «локальный калькулятор» brain
Screen Shot 2015-05-07 at 4.29.13 PM
При установке Модели program извне, «локальный калькулятор» brain принимает программу program и готов к использованию в методе

func y(x: CGFloat) -> CGFloat?

протокола GraphViewDataSource

Screen Shot 2015-05-16 at 6.42.38 PM

Попутно устанавливается заголовок графика. С использованием принятой извне  program и «локального калькулятора» brain реализуется метод  протокола GraphViewDataSource
Screen Shot 2015-05-07 at 9.18.29 PM
Модель program устанавливается в CalculatorViewController в методе prepareForSegue, универсальную реализацию которого предложил профессор Пол Хэгерти в Лекции 7. Универсальность в том смысле, что данный код работает как в случае, когда segue идет от кнопки «Graph» к непосредственно на Graph View Controller, так и через Navigation Controller
Screen Shot 2015-05-07 at 4.55.16 PM
Запускаем приложение и набираем на калькуляторе следующую последовательность 1⏎ M⏎ ÷ cos M⏎. Не обращаем внимание на сообщение «M не установлена», так как нам важна формула нашего графика, а не расчетный результат в одной точке, и нажимаем кнопку «Graph»
Screen Shot 2015-05-07 at 5.05.28 PM
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.

Пункт 5 обязательный

На iPad и в ландшафтном режиме на iPhone6+ устройствах, график должен быть (или может быть) на экране одновременно с вашим пользовательским интерфейсом существующего калькулятора (например, в Split View). На других iPhones график следует “выталкивать” (“push”) на экран через Navigation Controller.

Добавляем Split View Controller на storyboard и убираем «сопутствующие» View Controllers. Сделаем Сalculator View Controller — Master, а Graph View Controller — Detail. Добавим еще один Navigation Controller  для Detail. И segue теперь у нас будет не Show, а ShowDetail (лучше уничтожить старый segue и создать новый, не забульте указать идентификатор segue «Show Graph»):

 

Screen Shot 2015-05-12 at 11.30.46 AM

Запускаем наScreen Shot 2015-05-07 at 9.31.56 PM iPad.
Код для Swift 1.2 находится на Github. Полное решение для Swift 2.0 можно посмотреть на Github.