上一課介紹的Canvas把螢幕當作畫布來作圖,如果再結合第4課的時間軸視圖TimeilneView會如何呢?答案就是更有創意、更精細的動畫。本課利用這兩個功能強大的物件,讓螢幕的畫素動起來。
一開始,仍從簡單的做起,先做一個小球的圓周運動。想法很簡單,先畫一個大圓,再畫一個實心小圓,在大圓圓周上移動,在物理上稱為圓周運動,就像人造衛星繞著地球轉一樣。
上一課已學過如何在Canvas中畫圓,所以問題就是如何讓小球繞圓周運動?答案就是靠時間軸視圖TimelineView,仿照第4課4-4b的做法,在 Canvas 中加上 onChange 修飾語,每次收到時間軸的更新,就移動1°角。
.onChange(of: 時間) { _ in
圓心角 = 圓心角 + 1.0
}
所以接下來的問題,是如何計算小球的座標位置?這就需要用到最基本的三角函數:正弦 sin 與餘弦 cos,要利用三角函數來解問題,秘訣就在於找出「直角」,圓周運動哪裡可以找到直角呢?請參考下圖,半徑剛好是一個直角三角形的斜邊:
我們從正上方(12點鐘方向)為起點,順時針移動,已知中心點座標(x, y)、圓心角𝛉、半徑r,如何求得頂點座標呢?很簡單,從中心點往上移動dy, 再往右移動dx,就可得到頂點座標,而 dy, dx 長度分別等於 cos𝛉, sin𝛉 乘以半徑r(斜邊)。
其中圓心角𝛉要換算成弧度,弧度1𝜋等於180°,所以換算公式為:
弧度 = 角度 * 𝜋 / 180
在 Swift 中,凡是實數類型,包括Float, Double, CGFloat等,都會有一個類型屬性 .pi,代表 𝜋。程式中寫 CGFloat.pi 或 .pi 來引用,
我們讓圓心角每次變化1°,不管移動多少度,頂點座標都可以按照上面公式計算出來。然後以頂點為圓心畫一個實心小圓,經過TimelineView的驅動,小圓自然而然就移動起來了。
完整程式碼如下:
// 4-6a 圓周運動 Canvas + TimelineView
// Created by Heman, 2022/04/23
import PlaygroundSupport
import SwiftUI
struct 圓周運動: View {
let 時間: Date
@State var 圓心角 = 0.0
var body: some View {
Canvas { 圖層, 尺寸 in
let 寬 = 尺寸.width
let 高 = 尺寸.height
let 中心 = CGPoint(x: 寬/2, y: 高/2)
let 半徑 = min(寬, 高) / 2
var 畫筆 = Path()
畫筆.addArc( // 大圓
center: 中心,
radius: 半徑,
startAngle: .zero,
endAngle: .degrees(360),
clockwise: false
)
圖層.stroke(畫筆, with: .color(.blue), lineWidth: 2)
畫筆 = Path()
let 頂點 = CGPoint(
x: 中心.x + 半徑 * sin(圓心角 * .pi / 180),
y: 中心.y - 半徑 * cos(圓心角 * .pi / 180))
畫筆.move(to: 頂點)
畫筆.addArc( // 小圓
center: 頂點,
radius: 5,
startAngle: .zero,
endAngle: .degrees(360),
clockwise: false
)
圖層.fill(畫筆, with: .color(.cyan))
畫筆.move(to: 頂點)
畫筆.addLine(to: 中心)
圖層.stroke(畫筆, with: .color(.brown), lineWidth: 1)
}
.onChange(of: 時間) { _ in
圓心角 = 圓心角 + 1.0
}
}
}
struct 畫布: View {
var body: some View {
Label("[SwiftUI] 4-6a 圓周運動", systemImage: "swift")
.font(.largeTitle)
.foregroundColor(.orange)
.padding()
TimelineView(.animation) { 時間參數 in
圓周運動(時間: 時間參數.date)
.border(Color.red)
// .frame(width: 100)
Text("現在時間\\(時間參數.date)")
.padding()
}
}
}
PlaygroundPage.current.setLiveView(畫布())