Ваше воображение - единственный предел
Примечание. Все используемые здесь банковские карты, конечно же, поддельные
Вступление
Прошла пара месяцев с момента выпуска iOS 13, и хотя еще слишком рано для полной поддержки в наших приложениях, стоит взглянуть на две основные платформы, которые были представлены в этой новой версии: SwiftUI и Combine. Без сомнения, это будущее разработки под iOS.
Последние несколько месяцев я экспериментировал со SwiftUI. Даже если мне все еще не хватает некоторых вещей, я был приятно удивлен тем, насколько легко настроить пользовательский интерфейс, а также анимировать его. То, что потребует большого количества кода в UIkit, может быть выполнено парой строк в SwiftUI. И что самое приятное, все проходит гладко и быстро! Он обходит Core Animation и переходит прямо к Metal.
Забудь об UIkit и двигайся дальше, потому что ты остаешься позади, приятель!
Банковские карты: краткий обзор
BankCards - это небольшой пример того, что вы можете делать с SwiftUI. Я начал это совершенно неожиданно в тот день, когда почувствовал вдохновение. Иногда, честно говоря, труднее придумать хорошую идею для экспериментов с новым фреймворком, чем с реализацией; Я уверен, что ты меня поймал.
Если вы посмотрите на стартовый проект в моем репозитории, вы, возможно, захотите сначала проверить пару файлов, поскольку именно над ними мы будем работать: Wallet.swift
и WalletView.swift
. Первый моделирует кошелек и в основном содержит кучу карточек и предоставляет вспомогательные методы. Второй - складывает карты одна над другой и применяет некоторые изменения пользовательского интерфейса, чтобы красиво отображать карты в вашем кошельке:
Результат такой:
Анимируйте переход вашего кошелька
Как я уже упоминал ранее, SwiftUI упрощает создание необычных дизайнов. Как тогда это работает? SwiftUI за кулисами использует Combine и предоставляет три оболочки свойств (новые в Swift 5.1), которые помогут вам в этом процессе. Они в основном сообщают представлению, что что-то изменилось, что вызовет обновление представления. Эти обертки свойств:
@State
: он представляет свойство представления, которое содержит некоторое состояние, на которое представление полагается для рендеринга.@ObservedObject
: это объект, свойства которого наблюдаются View.@EnvironmentObject
: аналогично@ObservedObject
, но глобально доступен для View и его подпредставлений.
Давай приступим к работе! Сейчас бумажник отображается на экране без анимации - ничего особенного не происходит. Почему бы нам не перейти на презентацию кошелька? Объявите новое свойство isPresented
и сначала установите для него значение false:
@State var isPresented = false
Измените код внутри ZStack
, чтобы отображать карты, если isPresented
истинно. Затем используйте onAppear
для переключения флага.
Теперь добавьте неявную анимацию для анимации перехода CardView
:
- Вызов
transition
, чтобы всякий раз, когда карта добавляется кWalletView
, она отображалась движущейся вверх с постепенным исчезновением. - Используйте
animation
, чтобы неявно указать, как должна анимироваться каждая карточка и ее подпредставления.
Другой способ сделать это - использовать явную анимацию:
withAnimation { self.isPresented.toggle() }
Но для простоты мы всегда будем использовать анимацию по умолчанию, хотя и с другой задержкой (вы увидите позже).
Когда вы закончите, у вас должно получиться что-то вроде этого:
Перетащите карты, чтобы отсортировать кошелек
Теперь наш кошелек выглядит лучше, но недостаточно. Вот что мы собираемся делать сейчас. Мы собираемся сделать наш CardView
перетаскиваемым, чтобы мы могли сортировать карты в кошельке, перетаскивая карту вперед или назад вверх или вниз. Для этого нам нужно добавить DragGesture
к нашему первому CardView
экземпляру:
DragGesture
поставляется с двумя блоками. onChanged
вызывается каждый раз, когда DragGesture
обновляет свой перевод, тогда как onEnded
вызывается всякий раз, когда жест завершается. Мы отключим взаимодействие с пользователем на карточках, которые находятся сзади, чтобы перетаскивать только первую.
В файле уже определены некоторые свойства и вспомогательные методы, которые мы будем использовать в этом разделе. Взгляните на offset(for:)
:
private func offset(for card: Card) -> CGFloat { guard !wallet.isFirst(card: card) else { return draggingOffset } let cardIndex = CGFloat(wallet.index(of: card)) return cardIndex * Self.cardOffset }
Это функция, которая была вызвана для компенсации карт в зависимости от их положения в кошельке. dragginOffset
объявляется в верхней части файла и инициализируется значением 0
. Мы будем использовать onChanged
, чтобы обновить его значение:
self.draggingOffset = value.translation.height
Ой, подожди! Мы получаем сообщение об ошибке:
WalletView
и любой другой View
в SwiftUI - это struct
, что означает, что они неизменяемы. К счастью для нас, SwiftUI позволяет нам изменять их свойства, если мы заключаем их в @State
.
@State var draggingOffset: CGFloat = 0
Это вызовет обновление анимированного представления, которое мы неявно откладывали. Перейдите к ForEach
внутри вас WalletView
и добавьте:
ForEach(self.wallet.cards) { // code here }.onAppear { self.shouldDelay = false }
Это заставит вспомогательный метод transitionDelay(card:)
возвращать 0
после появления ForEach
. Не забудьте заключить shouldDelay
в оболочку, чтобы избавиться от ошибки компилятора:
@State var shouldDelay = true
Наконец, используйте onEnded
, чтобы вернуть draggingOffset
в 0
:
onEnded ({ _ in self.draggingOffset = 0 })
Теперь, чтобы отсортировать карты в кошельке, добавьте в onEnded
следующий код:
let newCards = [card] + Array(self.wallet.cards.dropLast()) self.wallet.cards = newCards
Последний штрих: коснитесь карты, чтобы вынести ее на передний план
Чтобы закончить этот урок, давайте добавим TapGesture
к нашим картам, чтобы вынести выбранную карту в начало стопки. Во-первых, избавьтесь от этой строки
.disabled(!self.wallet.isFirst(card: card))
и измените DragGesture
onChanged
и onEnded
так, чтобы перетаскивалась только передняя карта:
onChanged ({ _ in if self.wallet.isFirst(card: card) { // code here } }) .onEnded ({ _ in if self.wallet.isFirst(card: card) { // code here } })
Теперь используйте onTapGesture
, чтобы снова отсортировать карточки:
.gesture( DragGesture() // ... ).onTapGesture { let newCards = self.wallet.cards.filter { $0 != card } + [card] self.wallet.cards = newCards }
Строй и беги. Не совсем получили ожидаемый результат? Попробуйте обернуть wallet
@State
. Еще ничего? Вот что происходит. WalletView
не знает, что его нужно обновить. Нам нужно добавить другую оболочку к wallet
, @ObservedObject
:
@ObservedObject var wallet: Wallet = Wallet(cards: cards)
На этом этапе компилятор должен пожаловаться: Ссылка на инициализатор «init (wrappedValue :)» в «ObservedObject» требует, чтобы «Wallet» соответствовал «ObservableObject».
Перейдите к Wallet.swift
и выполните ObservableObject
:
class Wallet: ObservableObject { // ... }
Однако что именно следует учитывать в SwiftUI? Нам нужно явно указать, какие свойства следует соблюдать, или, другими словами, какие свойства будут опубликованы:
@Published var cards: [Card]
Строй и беги. На этот раз у нас должен быть ожидаемый результат.
Куда пойти отсюда?
Вы можете сделать так много вещей, чтобы улучшить свой кошелек. В начальном проекте есть дополнительные переменные isDragging
и firstCardScale
, которые вы можете использовать для улучшения своей анимации. Попробуйте использовать эти переменные (или другие, которые вы можете придумать) для изменения rotationEffect
, offset
,… или любого другого модификатора.
Ознакомьтесь с улучшениями, которые я внес в финальную версию этого руководства.
Заключение
SwiftUI - очень мощный инструмент, который облегчит вашу жизнь и выведет ваши проекты на новый уровень. Вот несколько плюсов:
- Он использует металл и поэтому работает очень быстро и плавно.
- Легко и просто использовать.
- Понятно в отличие от базового кода XIB или раскадровки. С конфликтами легче справляться.
- Более разборчивый, чем ограничения кодирования.
- Меньше строк кодов
С другой стороны, есть некоторые минусы:
- Он довольно новый, поэтому постоянно меняется.
- Некоторым представлениям, которые широко используются в UIkit, нет эквивалента в SwiftUI. Например,
UICollectionView
илиTextView
. - Интеграция в ваше приложение, поскольку SwiftUI поддерживается только iOS 13+.