開発環境
> xcodebuild -version Xcode 13.1 Build version 13A1030d
記事中のスクリーンショットのOS: iOS15
モチベーション
(シャニマス4周年WEB CM第3弾~イルミネーションスターズ・アルストロメリア・シーズ篇~【アイドルマスター】 - YouTube より)
このアニメーションSwiftUIでどうやるんだ? 書こう!
つくったもの
コード
import SwiftUI struct ContentView: View { @State private var flag = false private let customRed = Color.init(red: 255/255, green: 186/255, blue: 214/255) private let customBlue = Color.init(red: 20/255, green: 67/255, blue: 132/255) private let customYellow = Color.init(red: 255/255, green: 224/255, blue: 18/255) private let width: CGFloat = 320 private let height: CGFloat = 180 var body: some View { ZStack { // ■□□: 赤(下から上) VStack { Spacer() HStack(spacing: 0) { Rectangle() .fill(customRed) .frame(width: width/3, height: height) .offset(y: flag ? 0 : height) .animation(.easeIn(duration: 0.475), value: flag) Rectangle() .fill(.clear) .frame(width: width/3, height: height) Rectangle() .fill(.clear) .frame(width: width/3, height: height) } Spacer() } // □□■: 黄(上から下) VStack { Spacer() HStack(spacing: 0) { Rectangle() .fill(.clear) .frame(width: width/3, height: height) Rectangle() .fill(.clear) .frame(width: width/3, height: height) Rectangle() .fill(customYellow) .frame(width: width/3, height: height) .offset(y: flag ? 0 : -height) .animation(.easeIn(duration: 0.475), value: flag) } Spacer() } // □■□: 青(中央) VStack { Spacer() Rectangle() .fill(customBlue) .frame(width: width, height: height) .scaleEffect(flag ? CGSize(width: 9.0/16.0, height: 16.0/(9.0*3.0)) : CGSize(width: 1, height: 1), anchor: .center) .rotationEffect(Angle.degrees(flag ? 90 : 0)) .animation(.easeIn(duration: 0.5), value: flag) Spacer() } // 背景 VStack(spacing: 0) { Rectangle() .fill(.black) Rectangle() .fill(.clear) .frame(width: width, height: height) Rectangle() .fill(.black) } // ボタン VStack { Spacer() Button("toggle") { flag.toggle() } } } } }
ハマったところ
scaleEffect(_:anchor:) に渡すのは比率
scaleEffect(_:anchor:)
に渡すのは比率のため、比率を計算する必要がある。
// □■□: 青(全体から中央) VStack { Rectangle() ... .scaleEffect(flag ? CGSize(width: 9.0/16.0, height: 16.0/(9.0*3.0)) : CGSize(width: 1, height: 1), anchor: .center) ... }
rotationEffect、scaleEffectの定義順
(定義順というより宣言順?)
rotationEffectとscaleEffectを一つのViewに適用させたい場合、定義順で評価順が変わるため比率の計算をする場合に注意。
rotationEffectをA, scaleEffectをBとすると、
A -> B | B -> A |
---|---|
import SwiftUI struct ContentView: View { @State private var flag = false var body: some View { VStack { Spacer() Rectangle() .fill(.blue) .frame(width: 50, height: 50) // A .rotationEffect(Angle.degrees(flag ? 90 : 0)) // B .scaleEffect(flag ? CGSize(width: 3.0, height: 1.0) : CGSize(width: 1, height: 1), anchor: .center) .animation(.easeIn(duration: 0.5), value: flag) Spacer() Button("toggle") { flag.toggle() } } } }