[SwiftUI] Animating Views and Transitions

์›๋ฌธ: ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€

Animating Views and Transitions

์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด๋ณด๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜! storyboard๋กœ ๋งŒ๋“ค ๋•Œ๋Š” view๋ฅผ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ•  ์ผ์ด ๋งŽ์ด ์—†์–ด์„œ ๊ฐ„๋‹จํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜๋งŒ ์ถ”๊ฐ€ํ•ด๋ดค์—ˆ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” ์–ด๋–ค Animation๊ณผ Transition์„ ์‚ฌ์šฉํ• ๊นŒ?

Add Hiking Data to the App

  1. animation์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์ „์— animation์„ ์ ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

ํŠœํ† ๋ฆฌ์–ผ ํŽ˜์ด์ง€์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๋‹ค์šด๋ฐ›์•„ Resource ํด๋”์— ์žˆ๋Š” hikeData.json ํŒŒ์ผ์„ ์˜ฎ๊ฒจ์ค€๋‹ค.

json ํŒŒ์ผ ์†์— ์—ฌ๋Ÿฌ ๊ฐ’๋“ค์ด ๋“ค์–ด์žˆ๋‹ค. (name, id, distance, difficulty ๋“ฑ๋“ฑ)

  1. json ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•  Hike.swift ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

Landmark ์ฒ˜๋Ÿผ Codable์„ ์ฑ„ํƒํ• ๊ฑด๋ฐ Hike๋„ Landmark์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ key ๊ฐ’์ด ๋งค์นญ๋˜์–ด์•ผ ํ•œ๋‹ค.

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>
    }
}

observations์˜ ๋‚ด๋ถ€์— elevation, pace, heartRate๊ฐ€ ๋“ค์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— Observation์€ ๋ณ„๋„์˜ struct๋กœ ์ƒ์„ฑํ–ˆ๋‹ค.

  1. ModelData์—์„œ Hide ๋ฐ์ดํ„ฐ ๋กœ๋“œ

image

landmarks์™€ ๋‹ค๋ฅด๊ฒŒ hide ๋ฐ์ดํ„ฐ๋Š” ๋กœ๋“œ๋œ ์ดํ›„ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ์ผ์ด ์—†์œผ๋ฏ€๋กœ @Published๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

  1. Hike ํด๋” ์˜ฎ๊ธฐ๊ธฐ

๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ํ”„๋กœ์ ํŠธ์˜ Resource ํŒŒ์ผ ๋‚ด๋ถ€์— ์žˆ๋Š” Hike ํด๋”๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

Animation์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์• ํ”Œ์—์„œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ค€ ๊ทธ๋ž˜ํ”„ ์ฝ”๋“œ.

HikeView ํŒŒ์ผ์„ ์—ด์–ด preview๋ฅผ ์‹คํ–‰์‹œ์ผœ๋ณด๋ฉด ๊ทธ๋ž˜ํ”„๊ฐ€ ์ž˜ ๋‚˜์˜ค๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Add Animations to Individual Views

animation(_:) , animation(_:value:) ์‚ฌ์šฉํ•˜๊ธฐ

  1. Button Rotation

HikeView์—์„œ DetailView๋ฅผ ๋…ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ ๋ฒ„ํŠผ(>)์„ ๋ˆŒ๋ €์„ ๋•Œ ํšŒ์ „ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ณด์ด๋„๋ก ์ˆ˜์ •ํ•œ๋‹ค.

  • .animation(.easeInOut, value: showDetail) ์ถ”๊ฐ€ํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋ณ€๊ฒฝ๋œ๋‹ค.
As Is To Be
Aug-26-2022 13-06-47 Aug-26-2022 13-05-22

ํšŒ์ „์ด ๋˜๋Š” ๊ฒŒ ๋ณด์ธ๋‹ค..!!!

.easeInOut ๋Œ€์‹  .spring()์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

  • .scaleEffect(showDetail ? 1.5 : 1)๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด detail ๊ทธ๋ž˜ํ”„๊ฐ€ ๋‚˜์˜ฌ ๋•Œ ๋ฒ„ํŠผ์ด ์ปค์ง„๋‹ค. (1.5๋ฐฐ)

Animate the Effects of State Changes

์—ฌ๊ธฐ์„œ๋Š” withAnimation ์ด๋ผ๋Š” ์นœ๊ตฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

์š”๋ ‡๊ฒŒ button์˜ toggle ๋ฉ”์„œ๋“œ๋ฅผ withAnimation์œผ๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด

๊ต‰์žฅํžˆ detail ๊ทธ๋ž˜ํ”„๊ฐ€ ์Šค๋ฌด์Šคํ•˜๊ฒŒ ์—ด๋ฆฐ๋‹ค.

withAnimation์—๋„ .easyOut ๊ฐ™์€ ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. animation์ด ์™„๋ฃŒ๋˜๋Š” ๋ฐ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„๋„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์œ„ ์ฝ”๋“œ์˜ withAnimation์„ withAnimation(.easeInOut(duration: 4)) ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ๋œ๋‹ค.

Customize View Transitions

transition(_:) ์‚ฌ์šฉํ•˜๊ธฐ

detail ํ™”๋ฉด์„ ๋…ธ์ถœํ•  ๋•Œ transition์— slide ๊ฐ’์„ ์ฃผ๋ฉด

๊ต‰์žฅํžˆ ๋Š๋ฆฌ๊ฒŒโ€ฆ ๊ทธ๋ž˜ํ”„๊ฐ€ ๋ฐ€๋ ค๋“ค์–ด์˜จ๋‹ค.

View์˜ Transition์„ ์ปค์Šคํ…€ํ•  ๋•Œ AnyTransition ์— ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด extension์— moveAndFade๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด

extension AnyTransition {
    static var moveAndFade: AnyTransition {
        AnyTransition.slide
    }
}

๋™์ผํ•œ ์ฝ”๋“œ๋ฅผ ์ด๋ ‡๊ฒŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ง€๊ธˆ์€ ๊ฐ„๋‹จํ•œ transition์ด์ง€๋งŒ ํ›„์— ๋ณต์žกํ•œ transition์„ ๋งŒ๋“ค์–ด ์—ฌ๋Ÿฌ ๋ทฐ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. (์•„์ฃผ ์ข‹์€๋ฐ..?)

Compose Animations for Complex Effects

  1. canvas ํ•€๊ณ ์ •

๋‹ค๋ฅธ ํŒŒ์ผ๋กœ ๋„˜์–ด๊ฐ”์„ ๋•Œ๋„ ํ”„๋ฆฌ๋ทฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก canvas๋ฅผ ๊ณ ์ •ํ•œ๋‹ค. + showDetail์„ true๋กœ ์„ค์ •ํ•œ๋‹ค.

  1. Animation์— ripple() ์ถ”๊ฐ€
extension Animation {
    static func ripple() -> Animation {
        Animation.default
    }
}

GraphCapsule์— .animation(.ripple())๊นŒ์ง€ ์ ์šฉํ•˜๋ฉด ๊ทธ๋ž˜ํ”„์— animation์ด ์ ์šฉ๋œ๋‹ค.

  1. Animation.default ๋Œ€์‹  spring ์ ์šฉ
static func ripple() -> Animation {
        Animation.spring(dampingFraction: 0.5)
            .speed(2)
    }

์•„๊นŒ ๋ฒ„ํŠผ์— ์ ์šฉํ•  ๋• ๋ชฐ๋ž๋Š”๋ฐ ๊ทธ๋ž˜ํ”„๊ฐ€ ํ™•์‹คํžˆ ํŠ€๋Š” ๊ฒŒ ๋ณด์ธ๋‹ค.

  1. ์ตœ์ข… ์• ๋‹ˆ๋ฉ”์ด์…˜
static func ripple(index: Int) -> Animation {
        Animation.spring(dampingFraction: 0.5)
            .speed(2)
            .delay(0.03 * Double(index))
    }

๊ทธ๋ž˜ํ”„์˜ ๊ฐ index๋ณ„๋กœ ๋”œ๋ ˆ์ด๋ฅผ ์ฃผ๋„๋ก ํ•˜๋ฉด ์™„์„ฑ.

๋ฒˆ์™ธ

  • moveAndFade ์ฝ”๋“œ ๋ณ€๊ฒฝ
    .asymmetric(
              insertion: .move(edge: .trailing).combined(with: .opacity),
              removal: .scale.combined(with: .opacity)
          )
    

๋งˆ๋ฌด๋ฆฌ

transition์ด๋‚˜ animation์„ ๋งŒ์งˆ ์ผ์ด ์ž˜ ์—†์—ˆ๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ์ปค์Šคํ…€๋„ ํ•˜๊ณ  extension์œผ๋กœ ๊ด€๋ฆฌ๋„ ํ•˜๋‹ˆ๊นŒ ๊ฝค ์ข‹์•„๋ณด์ธ๋‹ค. ๋‚˜์ค‘์— ๋”ฐ๋กœ ์•ฑ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉํ•ด๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค. (๋ฌผ๋ก  ์‚ฝ์งˆ์„ ๋งŽ์ด ํ•˜๊ฒ ์ง€๋งŒโ€ฆ)

Leave a comment