На WWDC’23 была представлена анимация SF-символов. Имеется 7 различных предустановок анимации: появление, исчезновение, отскок, масштабирование, импульс, смена цвета и замена.
Каждый из этих пресетов имеет свое применение:
- Анимация появления используется при первом отображении символа в пользовательском интерфейсе.
- Анимация исчезновения используется при удалении символа из пользовательского интерфейса.
- Анимация отскока используется для сообщения пользователю о том, что действие было запущено или успешно завершено.
- Анимация масштабирования подходит для выделения элементов в пользовательском интерфейсе, например при наведении курсора на элемент. Она также может использоваться для того, чтобы сообщить пользователю о том, что действие совершилось (вспомните кнопку, которая находится в нажатом состоянии).
- Импульсная анимация — отличный способ показать, что какое-то действие продолжается. Например, во время записи видеоролика символ кнопки записи пульсирует.
- Анимация смены цвета передает состояние, которое меняется со временем (например, уровень сигнала Wi-Fi).
- Анимация замены полезна для передачи информации об изменении функции. Например, кнопка воспроизведения сменяется кнопкой паузы.
Анимация символов применяется с помощью symbolEffect(_:options:value:) для дискретных эффектов, symbolEffect(_:options:isActive:) для бесконечных эффектов и view-модификаторами contentTransition(_:) и transition() с новым symbolEffect. Дискретные эффекты являются on-off эффектами, в то время как бесконечные эффекты изменяют символ неограниченно и требуют явного прекращения. Отскок, пульсация и смена цвета это дискретные эффекты, а пульсация, замена, появление и исчезновение — бесконечные. Появление и исчезновение также поддерживают эффекты перехода, а замена — эффект перехода по содержимому. Поэтому при выборе модификатора вида необходимо помнить об этом.
Далее рассмотрим некоторые примеры и способы применения этих модификаторов вида.
Появление и исчезновение
struct AppearDisappearView: View { @State private var isDrizzleHidden = true var body: some View { VStack(spacing: 16) { HStack { Image(systemName: "cloud.fill") Image(systemName: "cloud.drizzle.fill") .symbolEffect(.disappear, isActive: isDrizzleHidden) Image(systemName: "cloud.heavyrain.fill") } .imageScale(.large) HStack { Image(systemName: "cloud.fill") if !isDrizzleHidden { Image(systemName: "cloud.drizzle.fill") .transition(.symbolEffect(.automatic)) } Image(systemName: "cloud.heavyrain.fill") } .imageScale(.large) Button("Appear/Disapper") { isDrizzleHidden.toggle() } } } }
Отскок
struct BounceView: View { @State private var value = 0 var body: some View { VStack(spacing: 16) { HStack { Image(systemName: "snowflake") .symbolEffect(.bounce, value: value) Image(systemName: "snowflake") .symbolEffect(.bounce, options: .speed(0.1), value: value) Image(systemName: "snowflake") .symbolEffect(.bounce, options: .repeat(2), value: value) } .imageScale(.large) Button("Bounce") { value += 1 } } } }
Масштабирование
struct ScaleView: View { @State private var isActive = false var body: some View { VStack(spacing: 16) { HStack { Image(systemName: "drop.fill") .symbolEffect(.scale.up, isActive: isActive) Image(systemName: "drop.fill") .symbolEffect(.scale.down, options: .speed(0.1), isActive: isActive) Image(systemName: "drop.fill") .symbolEffect(.scale.down, options: .speed(5), isActive: isActive) } .imageScale(.large) Button("Scale") { isActive.toggle() } } } }
Пульсация
struct PulseView: View { @State private var isActive = false var body: some View { VStack(spacing: 16) { HStack { Image(systemName: "moonphase.first.quarter") .symbolEffect(.pulse, isActive: isActive) Image(systemName: "moonphase.first.quarter") .symbolEffect(.pulse, options: .speed(0.1), isActive: isActive) Image(systemName: "moonphase.first.quarter") .symbolEffect(.pulse, options: .speed(5), isActive: isActive) } .imageScale(.large) Button("Pulse") { isActive.toggle() } } } }
Смена цвета
struct VariableColorView: View { @State private var isActive = false var body: some View { VStack(spacing: 16) { HStack { Image(systemName: "rainbow") .symbolEffect(.variableColor, options: .speed(0.1), isActive: isActive) Image(systemName: "rainbow") .symbolEffect(.variableColor.iterative, options: .speed(0.1), isActive: isActive) Image(systemName: "rainbow") .symbolEffect(.variableColor.iterative.reversing, options: .speed(0.1), isActive: isActive) } .symbolRenderingMode(.multicolor) .imageScale(.large) Button("Variable Color") { isActive.toggle() } } } }
Замена
struct ReplaceView: View { @State private var isActive = false var body: some View { VStack(spacing: 16) { HStack { Image(systemName: isActive ? "pause.circle.fill" : "play.circle.fill") .contentTransition(.symbolEffect(.replace)) Image(systemName: isActive ? "pause.circle.fill" : "play.circle.fill") .contentTransition(.symbolEffect(.replace.offUp)) Image(systemName: isActive ? "pause.circle.fill" : "play.circle.fill") .contentTransition(.symbolEffect(.replace.upUp)) } .imageScale(.large) Button("Replace") { isActive.toggle() } } } }
Пример проекта SymbolsAnimationExample (Xcode 15 beta 6)
Если это было полезно, сообщите мне об этом по адресу Mastodon@toomasvahter или в Twitter @toomasvahter. Не стесняйтесь подписываться на RSS-ленту. Спасибо, что читаете.