Задание 1 cs193p Spring 2016 Калькулятор. Решение. Обязательные и дополнительные пункты.

Содержание

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

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

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

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

В дополнение к Заданию 1 решаются вопросы работоспособности приложения на реальном устройстве в разных Регионах и построения адаптивного интерфейса, когда UI выглядит принципиально по-разному в портретном и ландшафтном режимах.

Для отдельных пунктов Задания есть релизы на Github для Xcode 7 и Swift 2.2.:
обязательные пункты 1-3, дополнительный 4,
обязательные пункты 4-8, дополнительный 4,
 дополнительные  1-2,
 дополнительный  3
с региональной точкой 
адаптивный интерфейс
отдельный Action для кнопки С
программируемый калькулятор — Лекция 3
глобальный formatter c помощью мгновенно выполняемого замыкания 

Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

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

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

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

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

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

Прежде всего необходимо проверить, что кнопка с точкой на вашем UI получена копированием цифры, а не операции. Это можно проверить, если вывести на экран одновременно ваш UI (storyboard) и класс ViewController, обслуживающий ваш UI.

Screen Shot 2016-05-07 at 4.54.01 PM

Если мышку навести на маленький кружочек слева от метода touchDigit, то будут показаны все кнопки, «подвязанные» к этому методу, то есть цифровая клавиатура. Мы видим, что кнопка с точкой «.» находится среди них.
Если мышку навести на маленький кружочек слева от метода performOperation, то будут показаны все кнопки, «подвязанные» к этому методу, то все операции и мы не должны увидеть там кнопку с точкой «.» .

Screen Shot 2016-05-07 at 5.00.36 PM

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

Screen Shot 2016-05-07 at 5.04.27 PM

Случай ввода в калькулятор числа “.5” работает правильно без дополнительного кода.

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

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

Пункт 4 дополнительный

Определите одну из ваших кнопок для операции, которая бы “генерировала бы случайное число между 0 и 1”. Кнопка для этой операции не является константой (так как меняет свое значение при каждом нажатии). Она не является и унарной операцией (так как ни с чем не оперирует).

Добавлять кнопки в Stack View одно удовольствие — кнопка в стэке принимает ту форму, которая определяется свойствами стэка. Можно копировать целыми стэками-строками. В результате очень легко UI расширяется до следующего вида:

Screen Shot 2016-05-07 at 7.53.11 PM

Кнопки «→M» и «M» не являются кнопками операций и нужны для будущего Домашнего Задания 2, а в этом Домашнем Задании не участвуют. Кнопка «» также не является кнопкой операции, она предназначения для “backspace” в случае ошибочного набора цифры при вводе числа, она упоминается в дополнительном пункте 1 этого Домашнего Задания и мы задействуем ее позже. Кнопка «С» нужна для обязательного пункта 8 этого Домашнего Задания и связана с очисткой всего и мы можем интерпретировать ее как операцию.Кнопка «rand» также является кнопкой операции, которая генерирует случайное число в диапазоне от 0 до 1.

Screen Shot 2016-05-07 at 9.18.24 PM

Добавляем операции в код. Для С (все очистить) добавляем в enum специальный тип операции C, для генерации случайного числа добавляем в  enum тип операции

NullaryOperation(() -> Double)

c ассоциированным значение виде функции, у которой нет никаких аргументов (нуль аргументов), а на выходе у нее Double.

Screen Shot 2016-05-07 at 8.33.57 PM

В методе performOperation будем в случае NullaryOperation просто вызывать функцию без аргументов, а в случае С вызываем функцию clear().

Screen Shot 2016-05-07 at 9.11.37 PM

.  .  .  .  .  .  .  .  .  .  . . . . . . . . . .

Screen Shot 2016-05-07 at 9.15.46 PM

Пополним словарь с операциями:

Screen Shot 2016-05-07 at 8.53.43 PM

Надо отметить, что класс CalculatorBrain сконструирован так, что его очень просто функционально расширить добавлением любого числа операций. Посмотрим, как это будет выглядеть в портретном режиме

Screen Shot 2016-05-07 at 9.23.43 PM

Отлично. Теперь ландшафтный режим.

Screen Shot 2016-05-07 at 9.42.27 PM

О! В ландшафтном режиме у нас исчез наш дисплей! По вертикале стало меньше место и система Autolayout предпочла «сжать» дисплей в пользу наших кнопок. Давайте повысим у метки display сопротивление к сжатию по вертикале и сделаем Content Compession Resistance Priority по вертикале равным 753, то есть выше, чем у стэка кнопок равного 750.

Screen Shot 2016-05-07 at 9.58.04 PM

Пробуем ландшафтный режим.

Screen Shot 2016-05-07 at 10.19.53 PM

Все нормально. Возвращаемся в портретный режим.

Screen Shot 2016-05-07 at 10.20.08 PM

Все в порядке. Результирующий код можно посмотреть на Github для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

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

Используйте цвета для придания вашему пользовательскому интерфейсу ( UI) привлекательности. По крайней мере, цвета ваших кнопок с операциями должны отличаться от цвета кнопок цифровой клавиатуры, но в любом случае вы можете использовать любые цвета для придания хорошего вида вашему калькулятору.

Мой пользовательский интерфейс имеет следующий вид на storyboard и на iPhone 6+ в портретном режиме.

Screen Shot 2016-05-08 at 10.45.36 AM

На iPhone 6+ в ландшафтном режиме.

Screen Shot 2016-05-08 at 10.50.25 AM

Все нормально, но в ландшафтном режиме хочется сделать другое распределение кнопок: не 5 х 7, а, например, 6 х 6. Возможность иметь разные UI для разных ориентаций прибора предоставляется механизмом Autolayout.
На iPhone 5s в портретном и ландшафтном режимах.

Screen Shot 2016-05-08 at 10.57.02 AM

Здесь также напрашивается другое распределение кнопок в ландшафтном режиме: не 5 х 7, а, например, 6 х 6. С использованием StackViews это делается не в  Interface Builder как это было раньше, а с применением API StackViews (addArrangedSubview и removeArrangedSubview) программным образом.

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

Добавьте String свойство к вашему CalculatorBrain с именем description, которое возвращает описание последовательности введенных операторов и операций, которые приводят к значению,  возвращаемому result. Ни символ “=“, ни символ   “” не должны появляться в этом описании.

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

Добавьте Bool свойство к вашему CalculatorBrain с именем isPartialResult, которое возвращает значение, является ли бинарная операция отложенной  (если да, возвращает true, если нет, то false).

Первое, что мы сделаем, это добавим ассоциированные значения во все операции enum Operation в классе CalculatorBrain Подобно тому как для вычислений на нашем калькуляторе  существуют, например, для унарных операций функции типа (Double) -> Double, для описания этих унарных операций введем функцию (String) -> String, которая берет описание аргумента в виде строки и возвращает описание функции вместе с взятым аргументом также в виде строки. Точно также для бинарной операции добавляем для описания функцию (String, String) -> String, которая берет описание двух аргументов в виде строки и возвращает описание функции вместе с взятыми аргументами также в виде строки.

Screen Shot 2016-05-09 at 1.57.10 PM

К бинарной операции BinaryOperation еще добавлено ассоциированное значение Int, которое задает приоритет бинарной операции, который используется для правильной расстановки круглых скобок в описании.

Согласно новым ассоциированным значениям модифицируем словарь operation:

Screen Shot 2016-05-09 at 2.03.20 PM

Совершенно симметрично вычислениям определяем переменные для описания. Внутренней переменной accumulator будет соответствовать внутренняя переменная descriptionAccumulator, которая будет накапливать введенные операнды и операции.  Внешней readonly переменной result, отображающей результат вычислений,  будет соответствовать внешней  readonly переменной description, показывающая описание того, что мы ввели для калькулятора.

Screen Shot 2016-05-09 at 2.15.06 PM

Переменная isPartialResult является readonly и нужна в ViewController. Переменная currentPrecedence отражает приоритет текущей операции.
Теперь дополним структуру PendingBinaryOperationInfo необходимой информацией для описания.

Screen Shot 2016-05-09 at 2.25.02 PM

Теперь в функциях  setOperand и performOperation  дополним код для формирования описания. Нам это будет сделать легко, так как там. где используется accumulator используем descriptionAccumulator.

Screen Shot 2016-05-09 at 2.32.43 PM

Screen Shot 2016-05-09 at 2.30.18 PM

Единственное добавление для описания связано с анализом приоритета preсedence выполняемой бинарной операции с приоритетом currentPreсedence предыдущей операции. Если он выше, то предыдущее описание мы заключаем в круглые скобки. Последний случай соответствует такой ситуации:

 в случае ввода 4 + 5 × 3 = должно быть показано “(4 + 5) × 3 =” (27 на display)

Заполнение структуры pending дополняется элементами описания операции и первого операнда.

Screen Shot 2016-05-09 at 2.56.10 PM

Функция executeBinaryOperation() также претерпела определенные изменения:

Screen Shot 2016-05-09 at 2.53.21 PM

Возвращаемся к определению нашей внешней readonly переменной description и дать некоторые пояснения.

Screen Shot 2016-05-09 at 3.39.39 PM

Если у нас нет отложенной операции, то есть pending = nil, то  возвращается descriptionAccumulator, в чистом виде. Если есть отложенная операция, то descriptionAccumulator дополняется описанием отложенной операции.

Изложенный алгоритм формирования description заимствован здесь.

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

Используйте два вышеуказанных свойства для реализации метки UILabel на вашем пользовательском интерфейсе (UI), которая показывает последовательность операндов и операций, которые привели к тому, что показывается на дисплее display. Если isPartialResult, добавьте . . .  в конец UILabel, иначе добавьте =.  Если userIsInTheMiddleOfTypingANumber, вы можете оставить UILabel, показывающую то, что было там перед тем, как пользователь начал набирать число.

Добавляем метку в стэке и создаем Outlet history:

Screen Shot 2016-05-09 at 5.20.00 PM

Добавляем формирование текста этой метки в класс ViewController при нажатии кнопки с операцией и срабатывании метода performOperation:

Screen Shot 2016-05-09 at 5.25.05 PM

Как указывалось в Задании используются переменные description и isPartialResult класса CalculatorBrain.

Нам предлагают тесты для проверки формирования нашего description:

  1. касаемся  7 +     будет показано “7 + …” (с 7, которая все еще на display)
  2. 7 + 9        будет показано “7 + …” (9 на display)
  3. 7 + 9 =     будет показано “7 + 9 =” (16 на display)
  4. 7 + 9 = √  будет показано  “√(7 + 9) =” (4 на display)
  5. 7 + 9 √      будет показано “7 + √(9) …” (3 на display)
  6. 7 + 9 √ =   будет показано “7 + √(9) =“ (10 на display)
  7. 7 + 9 = + 6 + 3 =   будет показано “7 + 9 + 6 + 3 =” (25 на display)
  8. 7 + 9 = √ 6 + 3 =   будет показано “6 + 3 =” (9 на display)
  9. 5 + 6 = 7 3    будет показано “5 + 6 =” (73 на display)
  10. 7 + =             будет показано “7 + 7 =” (14 на display)
  11. 4 × π =         будет показано “4 × π =“ (12.5663706143592 на display)
  12. 4 + 5 × 3 =    будет показано “4 + 5 × 3 =” (27 на display)
  13. 4 + 5 × 3 =  будет показано “(4 + 5) × 3 =” если вы предпочитаете(27 на display)

Для этого в тестовом файле CalculatorTests.swift формируем тесты на проверку  description:

Screen Shot 2016-05-09 at 5.33.53 PM

Запускаем приложение на тестирование

Screen Shot 2016-05-09 at 5.37.02 PM

Получаем положительный результат
Screen Shot 2016-05-09 at 5.39.04 PM

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

Добавьте кнопку C, которая очищает все ( ваш дисплей display, новую только ято добавленную метку UILabel  и т.д.). Калькулятор после нажатия кнопки C должен быть в том же состоянии, что и при старте приложения.

Эта кнопка уже работает, так как мы включили ее в состав кнопок-операций. Немного изменим функцию clear(),  которую она запускает, с учетом новых переменных для формирования описания description.

Screen Shot 2016-05-09 at 5.51.17 PM

Код находится на Github для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

Пункт 1 дополнительный

Дайте возможность пользователю нажимать кнопку  “backspace” если он ввел неверную цифру. Это вовсе не кнопка “undo,” так что если пользователь нажал неверную кнопку с операцией , то его ждет неудача ! Вам решать, как вы будете разруливать случай, когда пользователь кнопкой “backspace” полностью удалил число и все еще находится в процессе ввода числа (in the middle of entering), но оставлять дисплей display абсолютно пустым было бы не очень дружелюбно. Может оказаться, что раздел Strings and Characters Руководства по Swift будет очень полезно для решения этой проблемы.

Кнопка со знаком «» символизирует операцию “backspace” и она не привязана ни к цифровой клавиатуре, ни к операциям. Создадим для нее свой Action и соответственно метод  @IBAction func backspace(sender: UIButton) 

Screen Shot 2016-05-09 at 7.33.20 PM

В методе func backspace(sender: UIButton)  мы удаляем последний из набранных пользователем символов при вводе числа:

Screen Shot 2016-05-09 at 7.40.50 PM

Этого нет в Задании, но мы скорректируем кнопку с операцией изменения знака «±» с тем, чтобы пользователь мог вводить отрицательные числа. Если пользователь находится в середине ввода числа, то мы изменим знак этого числа и позволим ему продолжать его ввод. Но если он не находитесь в середине ввода числа, то это будет работать просто как любая другая унарная операция ( например, sqrt).
Создадим для кнопки «±» свой Action и соответственно метод  @IBAction funс plusMinus (sender: UIButton) 

Screen Shot 2016-05-09 at 7.51.45 PM

В методе funс plusMinus (sender: UIButton)  мы меняем знак на дисплее display, если пользователь находится в процессе ввода числа, а если нет, то выполняем операцию «±«.

Screen Shot 2016-05-09 at 7.59.26 PM

Поэтому надо отключить кнопку  «±» от Action performOperation.

Screen Shot 2016-05-09 at 8.01.35 PM

Пункт 2 дополнительный

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

Изменяем код для displayValue и сюда же переносим код для метки history.

Screen Shot 2016-05-09 at 9.17.55 PM

Код находится на Github для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

Пункт 3 дополнительный

Изучите документацию класса NSNumberFormatter с тем, чтобы использовать его для форматирования display, который должен показывать 6 цифр после десятичной точки  (вместо показа всех цифр, представляющих Double). Это уничтожит необходимость использования Autoshrink в display. Использование  класса NSNumberFormatter поможет избавиться от лишних “.0” , подсоединяемых к целочисленным значениям (например, показываем “4”, в не  “4.0” при извлечении квадратного корня из 16). Вы можете это использовать и для описания description в классе CalculatorBrain.

  Из-за того, что инициализация NSNumberFormatter может быть экстремально дорогой операцией в iOS, способной «поставить на колени» отдельные приложения, не хотелось бы, чтобы производительность приложения зависела от этого, особенно если используется одна и та же конфигурацию  NSNumberFormatter всюду в моем приложении. Будем использовать Singleton ( синглтон ) — ровно один экземпляр некоторого класса, глобально  доступный всем клиентам. В программистком сообществе идет большая дискуссия по поводу использования  Singleton (некоторые категорически не советуют использовать  Singleton, другие наоборот), но это паттерн, знакомый всем программистам и мы вправе его применить.

Добавляем в конец файла CalculatorBrain.swift класс CalculatorFormatter с соответствующими настройками и создаем Singleton formatter, который сможем использовать в любом месте приложения.

Screen Shot 2016-05-09 at 9.52.01 PM

Делаем соответствующие изменения в CalculatorBrain.swift  и в ViewController.swift 

Screen Shot 2016-05-09 at 9.55.15 PM

Screen Shot 2016-05-09 at 9.56.59 PM

Код находится на Github. для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

Дополнение. Работа с региональным десятичным разделителем.

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

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

Screen Shot 2015-03-25 at 8.13.14 PM

Если мы пойдем немного дальше, исследуя десятичный разделитель, то нам захочется иметь кнопку не с точкой «.», а с тем знаком, который принят в этом Регионе. Для этого сделаем outlet для кнопки с условным названием «точка»

Screen Shot 2016-05-10 at 11.26.36 AM

Screen Shot 2016-05-10 at 11.24.40 AM

Мы должны установить заголовок этой кнопки равным decimalSeparator и это можно сделать двумя способами :

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

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

Screen Shot 2015-03-24 at 11.37.09 PM

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

Screen Shot 2016-05-10 at 11.35.47 AM

Код находится на Github.

Дополнение. Адаптивный интерфейс.

Рассматривая работу приложения на iPhone 6+, мы замечаем, что в ландшафтном режиме хочется сделать другое распределение кнопок: не 5 х 7, а, например, 6 х 6.

Screen Shot 2016-05-10 at 11.54.47 AM

То же самое на iPhone 5s в портретном и ландшафтном режимах.

Screen Shot 2016-05-08 at 10.57.02 AM

Хотелось бы в ландшафтном режиме сделать другое распределение кнопок: не 5 х 7, а, например, 6 х 6.  С использованием StackViews это делается не в  Interface Builder как это было раньше, а с применением API StackViews (addArrangedSubview и removeArrangedSubview) программным образом.
Идея адаптации заключается в том, чтобы в ландшафтном режиме из строки сделать дополнительный столбец, удалив стэк №1 и распределив его кнопки по оставшимся 5-ти стэкам.

Screen Shot 2016-05-10 at 12.28.32 PM

В результате у нас появляется лишняя кнопка, которую мы разместим в правом верхнем углу полного стэка кнопок.
Давайте для всех стэков и кнопок-участников перестановки сделаем соответствующие outlets:

Screen Shot 2016-05-10 at 12.48.51 PM

С помощью lazy получения экземпляра класса получим пустую кнопку buttonBlank:

Screen Shot 2016-05-10 at 12.49.15 PM

Будем использовать метод willTransitionToTraitCollection View Controller для конфигурирования .Сompact высоты.

Screen Shot 2016-05-10 at 12.57.18 PM

Screen Shot 2016-05-10 at 12.59.47 PM

Метод configureView использует методы класса UIStackView addArrangedSubview и removeArrangedSubview. Теперь наш интерфейс выглядит по-разному в портретном и ландшафтном режимах.

Screen Shot 2016-05-10 at 1.27.49 PM

Код находится на Github для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

Дополнение. Отдельный Action для кнопки С.

В дополнительном пункте 2 этого Задания мы сделали в нашем ViewController переменную displayValue Double?, то есть Optional <Double>. Причем, если мы ей устанавливаем значение nil, то дисплей очищается, метка history очищается, то есть UI приводится в состояние, которое было при запуске приложения. У нас есть кнопка «С«, которая отнесена в разряд операций и не может воздействовать на UI, что неправильно.

Давайте сделаем для кнопки «С» отдельный Action в классе  ViewController. Для этого мы должны убрать «С» из операций. Вначале отсоединим ее от метода performOperation:.

Screen Shot 2016-05-17 at 7.21.16 PM

Убираем операцию С из класса CalсulatorBrain и делаем метод clear() public:

Screen Shot 2016-05-17 at 7.28.18 PM

. . . . . . . . . . . . . . . . . .

Screen Shot 2016-05-17 at 7.30.18 PM

. . . . . . . . . . . . . . . . . .

Screen Shot 2016-05-17 at 7.31.58 PM

. . . . . . . . . . . . . . . . . .

Screen Shot 2016-05-17 at 7.35.29 PM

Теперь с помощью CTRL-перетягивания сделаем Action clearAll(sender: UIButton):

Screen Shot 2016-05-17 at 7.38.58 PM

В clearAll вызовем clear() для brain и установим displayValue в nil:

Screen Shot 2016-05-17 at 7.45.21 PM

Код находится на Github для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

Дополнение. Код для «программируемого калькулятора» (Демо из Лекции 3)

Профессор просил вставить код для демонстрационного примера из Лекции 3 в код Задания 1.
Код размещен в классе CalculatorBrain:

Screen Shot 2016-05-17 at 8.40.35 PM

. . . . . . . . . . . . . . . . . .

Screen Shot 2016-05-17 at 8.47.29 PM

. . . . . . . . . . . . . . . . . .

Screen Shot 2016-05-17 at 8.49.25 PM

. . . . . . . . . . . . . . . . . .

Screen Shot 2016-05-17 at 8.55.14 PM

На нашем калькуляторе в настоящий момент есть две свободные кнопки: «→M» и «M«, которые мы заранее подготовили для работы с переменными в Задании 2. В этом задании мы можем их задействовать для сохранения и восстановления программы калькулятора. Кнопка  «→M» будет соответствовать кнопке «save» в Лекции 3, а кнопка «M» —  кнопке «restore«.

Screen Shot 2016-05-17 at 9.04.31 PM

Теперь можно проводить все эксперименты, которые профессор проводил на Лекции 3.
Код находится на Github.

Дополнение. Синглтон NSNumberFormatter c установкой значения свойства с помощью замыкания.

Как уже отмечалось выше экземпляр класса NSNumberFormatter нужен и в CalculatorBrain (нужно преобразовывать операнды из Double в String  и размещать их в описании description), и в ViewController (для формирования displayValue).

Поэтому создаем глобальную константу formatter все равно в каком файле, так как она не связана ни с каким классом и создавать ее мы будем очень интересным способом — путем задания значения константе или значения по умолчанию переменной путем мгновенного выполнения замыкания. В руководстве “The Swift Programming Language.”  нужно искать раздел «Setting a Default Property Value with a Closure or Function«. Указание на этот раздел есть и в Задании на чтение 2.

Вот как это выглядит:

Screen Shot 2016-05-26 at 3.13.32 PM

Обратите внимание на круглые скобки в конце. Это и есть инициализация переменной (или константы) с помощью мгновенного выполнения замыкания, оно выполняется один раз при инициализации переменной или константы. Это очень крутая вещь и о ней стоит почитать в руководстве по Swift.
Использование такого formatter не потребует указания класса ни в displayValue:

Screen Shot 2016-05-26 at 5.18.03 PM

ни в setOperand:

Screen Shot 2016-05-26 at 5.20.24 PM

Код находится на Github для Xcode 7 и Swift 2.2. Если вы установили Xcode 8, то для Swift 2.3 код находится на Github, а для Swift 3 — также на Github.

Задание 1 cs193p Spring 2016 Калькулятор. Решение. Обязательные и дополнительные пункты.: 19 комментариев

  1. «для блокировки повторного ввода точки «.» проще всего использовать функцию rangeOfString»
    Больше подходит функция: containsString

    • Вообще- то containsString — это API NSString, но, конечно, есть bringing и все работает. Но родной метод для Swift String — rangeOfString.

      • Не знал. А как понять, что метод из NSString? По описанию больше похоже, что просто удобная обёртка вокруг rangeOfString:
        ‘Returns true iff other is non-empty and contained within self by case-sensitive, non-literal search.
        Equivalent to self.rangeOfString(other) != nil’
        Да и rangeOfString декларирован недалеко в том же расширении (extension) String.

        • Да, и rangeOfString, и containsString оформлены как extension String, но это методы NSString.
          Достаточно взглянуть на класс NSString в Xcode и вы их там все обнаружите, кстати containsString — это относительно новый API (начиная с iOS 8), а rangeOfString — старый (начиная с iOS 8).
          Метод containsString намного удобнее и звучит лучше.
          Замечательно, давайте использовать containsString.

  2. В swift 3 функции rangeOfString больше нет, теперь она называется range, будьте внимательны. Татьяна Вам огромное спасибо за перевод и за гиты:)

    • Более того.В Swift 3 кардинально изменены названия всех функций с тем, чтобы уйти от традиций Objective-C, когда имя первого аргумента содержится в названии функции, как в приведенном вами примере. Сигнатура бывшей функции rangeOfString теперь range(of: String). Удалены дублирующие слова в названиях функций. В вашем случае нет смысла писать «OfString», потому что это метод String, поэтому для названия первого аргумента осталось только «of».
      Дальше больше, есть некоторые существенные концептуальные различия, особенно это касается базы данных Core Data.
      Поэтому, если вы хотите полноценно следовать этому стэнфордскому курсу, то для изучения лучше обновить ваши решения Заданий до Swift 2.3, а в версию Swift 3 посматривать для любопытства.

      • Ну да БД я еще не добрался, я все с этим заданием 1 вожусь:D Может Вы и правы и стоит действительно откатить до 2.3
        У меня к Вам очередной вопрос. В задании на изучения класса NSNumberFormatter, в гите есть строчка
        display.text = formatter.stringFromNumber(value)
        у меня все никак не получается перевести ее на новую версию, как понимаю должно выглядеть как то так: display.text = NumberFormatter().string(from: NSNumber(value))
        Но компилятор ругается и говорит «Argument labels «_:» do not match any available overloads «. Как победить эту заразу? Заранее спасибо

        • Посмотрите решения Заданий для Swift 3
          https://github.com/BestKora/Swift-3-Solution-Spring-2016
          для Swift 2.3
          https://github.com/BestKora/Swift-2.3-Solution-Spring-2016
          Вообще display.text = formatter.stringFromNumber(value) работает в Swift 3,
          где formatter — глобальная константа:
          let formatter:NSNumberFormatter = {
          let formatter = NSNumberFormatter()
          formatter.numberStyle = .DecimalStyle
          formatter.maximumFractionDigits = 6
          formatter.notANumberSymbol = «Error»
          formatter.groupingSeparator = » »
          formatter.locale = NSLocale.currentLocale()
          return formatter

          } ()

          • Это я чего-то не понимаю или по ссылке на решения для Swift 3 только пустые папки без файлов?

          • Спасибо за перевод. У меня formatter не виден в файле ViewController. Если напишу brain.formaner, то работает, но все равно не удается инициализировать decimalSeparator

          • Спасибо, я formatter размещал внутри класса, теперь все работает

  3. Татьяна, добрый день! Очень все круто описано, спасибо!
    Один короткий вопрос: не нашел, как добавлять надстрочные знаки, почему-то во вкладке edit superscript не активен. Как Вы у себя их добавляли?

    • В Xcode 7 меню Edit -> Emoji & Symbols (или раньше Special Symbols) и там все было.
      Сейчас обновилась до Xcode 8 и, действительно, там ничего нет для superscript, одни картинки.
      А в меню Edit -> Format -> Font -> Baseline, как вы правильно заметили, ничего не действует.
      Пыталась найти хоть какой-то способ задействовать этот пункт меню — все мертво.
      Пока возьмите из моего примера. Будем искать.

    • Все. Нашла. Когда высвечивается новая картинка, в самом верху справа нажимаете крохотную кнопку и все возвращается к старому формату, а там мы умеем все делать.
      Как сделать superscript для символов

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