SwiftUI
๊ฐ์ฅ ์น๊ทผํ ๊ธฐ์ ์์ ์ป์ ์ ์๋ ๊ฐ๋ ฅํ ์ ๋๋ฉ์ด์ . ์์์ SwiftUI ์์.
๊ฐ์
SwiftUI๋ ์ ์ธํ(Declarative) ์ ๋๋ฉ์ด์ ์์คํ ์ ์ ๊ณตํฉ๋๋ค. UIKit์ด โ์ด๋ป๊ฒ ์์ง์ผ์งโ(begin/commit, durations, curves)๋ฅผ ๋ช ๋ นํ์ผ๋ก ๊ธฐ์ ํด์ผ ํ๋ค๋ฉด, SwiftUI๋ โ์ต์ข ์ํ๊ฐ ๋ฌด์์ธ์งโ๋ง ์ ์ธํ๋ฉด ๋ฉ๋๋ค. ์ํ(State) ๋ณ๊ฒฝ์ ๊ฐ์งํ ํ๋ ์์ํฌ๊ฐ ์๋์ผ๋ก ์ค๊ฐ ํ๋ ์์ ๋ณด๊ฐ(interpolate)ํด ๋ถ๋๋ฌ์ด ์ ํ์ ๋ง๋ค์ด ์ค๋๋ค.
ํต์ฌ ๋ ํจํด:
- Implicit Animation โ
.animation(_:value:)modifier๋ก ํน์ ๊ฐ ๋ณ๊ฒฝ์ ์ ๋๋ฉ์ด์ ์ ๋ฐ์ธ๋ฉ - Explicit Animation โ
withAnimation { ... }๋ธ๋ก ์์์ ์ํ๋ฅผ ๋ณ๊ฒฝ, ์ํฅ๋ฐ๋ ๋ชจ๋ ๋ทฐ๊ฐ ํจ๊ป ์ ๋๋ฉ์ด์
// Implicit
Circle()
.scaleEffect(isExpanded ? 1.5 : 1.0)
.animation(.spring, value: isExpanded)
// Explicit
withAnimation(.spring(response: 0.5, dampingFraction: 0.3)) {
isExpanded.toggle()
}
์ด ๋ชจ๋์ 4๊ฐ ๋ฐ๋ชจ๋ ๊ฐ๊ฐ SwiftUI ์ ๋๋ฉ์ด์ ์ ๋ค๋ฅธ ์ธก๋ฉด์ ๋ค๋ฃน๋๋ค.
1. Spring Playground
๋ฌด์์ ๋ฐฐ์ฐ๋ โ ์ ๋๋ฉ์ด์ ์ ํ์ด๋ฐ ๊ณก์ (curve): ๊ฐ์ ๋ ์ํ ์ฌ์ด๋ผ๋ ์ด๋ป๊ฒ ์์ง์ด๋๊ฐ์ ๋ฐ๋ผ ๋๋์ด ์์ ํ ๋ฌ๋ผ์ง๋๋ค.
.spring์ SwiftUI์ ๊ฐ์ฅ ์์ฐ์ค๋ฌ์ด ๊ธฐ๋ณธ ๊ณก์ ์
๋๋ค. ๋ฌผ๋ฆฌ์ ์คํ๋ง-๋ํผ ์์คํ
์์ ์๊ฐ์ ๋ฐ์ ์ธ ๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ก ๋์์ ์ ์ดํฉ๋๋ค.
| ํ๋ผ๋ฏธํฐ | ์๋ฏธ | ์ง๊ด |
|---|---|---|
response |
๋ชฉํ์ ๋๋ฌํ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ๋๋ต์ ์๊ฐ(์ด) | ์์์๋ก ๋น ๋ฅด๊ณ , ํด์๋ก ๋๊ธ |
dampingFraction |
์ง๋(ํ๊น)์ ๊ฐ์ ๋น์จ (0~1) | 0์ ๊ฐ๊น์ธ์๋ก ๋ง์ด ํ๊น, 1์ด๋ฉด ์ ํ๊น |
blendDuration |
์งํ ์ค์ด๋ ์ ๋๋ฉ์ด์ ์ด ์ ์ ๋๋ฉ์ด์ ์ผ๋ก ์์ด๋ ์๊ฐ | ๋ณดํต 0์ด๋ฉด ์ถฉ๋ถ |
withAnimation(.spring(response: 0.5, dampingFraction: 0.3)) {
animated.toggle()
}
๋ฐ๋ชจ์์๋ Bouncy / Smooth / Snappy ํ๋ฆฌ์ ์ ๋น๊ตํ๊ณ , Custom ์ฌ๋ผ์ด๋๋ก ์ง์ ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐ์ ํด ๊ณก์ ์ ์ฐจ์ด๋ฅผ ์ง๊ด์ ์ผ๋ก ๋๋ ์ ์์ต๋๋ค.
2. Morphing Shapes
๋ฌด์์ ๋ฐฐ์ฐ๋ โ ์ปค์คํ
Shape์animatableData๋ฅผ ์ ์ํ์ฌ ๋ํ ์์ฒด๋ฅผ ๋ณํ์ํค๋ ๋ฐฉ๋ฒ.
SwiftUI์ Shape ํ๋กํ ์ฝ์ path(in:) ๋ฉ์๋๋ง ๊ตฌํํ๋ฉด ์ด๋ค ๋ํ์ด๋ ๊ทธ๋ฆด ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ animatableData: Double ํ ์ค์ ์ถ๊ฐํ๋ฉด, SwiftUI๊ฐ 0โ1 ์ฌ์ด๋ฅผ ๋ณด๊ฐํ๋ฉด์ ๋งค ํ๋ ์๋ง๋ค path(in:)์ ๋ค์ ํธ์ถํฉ๋๋ค.
struct MorphShape: Shape {
let shapeA: [CGPoint]
let shapeB: [CGPoint]
var progress: Double
var animatableData: Double {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
// shapeA[i]์ shapeB[i]๋ฅผ progress(0~1)๋ก lerpํด์ ๊ทธ๋ฆผ
}
}
๋ฐ๋ชจ์์๋ circle โ star, square โ triangle, heart โ diamond๋ฅผ ๋ชจํํ๋ฉฐ, ์ฌ๋ผ์ด๋๋ก progress๋ฅผ ์ง์ ์กฐ์ํด ๋ณด๊ฐ์ด ์ด๋ป๊ฒ ์ผ์ด๋๋์ง ๋จ๊ณ๋ณ๋ก ๋ณผ ์ ์์ต๋๋ค.
๐ก ํฌ์ธํธ:
animatableData๋ ๋จ์ผDouble๋๋AnimatablePair๊ฐ์ ์ค์นผ๋ผ ํ์ ์ด์ด์ผ ์์ ์ ์ผ๋ก ๋์ํฉ๋๋ค. ์์์Array<CGPoint>๋ฅผ ์ง์ ๋ณด๊ฐํ๋ ค๊ณ ํ๋ฉด ๋ฏธ๋ฌํ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํฉ๋๋ค.
3. Keyframe Animations
๋ฌด์์ ๋ฐฐ์ฐ๋ โ ์ฌ๋ฌ ํ๋กํผํฐ๊ฐ ๋์์, ๊ฐ๊ฐ ๋ ๋ฆฝ๋ ์๊ฐ ๊ณก์ ์ผ๋ก ์์ง์ด๋ ๋ณตํฉ ์ํ์ค.
iOS 17์ KeyframeAnimator๋ โํคํ๋ ์ + ํธ๋โ ๊ตฌ์กฐ๋ฅผ ๋์
ํ์ต๋๋ค. ํธ๋๋ง๋ค ์์ ๋ง์ ์๊ฐ/๊ณก์ ์ ๊ฐ์ง๊ณ , ๋ชจ๋ ๋์์ ์งํ๋์ด ๊ณต์ด ํ๊ธฐ๋ฉด์ ํ์ ํ๋ ๊ฐ์ ํฉ์ฑ ๋ชจ์
์ ๊น๋ํ๊ฒ ํํํ ์ ์์ต๋๋ค.
struct AnimValues {
var offset: CGFloat = 0
var scale: CGFloat = 1
var rotation: Angle = .zero
}
KeyframeAnimator(initialValue: AnimValues(), trigger: trigger) { values in
Image(systemName: "soccerball")
.offset(y: values.offset)
.scaleEffect(values.scale)
.rotationEffect(values.rotation)
} keyframes: { _ in
KeyframeTrack(\.offset) {
SpringKeyframe(-200, duration: 0.4)
SpringKeyframe(0, duration: 0.6, spring: .bouncy)
}
KeyframeTrack(\.rotation) {
LinearKeyframe(.degrees(720), duration: 1.0)
}
}
๋ฐ๋ชจ๋ bounce / orbit / shake / wave 4๊ฐ์ง ํ๋ฆฌ์ ์ ์ ๊ณตํด ํธ๋์ ์ด๋ป๊ฒ ์กฐํฉํ๋ฉด ์ด๋ค ๋ชจ์ ์ด ๋์ค๋์ง ๋น๊ตํฉ๋๋ค.
4. Phase Animations
๋ฌด์์ ๋ฐฐ์ฐ๋ โ ์ฌ๋ฌ ๋จ๊ณ๋ฅผ ์๋์ผ๋ก ์ํํ๋ ์ํ ๋จธ์ ํจํด. ์ฌ์ฉ์ ์ ๋ ฅ ์์ด ๊ณ์ ํ๋ฅด๋ ์ ๋๋ฉ์ด์ ์ ์ ํฉ.
PhaseAnimator๋ phase ๋ฐฐ์ด์ ์ ์ํ๊ณ , SwiftUI๊ฐ ์๋์ผ๋ก ๋ค์ phase๋ก ๋์ด๊ฐ๊ฒ ํฉ๋๋ค. ๋ก๋ฉ ์ธ๋์ผ์ดํฐ๋ ํ์ฑ ๋ฑ์ง์ฒ๋ผ โ๊ฐ๋งํ ์์ด๋ ๊ณ์ ์์ง์ด๋โ ํํ์ ์์ฑ๋ง์ถค.
enum LoadPhase: CaseIterable { case dot1, dot2, dot3 }
HStack {
ForEach(0..<3) { i in
Circle()
.phaseAnimator(LoadPhase.allCases) { view, phase in
let scale = (phase.rawValue == i) ? 1.3 : 0.7
view.scaleEffect(scale)
} animation: { _ in
.easeInOut(duration: 0.4)
}
}
}
๋ฐ๋ชจ์๋ ๋ก๋ฉ ์ธ๋์ผ์ดํฐ, ํ์ฑ ์๋ฆผ ๋ฑ์ง, ์ํ ์ ํ(connecting โ connected โ synced) ์ธ ๊ฐ์ง ์ฌ์ฉ ์์๊ฐ ์์ด, ๊ฐ์ API๋ก๋ ์ผ๋ง๋ ๋ค์ํ UX๋ฅผ ๋ง๋ค ์ ์๋์ง ๋ณด์ฌ์ค๋๋ค.
๐ก Spring๊ณผ์ ์ฐจ์ด: Spring์ โ๊ณก์ โ์, Phase๋ โ์ํ ์ํ์คโ๋ฅผ ๋ค๋ฃน๋๋ค. ์ฆ, Phase Animator์ ๊ฐ phase ์ ํ์ ๊ณก์ ์ผ๋ก Spring์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค โ ๋ ๊ฐ๋ ์ ์ง๊ตํฉ๋๋ค.
์ค์ ํ
Best Practices
- ๋จ์ผ ํ๋กํผํฐ ๋ณ๊ฒฝ์๋ Implicit(
.animation), ์ฌ๋ฌ ์ํ ๋์ ๋ณ๊ฒฝ์๋ Explicit(withAnimation). - Spring ๊ณก์ ์ด ๊ฐ์ฅ ์์ฐ์ค๋ฌ์ด ๊ธฐ๋ณธ๊ฐ. ์์ฌ์ค๋ฝ๋ค๋ฉด
.spring๋ง ์ฐ๋ฉด ๋๋ถ๋ถ ๊ด์ฐฎ์ต๋๋ค. - ๋จ๋ฐ์ฑ ๋ณตํฉ ๋ชจ์
โ
KeyframeAnimator. ๋ฐ๋ณต/์ํ ๋ชจ์ โPhaseAnimator.
์ฃผ์ ์ฌํญ
.animation()์value:ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ตํ์ง ๋ง์ธ์. ์ ๊ทธ๋ฌ๋ฉด ์๋ํ์ง ์์ ํ๋กํผํฐ๊น์ง ์ ๋๋ฉ์ด์ ๋ฉ๋๋ค.- ๋ณต์กํ ๋ทฐ ํธ๋ฆฌ์์ ๊ณผ๋ํ ์ ๋๋ฉ์ด์ ์ ํ๋ ์ ๋๋กญ์ ์์ธ. Instruments์ Animation Hitches ๋๊ตฌ๋ก ํ๋กํ์ผ๋งํ์ธ์.
- Simulator๋ ์ค๊ธฐ๊ธฐ๋ณด๋ค ํํ๊ฒ ๊ทธ๋ ค์ฃผ๋ ๊ฒฝ์ฐ๊ฐ ์์ผ๋, ์ฑ๋ฅ์ ๋ฐ๋์ ์ค๊ธฐ๊ธฐ์์ ๊ฒ์ฆ.