# 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)