1. Add hiking data to the app
제공하는 json 데이터 파일을 프로젝트에 다운 받은 다음 데이터 양식에 맞게 구조체를 구현한다.
struct Hike: Codable, Hashable, Identifiable {
var id: Int
var name: String
var distance: Double
var difficulty: Int
var observations: [Observation]
static var formatter = LengthFormatter()
var distanceText: String {
Hike.formatter
.string(fromValue: distance, unit: .kilometer)
}
struct Observation: Codable, Hashable {
var distanceFromStart: Double
var elevation: Range<Double>
var pace: Range<Double>
var heartRate: Range<Double>
}
}
이번에는 튜토리얼을 위해 이미 구현해놓은 view들을 제공한다. 프로젝트로 다운 받는다.
2. Add animations to individual views
우선 애니메이션을 적용할 view 가 Equatable 이 아닐 경우 animation(_:value:) 메서드로 애니메이션을 조작할 수 있다.
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(hike: hike, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button {
showDetail.toggle()
} label: {
Label("Graph", systemImage: "chevron.right.circle")
.labelStyle(.iconOnly)
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.animation(nil, value: showDetail)
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
.animation(.spring(), value: showDetail)
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
이펙트를 따로 주고 싶다면 중간 중간 추가로 animation(_:value:)메서드를 호출한다.
3. Animate the effects of state changes
다음으로는 state의 value을 변경해서 애니메이션을 작동시키는 방법을 사용해보자.
withAnimation을 state가 변화하는 코드를 감싸보자. 그러면 state의 변화로 인해 영향을 받는 views 들이 transition을 갖고 변화한다.
Button {
withAnimation(.easeInOut(duration: 4)) {
showDetail.toggle()
}
}
4. Customize view transitions
각각의 view에 transition 설정을 transition(_:) modifier을 통해 커스터마이징할 수 있다.
if showDetail {
HikeDetail(hike: hike)
.transition(.slide)
}
Extension으로 Transition을 커스터마이징해보자.
extension AnyTransition {
static var slideFromTrailing: AnyTransition {
AnyTransition.move(edge: .trailing)
}
static var moveAndFade: AnyTransition {
.asymmetric(
insertion: .move(edge: .trailing).combined(with: .opacity),
removal: .scale.combined(with: .opacity)
)
}
}
5. Compose animations for complex effects
Animation을 좀 더 복잡하게 설정해보자.
extension Animation {
static func ripple(index: Int) -> Animation {
Animation.spring(dampingFraction: 0.5)
.speed(2)
.delay(0.03 * Double(index))
}
}
struct HikeGraph: View {
var hike: Hike
var path: KeyPath<Hike.Observation, Range<Double>>
var color: Color {
switch path {
case \.elevation:
return .gray
case \.heartRate:
return Color(hue: 0, saturation: 0.5, brightness: 0.7)
case \.pace:
return Color(hue: 0.7, saturation: 0.4, brightness: 0.7)
default:
return .black
}
}
var body: some View {
let data = hike.observations
let overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: path] })
let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!
let heightRatio = 1 - CGFloat(maxMagnitude / magnitude(of: overallRange))
return GeometryReader { proxy in
HStack(alignment: .bottom, spacing: proxy.size.width / 120) {
ForEach(Array(data.enumerated()), id: \.offset) { index, observation in
GraphCapsule(
index: index,
color: color,
height: proxy.size.height,
range: observation[keyPath: path],
overallRange: overallRange
)
.animation(.ripple(index: index))
}
.offset(x: 0, y: proxy.size.height * heightRatio)
}
}
}
}
func rangeOfRanges<C: Collection>(_ ranges: C) -> Range<Double>
where C.Element == Range<Double> {
guard !ranges.isEmpty else { return 0..<0 }
let low = ranges.lazy.map { $0.lowerBound }.min()!
let high = ranges.lazy.map { $0.upperBound }.max()!
return low..<high
}
func magnitude(of range: Range<Double>) -> Double {
range.upperBound - range.lowerBound
}
#Preview {
let hike = ModelData().hikes[0]
return Group {
HikeGraph(hike: hike, path: \.elevation)
.frame(height: 200)
HikeGraph(hike: hike, path: \.heartRate)
.frame(height: 200)
HikeGraph(hike: hike, path: \.pace)
.frame(height: 200)
}
}
'iOS > SwiftUI Tutorial' 카테고리의 다른 글
SwiftUI Tutorial 8 - Interfacing with UIKit (0) | 2024.07.10 |
---|---|
SwiftUI Tutorial 7 - Composing complex interfaces (0) | 2024.07.09 |
SwiftUI Tutorial 5- Drawing paths and shapes (0) | 2024.07.07 |
SwiftUI Tutorial 4 - Handling user input (0) | 2024.07.06 |
SwiftUI Tutorial 3 - Building lists and navigation (0) | 2024.07.05 |