Содержание
Текст Домашнего задания на английском языке доступен на 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, то оно может работать на симуляторе и не работать на вашем реальном устройстве, если Регион на вашем приборе установлен так, что там не применяется «.» для десятичных чисел. В этом случае приложение закончится аварийно. Но приложение должно работать в любом Регионе.
Для того, чтобы не зависеть от Региона, лучше получать разделитель для десятичных чисел непосредственно в вашем калькуляторе :
Но вернемся к нашему пункту Задания и попытаемся заблокировать повторный ввод десятичного разделителя, который дальше по тексту мы будем условно называть «точкой», хотя, как мы уже знаем, это — не всегда точка. Для блокировки повторного ввода точки проще всего использовать функцию rangeOfString при вводе любой цифры (к которым относится и точка). Для этого нам понадобится всего одна строка
Случай ввода в калькулятор числа “.5” работает правильно без дополнительного кода.
Если мы пойдем немного дальше, исследуя десятичный разделитель, то нам захочется иметь кнопку не с точкой «.», а с тем знаком, который принят в этом Регионе. Для этого сделаем outlet для кнопки с условным названием «точка»
Мы должны установить заголовок этой кнопки равным decimalSeparator и это можно сделать двумя способами :
- с использованием метода viewDidLoad () «жизненного цикла» Controller (об этом будет лекция 8); этот метод срабатывает один раз при загрузке Controller
- с использованием Наблюдателя didSet{} Свойста @IBOutlet weak var tochka; этот метод вызывается один раз при установке этого outlet со storyboard
Первый способ связан с добавлением метода viewDidLoad ()
Второй метод использует Наблюдателя Свойства tochka didSet {}
Я выбрала второй метод и для проверки поставила на кнопке не «.«, а три символа «Poi«, которые должны заменится на десятичный разделитель decimalSeparator, взятый из NSNumberFormatter()
Пункт 3 обязательный
Добавьте следующие 4 кнопки с операциями:
-
sin : вычисляет sin операнда на вершине стэка.
-
cos : вычисляет cos операнда на вершине стэка.
-
sqrt : вычисляет √ (квадратный корень) операнда на вершине стэка.
-
π: вычисляет (ассоциируется) с величиной π. Например: 3 π × должно показать на дисплее (display) вашего калькулятора величину π, умноженную на 3, точно также 3 ⏎ π ×, и π 3 × должны показывать величину π, умноженную на 3.
Добавляем заголовки к безымянным кнопкам справа и проверяем, что все ли они подсоединены к Action @IBAction func operate(sender: UIButton)
Добавляем еще одну функцию performOperation для операции, соответствующей возвращению константы (входные операнды не нужны)
Первая функция для констант — у нее в параметре operation нет входных операндов. Вторая функция для унарный операций — у нее в параметре operation один входной операнд. Третья функция для бинарных операций — у нее в параметре operation два входных операнда.
Хочу обратить ваше внимание на наличие у всех функций атрибута @nonobjc. Пока не обращайте на это внимание, но если вам очень любопытно, то посмотрите пост «Дополнение к Лекции 2 — особенности кода Calculator в Swift 1.2 и Swift 2.0 или как не «застрять» на этом месте.»
Добавляем указанные в этом пункте 4 функции: sin, cos, sqrt, π.
Кроме этого добавим операцию ± замены знака числу, когда оно полностью введено, что составляет часть дополнительного пункта 3 Задания 1.
Теперь объясним, почему при вызове функций performOperation входный параметр оформлен как замыкание без круглых скобок. Дело в том, что если последним входным параметром для функции является замыкание, то его можно вынести за круглые скобки. Для функций performOperation это единственный входной параметр, поэтому скобки исчезают. Такой способ оформления вызова функций в Swift называется trailing closure (хвостовое замыкание), и как увидим дальше, используется повсеместно.
Для функций sin, cos, sqrt из стандартной библиотеки известен тип входных параметров (Double) и тип возвращаемого значения (Double), что полностью совпадает с нашими требованиями, поэтому для этих функций можно не использовать замыкание, а оставить название функции в круглых скобках. Отметим, что функции sin и cos в качестве входного параметра используют угол, представленный в радианах.
Запускаем калькулятор и проверяем предложенные в этом пункте комбинации
3 π ×
3 ↲ π x
π 3 x
Код этого пункта задания на Github.
Пункт 4 обязательный
Добавьте метку UILabel на ваш пользовательский интерфейс, которая показывает историю ввода пользователем каждого операнда и операции. Найдите подходящее место для этой текстовой метки.
Добавьте UILabel на верхнюю часть storyboard, сразу чуть ниже метки дисплея и выравняйте ее по голубым направляющим линиям слева и сверху. Задайте хвостовой, лидирующий зазоры и вертикальный зазор между метками. Кнопки нужно разместить заново: либо с помощью методики «сетки» или просто в верхний левый угол.
Создаем outlet для новой метки с именем history
@IBOutlet
weak
var
history:
UILabel
!
Добавляем метод addHistory для накопления ввода операндов и операций в тексте метки history:
Этот метод написан с учетом дополнительного пункта 2 этого Задания, в котором предполагается добавлять знак равенства «=» при нажатии пользователем на любой клавише с операциями. Поэтому предполагаем, что знак «=» может находится в тексте метки от предыдущей операции, и убираем его, чтобы избежать повторного употребления.
Метка history формируется при вводе операнда:
и операции:
Запускаем. Получаем требуемый результат.
Замечание. Установленная таким образом метка history при отсутствии в ней информации сжимается, и цифровая клавиатура калькулятора немного поднимается вверх, а при появлении информации в метке history цифровая клавиатура калькулятора немного поднимается вниз. Это не очень хорошо. Нужно поместить пробел » » ( именно » «, а не «») в метку на storyboard.
Усекаем головную часть строки history, чтобы видеть последние элементы ввода опрандов и операторов.
Есть еще один момент — лидирующие нули. Сейчас возможен ввод чисел с лидирующими нулями (например, “000” или “0367”).
Я предлагаю для уничтожения лидирующих нулей следующий код
Код этого пункта задания на Github.
Пункт 5 обязательный
Добавьте кнопку “C”, которая будет чистить все (ваш дисплей ввода, новую метку, которую вы только что добавили). При нажатии кнопки “C”. калькулятор должен находится в таком же состоянии как при старте.
Добавляю снизу еще ряд кнопок и одной из них даю название «С». Создаем Action clearAll. Внутри метода помещаем код и не забываем очистить метку history, но не пустой строкой «», а пробелом » «.
Код этого пункта задания на Github.
Пункт 1 дополнительный (extra credit)
Дайте возможность пользователю нажимать кнопку “backspace” если он ввел неверную цифру. Это вовсе не кнопка “undo,” так что если пользователь нажал неверную кнопку с операцией , то его ждет неудача! Вам решать как вы будете разруливать случай, когда пользователь кнопкой “backspace” полностью удалил число и все еще находится в процессе ввода числа , но оставлять дисплей (display) абсолютно пустым было бы не очень дружелюбно.
Одной из добавленных снизу кнопок даю название «back«. Создаем Action backSpace
Внутри метода помещаем код
Этот метод работает только если пользователь находится в процессе набора числа.
Код этого пункта задания на Github.
Пункт 2 дополнительный (extra credit)
Когда пользователь нажимает кнопку операции (operation button), разместите знак pавно = в конце текстовой метки UILabel, которая добавляется в Обязательном задании ( #4). Таким образом пользователь сможет понять, является ли число на дисплее калькулятора результатом вычислений или это число только что ввел пользователь. Не позволяйте знаку “=” многократно появляться в конце метки UILabel.
Этот пункт выполнен в обязательном пункте 4. Мы добавили знак «=» при нажатии кнопки с операцией
И предотвратили многократное его появление в методе addHistory
В результате пользователь сможет понять, является ли число на дисплее калькулятора результатом вычислений или это число только что ввел пользователь.
Код этого пункта задания на Github.
Пункт 3 дополнительный (extra credit)
Добавьте +/- операцию, которая меняет знак числа на дисплее (in the display). Будьте более внимательны с этим. Если пользователь находится в середине ввода числа, вы возможно, захотите изменить знак этого числа и позволить ему продолжать его ввод, не прибегайте к “принудительному” “enter” как в других операциях. Но если вы не находитесь в середине ввода числа, то это будет работать просто как любая другая унарная операция ( например, sqrt).
Одной из добавленных снизу кнопок даю название «±«. Создаем Action plusMinus
Внутри метода помещаем код
Код довольно простой: если вы находитесь в процессе ввода числа (userIsInTheMiddleOfTypingANumber ), то вы меняете знак. Если нет, то пересылаете вашу кнопку методу operate. Но для того, чтобы «±» работала как операция, необходимо добавить ее в операции.
Код этого пункта задания на Github.
Пункт 4 дополнительный (extra credit)
Измените вычисляемую переменную экземпляра класса displayValue так, чтобы она была Optional Double, a не Double. Его значение должно быть nil если содержимое display.text не может быть интерпретировано как Double (вам нужно использовать документацию для понимания кода NSNumberFormatter ). Установка этого значения в nil должна очищать display.
Делаем displayValue так, чтобы она была Optional Double — Double?, a не Double.
Начнем с get { }
Замечаем, что текст нашего дисплея тоже Optional, но String — String?, поэтому вначале «развертываем» display.text и получаем displayText, который имеет тип String. Затем подаем displayText как входной параметр на вход NSNumberFormatter .
«Pазвертываем» результат преобразования NSNumberFormatter и получаем displayNumber, который имеет тип NSNumber.
Делаем его Double и возвращаем.
Примечание. Результат преобразования NSNumberFormatter можно не разворачивать, а использовать так называемую chaining Optionals (цепочку Optionals), это будет в Задании 2.
Перейдем к set { }. Код достаточно простой и понятный: если newValue не равно nil, то используем String interpolation (интерполяцию строк) для преобразования числа в строковое значение, в противном случае — очищаем дисплей.
Замечание 1. Но здесь возникает та же проблема, что и с меткой history, при отсутствии в ней информации она сжимается до нулевых размеров, и появляется, если в ней что-то есть. Нужно поместить пробел » « ( именно » «, а не «») на наш дисплей. Поэтому внесем изменения в код установки displayValue = » «.
Замечание 2. Оставить просто пустым дисплей все-таки не очень дружелюбно, хотя именно в этом и состоит последний дополнительный пункт Задания, поэтому в метке history с помощью добавления » Errror» мы укажем, что в этом случае возникла ошибка.
Теперь во всем коде ViewController вместо displayValue = 0
ставим
displayValue = nil, кроме метода clearAll
Мы присваиваем значение nil вычисляемой переменной displayValue в случае, если у операции нет необходимого числа операндов, если сама операция возвращает значение, которое невозможно преобразовать в Double или если осуществлен ввод текста, который нельзя преобразовать в Double.
Теперь мы можем запускать калькулятор со значением «0» на дисплее, а в случае ошибки экран будет пустым, но появится сообщение об ошибке в строке, отображающей историю ввода операндов и операций.
Замечание 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.
Мы можем использовать NSNumberFormatter со специальными свойствами при выводе результатов на display. В NSNumberFormatter можно настраивать максимальное количество цифр после точки ( maximumFractionDigits ).В случае больших чисел нам нужны разделители групп цифр (groupingSeparator), если после точки выводится очень много знаков, то мы бы хотели ограничиться 10 знаками после точки, некоторые функции, например, √ могут возвращать значение Nan при отрицательном аргументе и нам надо указать, что это ошибка Error (notANumberSymbol). Мы получаем NSNumberFormatter со специальными свойствами при помощи функции numberFormatter()
Замечание 3. Необходимо скорректировать функцию addHistory, так как помимо знака «=» мы еще добавили строку «Error» . Необходимо уметь убирать их перед вводом нового значения, предполагая, что они не обязательно могут находится в конце строки
Код этого пункта задания на Github.
————————————————————
Задание 1 для iOS 9 и Swift 2.0
Автоматически преобразуем код Swift 1.2 в Swift 2.0 с помощью меню Edit -> Convert -> To Latest Swift Syntax…
Преобразования коснулись только работы со строками
Swift 1.2
Swift 2.0
Код для Swift 2.0 находится на Github.
На русском языке вы можете скачать здесь:
Задание 1 iOS 8.pdf
Результаты выполнения заданий можно посылать на форум Swift[ru] для обсуждения. Просьба давать развернутую оценку присланным результатам и сравнивать решения.