---
# System prepended metadata

title: Drawing and Animation

---

# Drawing and Animation

建議搭配 Apple Tutorial 服用，下載官方範例程式，從 StartingPoint 開始做起，前置作業的檔案都可以在 Resources 資料夾下找到。

## Drawing Paths and Shapes
[🔗 Apple Tutorial](https://developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes)

### 畫直線
教學中的六角形有點嚇人，我們可以先從畫四角形開始。`move` 可以想成移動你的畫筆到定點位置，`addLine` 是從目前位置畫一條直線到目標位置。
```swift=
struct Canvas: View {
    let gradientEnd = Color(hex: 0x0093E9)
    let gradientStart = Color(hex: 0x80D0C7)
    
    var body: some View {
        Path { path in
            path.move(to: CGPoint(x: 100, y: 300))
            
            path.addLine(to: CGPoint(x: 300, y: 300))
            
            path.addLine(to: CGPoint(x: 300, y: 100))
            
            path.addLine(to: CGPoint(x: 100, y: 100))
        }
    }
}
```
1. [Path](https://developer.apple.com/documentation/swiftui/path) The outline of a 2D shape.
2. [move(to:)](https://developer.apple.com/documentation/swiftui/path/move(to:)) Begins a new subpath at the specified point.
3. [addLine(to:)](https://developer.apple.com/documentation/swiftui/path/addline(to:)) Appends a straight line segment from the current point to the specified point.

填滿顏色，會將終點連接到起始點，所組成的區域填滿顏色
```swift=
.fill(.blue)
```
也可以上漸層色，漸層方式有很多種，可以通過下方連結了解更多
```swift=
// 線性漸層
.fill(.linearGradient(colors: [gradientStart, gradientEnd],
                      startPoint: .topLeading,
                      endPoint: .bottomTrailing))

// 中心向外漸層
.fill(.radialGradient(colors: [gradientStart, gradientEnd],
                      center: .center,
                      startRadius: 15,
                      endRadius: 80))
```
1. [ShapeStyle](https://developer.apple.com/documentation/swiftui/shapestyle) A color or pattern to use when rendering a shape.
![](https://i.imgur.com/vbVRAok.png)

填滿外框
```swift=
.stroke(Color(hex: "0093E9"), lineWidth: 10)
```
![](https://i.imgur.com/WvSDZIK.png)

### 畫曲線
一元二次方程式的曲線
```swift=
Path { path in
    path.move(to: CGPoint(x: 100, y: 100))
    
    path.addQuadCurve(to: CGPoint(x: 300, y: 300), control: CGPoint(x: 100, y: 300))
}
.stroke(Color(hex:"0093E9"), lineWidth: 10)
```
1. [addQuadCurve(to:control:)](https://developer.apple.com/documentation/swiftui/path/addquadcurve(to:control:)) Adds a quadratic Bézier curve to the path, with the specified end point and control point.
![](https://i.imgur.com/JBjaN7S.png)

一元三次方程式的曲線
```swift=
 path in
    path.move(to: CGPoint(x: 0, y: 0))

    path.addCurve(to: CGPoint(x: 400, y: 400),
                  control1: CGPoint(x: 0, y: 400),
                  control2: CGPoint(x: 400, y: 0))
}
.stroke(Color(hex:"0093E9"), lineWidth: 10)
```
1. [addCurve(to:controlPoint1:controlPoint2:)](https://developer.apple.com/documentation/uikit/uibezierpath/1624357-addcurve) Appends a cubic Bézier curve to the path.
![](https://i.imgur.com/Ly24JdN.png)


### 使用 GeometryReader 動態調整長寬
目前都是 hardcore 定義圖形的位置，要根據 superview 來計算長度，就要使用 GeometryReader，`geometry` 型態是 `GeometryProxy`，可以取得 superview 的長寬。
```swift=
GeometryReader { geometry in
    let width = geometry.size.width
    let height = geometry.size.height
                
    Path { path in
        path.move(to: CGPoint(x: width * 0.1, y: height * 0.1))
        
        path.addQuadCurve(to: CGPoint(x: width * 0.9, y: height * 0.9), control: CGPoint(x: width * 0.1, y: height * 0.9))
    }
    .stroke(Color(hex:"0093E9"), lineWidth: 10)
}
```
1. [GeometryReader](https://developer.apple.com/documentation/swiftui/geometryreader) A container view that defines its content as a function of its own size and coordinate space.
2. [GeometryProxy](https://developer.apple.com/documentation/swiftui/geometryproxy) A proxy for access to the size and coordinate space (for anchor resolution) of the container view.
![](https://i.imgur.com/cPSVS17.png)

### 旋轉畫面
搭配 `overlay` 看出選擇不同的 `anchor` 變化。
```swift=
Rectangle()
    .frame(width: 100, height: 200)
    .foregroundColor(.gray)
    .overlay {
        Rectangle()
            .frame(width: 100, height: 200)
            .foregroundColor(gradientStart)
            .rotationEffect(Angle(degrees: 40), anchor: .bottom)
    }
```
1. [rotationEffect](https://developer.apple.com/documentation/swiftui/view/rotationeffect(_:anchor:)) - Rotates this view’s rendered output around the specified point.
![](https://i.imgur.com/ua1s4fZ.png)

## Animating Views and Transitions
[🔗 Apple Tutorial](https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions)

### 使用 animation() 元件顯示動畫
當`animation()`的`value`改變時，會驅動Animation
```swift=
Label("Graph", systemImage: "chevron.right.circle")
    .labelStyle(.iconOnly)
    .imageScale(.large)
    .rotationEffect(.degrees(showDetail ? 90 : 0))
    .scaleEffect(showDetail ? 1.5 : 1)
    .padding()
    .animation(.spring(), value: showDetail)
```
1. [Label](https://developer.apple.com/documentation/swiftui/label) - A standard label for user interface items, consisting of an icon with a title.
Availability
3. [scaleEffect](https://developer.apple.com/documentation/swiftui/text/scaleeffect(_:anchor:)-9qu74) - Scales this view’s rendered output by the given amount in both the horizontal and vertical directions, relative to an anchor point.
4. [animation](https://developer.apple.com/documentation/swiftui/view/animation(_:value:)) - Applies the given animation to this view when the specified value changes.

Animation有很多種，可以都玩玩看效果，想知道有哪些可以查看 [Animation](https://developer.apple.com/documentation/swiftui/animation)，`spring(response:dampingFraction:blendDuration)`滿有趣的，會有彈簧效果。
```swift=
.animation(.easeInOut, value: value)
.animation(.linear, value: value)
.animation(.spring(), value: value)
```
調整動畫執行時間
```swift=
.animation(.easeInOut(duration: 3), value: value)
```
重複次數，autoreverses 會帶著動畫回到開始，反之，會直接回到開始的狀態
```swift=
.animation(.easeInOut.repeatCount(5, autoreverses: true), value: value)
.animation(.easeInOut.repeatForever(autoreverses: true), value: value)
```

### 使用 withAnimation { } 顯示動畫
為顯示 HikeDetail 和 Label 加上特效，以例子為例，會有淡入淡出效果，另外，Label 因為有額外設定特效，會以該設定為準。
```swift=
Button {
    withAnimation(.easeInOut) {
        showDetail.toggle()
    }
} label: {
    Label("Graph", systemImage: "chevron.right.circle")
        .labelStyle(.iconOnly)
        .imageScale(.large)
        .rotationEffect(.degrees(showDetail ? 90 : 0))
        .scaleEffect(showDetail ? 1.5 : 1)
        .padding()
        .animation(.easeInOut.repeatForever(autoreverses: true), value: showDetail)
}

if showDetail {
    HikeDetail(hike: hike)
}
```

### Customize Animation
透過 extension Animation 可以定義客製的動畫，官方範例中，帶入 index 製造不同的延遲效果，讓長條圖有波動的效果。
```swift=
extension Animation {
    static func ripple(index: Int) -> Animation {
        Animation.spring(dampingFraction: 0.5)
            .speed(2)
            .delay(0.03 * Double(index))
    }
}
```

### Customize View Transitions
上面提到 HikeDetail 的轉場特效預設是淡入淡出，可以透過 `.transition` 設定，[AnyTransition](https://developer.apple.com/documentation/swiftui/anytransition) 有幾種選擇，像是 `opacity`, `scale`, `slide`, `move(edge:)`, `identity`
```swift=
HikeDetail(hike: hike)
    .transition(.slide)
```

[combined(with: AnyTransition)](https://developer.apple.com/documentation/swiftui/anytransition/combined(with:)) 可以把轉場動畫串接起來。
```swift=
HikeDetail(hike: hike)
    .transition(.scale
                .combined(with: .opacity)
                .combined(with: .move(edge: .trailing)))
```

[asymmetric(insertion:removal:)](https://developer.apple.com/documentation/swiftui/anytransition/asymmetric(insertion:removal:)) 可以分別控制進場和出場的特效，比如，下方客製了一種轉場特效叫做 moveAndFade 和其用法。
```swift=
extension AnyTransition {
    static var moveAndFade: AnyTransition {
        AnyTransition.asymmetric(
            insertion: .move(edge: .trailing).combined(with: .opacity),
            removal: .scale.combined(with: .opacity)
        )
    }
}
```
```swift=
HikeDetail(hike: hike)
    .transition(.moveAndFade)
```

## 畫畫時間 🎨
1. 可以先照著 Apple Tutorial 做一次
2. 過程中，可以嘗試不同的動畫、轉場特效
3. 如果已經會的人，也可以嘗試畫不同的圖形


![](https://i.imgur.com/jM95zeZ.png)
[source code](https://gist.github.com/bing-Guo/4ba7681c35e40027a466f41c01976ac3)