SwiftUI: Wordle 1/2
Всем привет, думаю вы уже слышали об завирусившейся игре wordle, ну и предлагаю не долго думая написать свой клон!
Запускаем Xcode, далее File>New>Project и выбираем iOS app (в целом можно выбрать Multiplatform, но в рамках статьи я буду говорить только про iOS) и SwiftUI в меню Interface.
Давайте начнем с первого кирпичика игры, view которое будет отбражать введеную букву. Создадим папку view, а в ней файл CharView.swift и сразу создаим для view модель. Создадим папку models и файл Char.swift.
Тут думаю все просто, Char состоит из 2 полей, title в котором мы будем хранить введеный символ и highlight для выбора подсветки поля.
Теперь, когда у нас есть модель, давайте вернемся к файлу CharView.swift и пропишем код:
Не забывайте добавлять import SwiftUI!
И если мы все сделали верно, то в previews увидим:
Итак, мы можем отобразить введенный символ, теперь давайте создадим view, которое будет отвечать за слово целиком, для этого создадим в папке Models файл Word.swift:
Тут все просто слово состоит из букв, а поле wrongAnswerCounter нам нужно будет для анимаций, к нему вернемся позже. Таке сразу создадим 2 метода инициализации, один просто принимает массив букв, а вот второй принимает параметр numberOfChars по значению которого просто создает массив и “пустых символов”.
Теперь перейдем ко view, в папке View создадим WordView.swift:
И в preview увидим:
Вернемся к параметру wrongAnswerCounter из структуры Word, при вводе неверной догадки мы будем “трясти наше слово”, для этого во view model мы будем увеличивать значение wrongAnswerCounter. Для работы анимации мы добавили 2 строчки modifier и animation, давайте разберем их. Начнем с animation, данный метод указывает при изменении какого значения необходимо запускать анимацию, в данном случае мы передаем wrongAnswerCounter. Второй метод modifier в который мы передаем свою кастомную анимацию. Давайте ее реализуем, создадим папку Common, а в ней файл Shake.swift:
Реализацию анимации я подсмотрел тут, поэтому в рамках статьи не буду разбирать ее подробно.
Теперь, когда мы можем отображать слово целиком, пора сделать view с игровым полем, в папке View создадим файл BoardView.swift:
Тут, я думаю все понятно, мы просто объеденили несколько WordView в вертикальный стек. Давайте посмотрим что получилось:
Отлично, теперь остался последний шаг, нам нужна клавиатура!
Для этого в папке Models созадим 2 файла: KeysRow.swift и Keys.swift
Разберем их, KeysRow это просто массив из Char и является вспомогательным для Keys. Я изначально хотел сделать клавиатуру универсальной, для любого языка, поэтому структура Keys нужна для указания символов клавиатуры, в методе defaultKeys вы можете увидеть пример инициализации, мы должны просто передать строки с символами в правильном порядке, а метод initFrom(keysStrs: [String]) разобьет каждую строку на символы. Думаю идея понятна, вернемся к UI.
В папке View создадим KeyboardView.swift:
И сразу посмотрим что у нас получилось:
В результате у нас получилась клавиатура, похожая на нативную. Думаю по коду все понятно, за исключением парамера width, он нам нужен для расчета размера клавиш, а передаем мы его из вне, для того чтобы отказаться от использования GeometryReader внутри KeyboardView, так как GeometryReader имеет ряд неудобных моментов, при работе с ним, поэтому лучше использовать его на root view.
Теперь когда у нас есть поле для ввода символов и клавиатура, давайте объединим все это!
Создадим папку ViewModels, а в ней файл GameViewModel.swift
Теперь давайте создадим модель для нашего загаданого слова — Ridle, в папке Models создадим Ridle.swift:
Так же нам нужно создать 2 сервиса RidleProvider и LastRidleIdStore. Первый будет имитировать получение загаданного слова по сети, а второй будет отвечать за хранение id последнего отгаданного слова. Давайте создадим папку Services а в ней файлы RidleProvider.swift и LastRidleIdStore.swift:
Тут мы просто через 1 секунду возвращаем рандомное слово, для того что бы в рамках одной сессии пользователь получал одно и тоже слово, используем статическую переменную riddle (в оригинальной игре вы можете отгадывать только 1 загадку в день)
Тут мы просто храним id последнего отгаданного слова в UserDefaults.
Теперь, когда у нас есть все необходимое, в папке View создадим ContentView.swift:
Запустим и посмотрим что получилось!
На этой ноте предлагаю закончить эту часть, в следующей части мы заменим все EmptyView() и добавим шиммеры!
Продолжение в части 2