Задание 2 cs193p Winter 2015 Умный Калькулятор

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

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

 Задание 2 — очень трудное, но выполнимое. Если вам удастся выполнить его так, как планировал Пол Хэгерти, то есть в соответствии с логикой Swift, а не логикой того языка программирования (C++, C#, Java, Python и т.д.), с которого вы пришли, то можете считать себя «гуру» в Swift и представлять, что вы стоите на «Эвересте» и «снисходительно» смотрите на остальных. Что я имею ввиду под логикой Swift, которую так изобретательно использует Пол Хэгерти? Если вы смотрели предыдущий его курс по iOS 7, то там он в буквальном смысле «жонглирует» nil, чтобы ваш код из одной строчки  выполнял кучу работы. То же самое здесь, он будет также хитроумно использовать Optionals. Почти в каждом пункте Задания 2 он призывает рассмотреть вариант решения, состоящий из одной — двух строк. Помимо этого, Задание 2 содержит множество подсказок, которые помогут вам почувствовать «дух»  Swift.

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

Результаты выполнения заданий можно посылать на форум Swift[ru] для обсуждения. Выкладывайте решения в Github или давайте ссылку на Dropbox или используйте другие системы управления версиями. Xcode  работает напрямую с Github. Если у кого-то есть затруднения с этим — обращайтесь на форум, проведем ускоренный курс по выкладывание кода на Github. Если состоится обсуждение ( пока этого на форуме почти нет совсем) — будет интересно.

Специально привела возможное решение Задания 1 ( у вас может быть лучше), чтобы каждый, кто даже «застрял» по какой-то причине,  мог «приступить к выполнению Задания 2.

Задание 1 cs193p Winter 2015 Калькулятор

Содержание

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

Задание 1 iOS 8.pdf

Решение Задания 1 для Swift 1.2 и Swift 2.0 находится в посте Задание  1 cs193p Winter 2015 Калькулятор для Swift 1.2 и Swift 2.0.

Задание 1 cs193p Winter 2015 Калькулятор Swift 1.2 и Swift 2.0

Содержание

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

Задание 1 iOS 8.pdf

Начинаем выполнять Задание 1 с кода, полученного в конце Лекции 2 с учетом уточнений для Swift 1.2. Профессор настоятельно рекомендует не копировать код в первых 2-х Заданиях, а непосредственно печатать, так как это даст хороший опыт освоения среды разработки Xcode. Я все-таки привела на Github код демонстрационного примера, соответствующий окончанию Лекции 2. Это позволит совсем начинающим не «застрять» на самом первом этапе.

Ниже приводится решение всех пунктов задания для тех, кто хочет сравнить свой результат с моим, и окончательный код  Задания 1 на Github.

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

Ваш калькулятор (calculator) уже умеет работать с числами с плавающей точкой (например, если вы последовательно будете нажимать на клавиши 3 ⏎ 4 ÷  то он покажет правильный результат  0.75), тем не менее, пока нет ввода чисел с плавающей точкой. Это надо исправить. Разрешите вводить только правильные числа с плавающей точкой (напримр,  “192.168.0.1”  — неправильное число с плавающей точкой). Вам нужно добавить новую кнопку с точкой “.”. В этом задании не беспокойтесь о точности.

Прежде всего надо сделать небольшое замечание относительно точки «.» как десятичного разделителя.

Замечание. Если вы будете использовать символ  точки «.» в качестве десятичного разделителя в Задании 1, то оно может работать на симуляторе и не работать на вашем реальном устройстве, если Регион на вашем приборе установлен так, что там не применяется «.» для десятичных чисел. В этом случае приложение закончится аварийно. Но приложение должно работать в любом Регионе.

Для того, чтобы не зависеть от Региона, лучше получать разделитель для десятичных чисел непосредственно в вашем калькуляторе :

Screen Shot 2015-03-25 at 8.13.14 PM

Но вернемся к нашему пункту Задания и попытаемся заблокировать повторный ввод десятичного разделителя, который дальше по тексту мы будем условно называть «точкой», хотя, как мы уже знаем, это — не всегда точка. Для блокировки повторного ввода точки проще всего использовать функцию rangeOfString при вводе любой цифры (к которым относится и точка). Для этого нам понадобится всего одна строка

Screen Shot 2015-08-19 at 9.05.30 AM
Случай ввода в калькулятор числа “.5” работает правильно без дополнительного кода.
Если мы пойдем немного дальше, исследуя десятичный разделитель, то нам захочется иметь кнопку не с точкой «.», а с тем знаком, который принят в этом Регионе. Для этого сделаем outlet для кнопки с условным названием «точка»

Screen Shot 2015-03-25 at 8.21.37 PM

Screen Shot 2015-03-24 at 8.45.26 PM
Мы должны установить заголовок этой кнопки равным decimalSeparator и это можно сделать двумя способами :

  1. с использованием метода viewDidLoad () «жизненного цикла» Controller (об этом будет лекция 8); этот метод срабатывает один раз при загрузке Controller
  2. с использованием Наблюдателя didSet{} Свойста @IBOutlet weak var tochka; этот метод вызывается один раз при  установке этого outlet  со  storyboard

Первый способ связан с добавлением метода viewDidLoad ()

Screen Shot 2015-03-24 at 11.32.10 PM

Второй метод использует Наблюдателя Свойства tochka didSet {}

Screen Shot 2015-03-24 at 11.37.09 PM

Я выбрала второй метод и для проверки поставила на кнопке не «.«, а три символа «Poi«, которые должны заменится на десятичный разделитель decimalSeparator, взятый из NSNumberFormatter()

Screen Shot 2015-03-25 at 7.17.51 AM

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

Добавьте следующие 4 кнопки с операциями:

  • sin : вычисляет sin операнда на вершине стэка.

  • cos : вычисляет cos операнда на вершине стэка.

  • sqrt : вычисляет √ (квадратный корень) операнда на вершине стэка.

  • π: вычисляет (ассоциируется) с величиной π. Например: 3 π × должно показать на дисплее (display) вашего калькулятора величину π, умноженную на 3, точно также  3  π ×,  и  π 3 × должны показывать величину π, умноженную на 3.

Добавляем заголовки к безымянным кнопкам справа и проверяем, что все ли они подсоединены к Action @IBAction func operate(sender: UIButton)  
Screen Shot 2015-08-16 at 11.12.08 AM
Добавляем еще одну функцию performOperation для операции, соответствующей  возвращению константы (входные операнды не нужны)
Screen Shot 2016-03-10 at 4.33.54 PM
Первая функция для констант — у нее в параметре operation нет входных операндов. Вторая функция для унарный операций — у нее в параметре operation один входной операнд. Третья функция для бинарных операций — у нее в параметре operation два входных операнда.
Хочу обратить ваше внимание на наличие у всех функций атрибута @nonobjc. Пока не обращайте на это внимание, но если вам очень любопытно, то посмотрите пост «Дополнение к Лекции 2 — особенности кода Calculator в Swift 1.2 и Swift 2.0 или как не «застрять» на этом месте.»
Добавляем указанные в этом пункте 4 функции: sin, cos, sqrtπ.

Screen Shot 2015-08-16 at 8.14.27 PM
Кроме этого добавим операцию ± замены знака числу, когда оно полностью введено, что составляет часть дополнительного пункта 3 Задания 1.

Теперь объясним, почему при вызове функций performOperation входный параметр оформлен как замыкание без круглых скобок. Дело в том, что если последним входным параметром для функции является замыкание, то его можно вынести за круглые скобки. Для функций performOperation это единственный входной параметр, поэтому скобки исчезают. Такой способ оформления вызова функций в Swift называется trailing closure (хвостовое замыкание), и как увидим дальше, используется повсеместно.

Для функций sincossqrt из стандартной библиотеки известен тип входных параметров (Double) и тип возвращаемого значения (Double), что полностью совпадает с нашими требованиями, поэтому для этих функций можно не использовать замыкание, а оставить название функции в круглых скобках. Отметим, что функции sin и cos в качестве входного параметра используют угол, представленный в радианах.

Screen Shot 2015-08-16 at 10.16.54 PM

Запускаем калькулятор и проверяем  предложенные в этом пункте комбинации
3 π ×  
3 ↲ π x   
π 3  x

Screen Shot 2015-08-17 at 8.31.37 PM

Код этого пункта задания на Github.

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

Добавьте метку UILabel  на ваш пользовательский интерфейс, которая показывает историю ввода пользователем каждого операнда и операции. Найдите подходящее место для этой текстовой метки.

Добавьте UILabel на верхнюю часть  storyboard, сразу чуть ниже метки дисплея и выравняйте ее по голубым направляющим линиям слева и сверху. Задайте хвостовой, лидирующий зазоры и вертикальный зазор между метками. Кнопки нужно разместить заново: либо с помощью методики «сетки» или просто в верхний левый угол.

Создаем outlet для новой метки с именем history

@IBOutlet weak var history: UILabel!

Screen Shot 2015-08-16 at 10.29.26 PM

 Добавляем метод addHistory для накопления ввода операндов и операций в тексте метки history:

Screen Shot 2015-08-17 at 5.26.20 PM

Этот метод написан с учетом дополнительного пункта 2 этого Задания, в котором предполагается добавлять знак равенства «=» при нажатии пользователем на любой клавише с операциями. Поэтому предполагаем, что знак «=» может находится в тексте метки от предыдущей операции, и убираем его, чтобы избежать повторного употребления.
Метка history формируется при вводе операнда:
Screen Shot 2016-03-10 at 4.51.59 PM
и операции:
Screen Shot 2015-08-17 at 5.40.40 PM
Запускаем. Получаем требуемый результат.
Screen Shot 2015-08-17 at 8.42.36 PM
Замечание. Установленная таким образом метка history при отсутствии в ней информации сжимается, и цифровая клавиатура калькулятора немного поднимается вверх, а при появлении информации в метке history цифровая клавиатура калькулятора немного поднимается вниз. Это не очень хорошо. Нужно поместить пробел » » ( именно » «, а не «») в метку на  storyboard.
Screen Shot 2015-08-17 at 8.46.01 PM
Усекаем головную часть строки history, чтобы видеть последние элементы ввода опрандов и операторов.
Есть еще один момент — лидирующие нули. Сейчас возможен ввод чисел с лидирующими нулями (например, “000” или “0367”).
Я предлагаю для уничтожения лидирующих нулей следующий код
Screen Shot 2015-02-11 at 7.23.41 PM

Код этого пункта задания на Github.

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

Добавьте кнопку “C”,  которая будет чистить все  (ваш дисплей ввода, новую метку, которую вы только что добавили). При нажатии кнопки “C”. калькулятор должен находится в таком же состоянии как при старте.

Добавляю снизу еще ряд кнопок и одной из них даю название «С». Создаем Action clearAll. Внутри метода помещаем код и не забываем очистить метку history, но не пустой строкой «», а пробелом » «.
Screen Shot 2015-08-19 at 10.24.40 AM

Код этого пункта задания на Github.

Пункт 1 дополнительный (extra credit)

Дайте возможность пользователю нажимать кнопку  “backspace” если он ввел неверную цифру. Это вовсе не кнопка “undo,” так что если пользователь нажал неверную кнопку с операцией , то его ждет неудача! Вам решать как вы будете разруливать случай, когда пользователь кнопкой “backspace” полностью удалил число и все еще находится в процессе ввода числа , но оставлять дисплей (display) абсолютно пустым было бы не очень дружелюбно.                                             

Одной из добавленных снизу кнопок даю название «back«. Создаем Action backSpace
Screen Shot 2015-08-19 at 2.13.51 PM
Внутри метода помещаем код

Screen Shot 2015-08-19 at 2.18.22 PM

Этот метод работает только если пользователь находится в процессе набора числа.

Код этого пункта задания на Github.

Пункт 2 дополнительный (extra credit)

Когда пользователь нажимает кнопку операции (operation button), разместите знак pавно = в конце текстовой метки UILabel, которая добавляется в Обязательном задании ( #4). Таким образом пользователь сможет понять, является ли число на дисплее калькулятора результатом вычислений или это число только что ввел пользователь. Не позволяйте знаку “=” многократно появляться в конце метки UILabel.

Этот пункт выполнен в обязательном пункте 4. Мы добавили знак «=» при нажатии кнопки с операцией
Screen Shot 2015-08-17 at 5.40.40 PM
И предотвратили многократное его появление в методе addHistory
Screen Shot 2015-08-17 at 5.26.20 PM
В результате пользователь сможет понять, является ли число на дисплее калькулятора результатом вычислений или это число только что ввел пользователь.
Screen Shot 2015-02-12 at 1.50.41 PM

Код этого пункта задания на Github.

Пункт 3 дополнительный (extra credit)

 Добавьте +/- операцию, которая меняет знак числа на дисплее (in the display). Будьте более внимательны с этим. Если пользователь находится в середине ввода числа, вы возможно, захотите изменить знак этого числа и позволить ему продолжать его ввод, не прибегайте к “принудительному” “enter” как в других операциях. Но если вы не находитесь в середине ввода числа, то это будет работать просто как любая другая унарная операция ( например, sqrt).

Одной из добавленных снизу кнопок даю название «±«. Создаем Action  plusMinus
Screen Shot 2015-08-18 at 11.27.00 AM
Внутри метода помещаем код
Screen Shot 2015-08-18 at 11.29.41 AM
Код довольно простой: если вы находитесь в процессе ввода числа (userIsInTheMiddleOfTypingANumber ), то вы меняете знак. Если нет, то пересылаете вашу кнопку методу operate. Но для того, чтобы  «±» работала как операция, необходимо добавить ее в операции.
Screen Shot 2015-08-18 at 11.31.35 AM

Код этого пункта задания на Github.

 Пункт 4 дополнительный (extra credit)

Измените вычисляемую переменную экземпляра класса displayValue так, чтобы она была Optional Double, a не Double. Его значение должно быть nil если содержимое display.text не может быть интерпретировано как Double (вам нужно использовать документацию для понимания кода NSNumberFormatter ). Установка этого значения в nil должна очищать display.

Делаем  displayValue так, чтобы она была Optional Double — Double?, a не Double.
Начнем с get {  }
Screen Shot 2015-02-12 at 4.47.43 PM
Замечаем, что текст нашего дисплея тоже Optional, но  String — String?, поэтому вначале «развертываем» display.text и получаем displayText, который имеет тип String. Затем подаем displayText как входной параметр на вход NSNumberFormatter .
Screen Shot 2015-02-12 at 5.11.59 PM
«Pазвертываем» результат преобразования  NSNumberFormatter и получаем displayNumber, который имеет тип NSNumber
Делаем его Double и возвращаем.
Screen Shot 2015-02-12 at 5.19.17 PM
Примечание. Результат преобразования  NSNumberFormatter можно не разворачивать, а использовать так называемую chaining Optionals (цепочку Optionals), это будет в Задании 2. 
Screen Shot 2015-02-13 at 7.34.59 AM
Перейдем к set {  }. Код достаточно простой и понятный: если newValue  не равно nil,  то используем String interpolation (интерполяцию строк) для преобразования числа в строковое значение, в противном случае — очищаем дисплей.
Screen Shot 2015-08-19 at 10.40.21 AM

Замечание 1. Но здесь возникает та же проблема, что и с меткой history, при отсутствии в ней информации она сжимается до нулевых размеров, и появляется, если в ней что-то есть. Нужно поместить пробел » « ( именно » «, а не «») на наш дисплей. Поэтому внесем изменения в код установки displayValue = » «.

Замечание 2. Оставить просто пустым дисплей все-таки не очень дружелюбно, хотя именно в этом и состоит последний дополнительный пункт Задания, поэтому в метке history  с помощью добавления » Errror» мы укажем, что в этом случае возникла ошибка.
Теперь во всем коде ViewController вместо displayValue 0 ставим
displayValue = nil, кроме метода clearAll

 

Screen Shot 2016-03-10 at 5.00.37 PM

Мы присваиваем значение nil вычисляемой переменной displayValue в случае, если у операции нет необходимого числа операндов, если сама операция возвращает значение, которое невозможно преобразовать в Double или если осуществлен ввод текста, который нельзя преобразовать в Double.
Теперь мы можем запускать калькулятор со значением «0» на дисплее, а в случае ошибки экран будет пустым, но появится сообщение об ошибке в строке, отображающей историю ввода операндов и операций.

Screen Shot 2015-08-19 at 3.14.19 PM

 

Замечание 3. Один из пользователей сайта прислал такой комментарий :»при вычитании 8.2 минус 8.5 результат получается -0.300000000001.
Почему возникает этот случай? при этом 5.2-5.3 результат ровно -0.3. я не очень понимаю…»

Ответ. Действительно так, любопытно. Это связано с точностью бинарного представления числа с плавающей точкой. Если вы включите отладчик, то увидите, что число 8.2 представлено как 8.1999999999999993, а 8.5 как 8.5, так что разница будет -0.30000000000000071. Но в Double 15 десятичных значащих цифр. При вычитании близких чисел (8.2 и 8.5) точность представления разности  чисел снижается (может быть до 12 -13 значащих цифр), а нам показывается представление 15 десятичными значащими цифрами, так что цифры 71 находятся за пределами точности числа Double, но именно они и дают нам в конце 1. Кстати, число 5.2 представляется как 5.2000000000000002, а а 5.5 как 5.5, так что разница будет -0.29999999999999982, но в этом случае округление выдаст правильный результат (нам просто повезло). Для преобразования числа (8.2 -8.5) в строку мы используем String interpolation «\(newValue)», которая пытается представить все 15 десятичных цифр после точки, что неверно, так как исходное число содержало только 15 верных значащих цифр 8.20000000000000. К сожалению, мы не можем управлять форматом вывода в String interpolation, но можем получить более гибкий результат с помощью NSNumberFormatter.

Screen Shot 2015-08-19 at 3.41.43 PMМы можем использовать NSNumberFormatter со специальными свойствами при выводе результатов на displayВ NSNumberFormatter можно настраивать максимальное количество цифр после точки ( maximumFractionDigits ).В случае больших чисел нам нужны разделители групп цифр (groupingSeparator), если после точки выводится очень много знаков, то мы бы хотели ограничиться 10 знаками после точки, некоторые функции, например, √ могут возвращать значение Nan при отрицательном аргументе и нам надо указать, что это ошибка Error (notANumberSymbol). Мы получаем NSNumberFormatter со специальными свойствами при помощи функции numberFormatter()

Замечание 3. Необходимо скорректировать функцию addHistory, так как помимо знака «=» мы еще добавили строку «Error» . Необходимо уметь убирать их перед вводом нового значения, предполагая, что они не обязательно могут находится в конце строки

Screen Shot 2015-08-19 at 5.14.33 PM

 

Код этого пункта задания на Github.

————————————————————

Задание 1 для iOS 9  и Swift 2.0

Автоматически преобразуем код Swift 1.2 в Swift 2.0 с помощью меню Edit -> Convert -> To Latest Swift Syntax
Преобразования коснулись только работы со строками
Swift 1.2
Screen Shot 2015-08-19 at 6.53.23 PM
Swift 2.0
Screen Shot 2015-08-19 at 6.56.38 PM
Код для Swift 2.0 находится на Github.
На русском языке вы можете скачать здесь:

Задание 1 iOS 8.pdf

Результаты выполнения заданий можно посылать на форум Swift[ru] для обсуждения. Просьба давать развернутую оценку присланным результатам и сравнивать решения.

Форум для обсуждения лекций и заданий

ВНИМАНИЕ ОБСУЖДЕНИЕ лекций и заданий этих курсов будет проводится на форуме Google Group  Swift[ru].

Там можно задавать вопросы и выкладывать варианты заданий в Github или Dropbox.