extension Double {
var asDegrees: Double { return self * 180 / .pi }
var asRadians: Double { return self * .pi / 180 }
}
let radAngle: Double = 2.0
print("radAngle radians= \(radAngle.asDegrees) degrees")
let degAngle: Double = 180
print("degAngle degrees = \(degAngle.asRadians) radians")
SwiftUI 有一个名为 Angle 的类型,带有一些方便的初始化式和计算属性:
let a = Angle(degrees: 180) // Create an angle using degrees
let b = Angle(radians: 2.3456) // Create an angle using radians
print("\(a.radians) radians = \(a.degrees) degrees")
print("\(b.radians) radians = \(b.degrees) degrees")
为什么会复杂化呢?degrees 不是更容易使用吗?也许对我们来说是这样,但数学上,弧度是有意义的,如果你有兴趣了解原因,可以浏览参考:Why Radians?。我们需要知道弧度,因为在 Swift 中的三角函数需要用弧度来指定角度。
三、三角函数的作用
正如我们看到的,给定一个直角三角形,可以从其他三角形推导出一些值。例如,如果知道斜边和其中一个角,就可以得到腿和其他角的大小。如果知道两条边,你就能得到斜边和角等。我们为什么需要这个呢?如果开始考虑三角形的顶点(A, B 和 C),在你的视图中作为 CGPoints,那么一切都比较清晰了。给定两个 CGPoint,可以计算从一个到另一个的方向(角度)(例如,对一个视图旋转效果很有用)。给定两个点的 x、y 坐标,可以得到它们之间的距离(斜边),给定一个 CGPoint 的距离和方向,可以获得第二个 CGPoint 坐标等。三角函数的另一个应用,是当你需要一个函数来平滑一个效果,一个距离,一个颜色,或者任何可以用数字表示的东西。
四、正弦,余弦和正切是什么?
除了三个基本的三角函数之外,还应该知道反函数(反正弦,反余弦和反正切)。例如,如果一个角度 𝝱 的正弦值为 x,那么 x 的反正弦值为 𝝱:
根据勾股定理,斜边的平方等于两条边的平方和:
还有 SOH-CAH-TOA:
由这些公式,我们可以推断出其余的一切,当需要某个没有的值时,看看其他知道的值,然后选择正确的公式就行了。对于任何值,需要知道两条边,或者一条边和一个角,所有的计算组合都在下表中,如下图所示:
func getDistanceAndDirection(_ pt1: CGPoint, _ pt2: CGPoint) -> (distance: CGFloat, angle: Angle) {
let a = pt2.y - pt1.y // calculate leg a
let b = pt2.x - pt1.x // calculate leg b
var alpha = atan2(a, b) // calculate angle
let s = sin(alpha) // sine of the angle
let h = (a == 0 ? abs(b) : (a / s)) // calculate hypotenuse, and prevent divide by zero
alpha = alpha < 0 ? alpha + (.pi * 2) : alpha // make sure angles are returned as positive values
return (h, Angle(radians: Double(alpha)))
}
看这两点构成的三角形,距离与三角形的斜边相匹配,用 arctan 计算角度。在给定的方向和角度下获得第二个点:
如果知道一个点的坐标 (pt1),给定一个方向和长度,那么如何获得第二个点 (pt2)?
在这个例子中,创建一个形状来绘制一条线,给定一个 CGPoint,一个角度和一个距离:
Line(pt1: CGPoint(x: 100, y: 300), direction: Angle(degrees: 25), length: 300)
.stroke(Color.blue, lineWidth: 2)
.frame(width: 400, height: 400)
struct Line: Shape {
let pt1: CGPoint
let direction: Angle
let length: CGFloat
func path(in rect: CGRect) -> Path {
let x = pt1.x + length * CGFloat(cos(direction.radians))
let y = pt1.y - length * CGFloat(sin(direction.radians))
let pt2 = CGPoint(x: x, y: y)
var p = Path()
p.move(to: pt1)
p.addLine(to: pt2)
return p
}
}
六、绘制一个多边形
接下来,来创建一个形状来绘制一个正多边形,这里将使用一个七边多边形,代码几乎类似,可以创建任意数量的边:
一个多边形有许多顶点,想要得到相应的坐标,这样就可以画出连接它们的线。如下图所示,所有顶点到圆周中心的距离都相同:
如前所述,在两点之间,您总是可以创建一个直角三角形。在某些情况下,会形成一个三角形,其中一条边的长度为 0,而另一条边的长度为斜边,想象一个三角形,其中一条边收缩,直到它的长度为零。在这种情况下,正弦值为 0,余弦值为 1,反之亦然。三角函数可以很好地处理这个问题,其中一种情况,就是顶部的顶点(90 度角),cos(90) = 0 sin(90) = 1:
如果我们定义了多边形的中心和周长的半径,就可以得到所有的顶点,每个顶点的角度由多边形的边数决定。三角函数的美妙之处在于,它们可以处理大于 90 度的角,(在某些情况下)返回负值,这很好地满足了我们的绘图要求。例如,在上面的第二个三角形中,余弦值是负的,这意味着顶点的 x 坐标将小于圆周中心的 x 坐标,正是需要的:
struct PolygonShape: Shape {
var sides: Int
func path(in rect: CGRect) -> Path {
// hypotenuse (we make it fit inside the available rect
let h = Double(min(rect.size.width, rect.size.height)) / 2.0
// center
let c = CGPoint(x: rect.size.width / 2.0, y: rect.size.height / 2.0)
var path = Path()
for i in 0..<sides {
let angle = (Double(i) * (360.0 / Double(sides))) * Double.pi / 180
// Calculate vertex position
let pt = CGPoint(x: c.x + CGFloat(cos(angle) * h), y: c.y + CGFloat(sin(angle) * h))
if i == 0 {
path.move(to: pt) // move to first vertex
} else {
path.addLine(to: pt) // draw line to next vertex
}
}
path.closeSubpath()
return path
}
}
我们随时都可以看到三角形,例如,在下面的花中,每个花瓣都由两条曲线组成;要画一条曲线,需要一个起点、一个终点和一个控制点,我们能用这三个点做什么呢?可以用来制作半瓣的三个点,另一半是对称的:
七、平滑进,平滑出
正弦(或余弦)函数有一个特点,我们看它的图形,可以注意到图形的形状重复,它的最小值是 -1,最大值是 1,f(x) 开始缓慢增长,然后稳定,然后再次缓慢增长:
如果稍微改变函数,为了移动和压缩图,我们得到一对理想的波,平滑地增加和减少一个任意值:
当然,还有其他方法可以实现平滑值,但这是一个值得提及的简单方法。可以使用这个功能淡入淡出几乎任何东西:声音音量、定位、移动、颜色、缩放等。如下所示,创建具有渐进缩放值的文本:
struct ContentView: View {
var body: some View {
ProgressiveText(text: "AAAAAAAA")
}
}
struct ProgressiveText: View {
let text: String
var body: some View {
HStack(spacing: 10) {
ForEach(Array(text.enumerated()), id: \.0) { (n, ch) in
Text(String(ch)).font(.largeTitle).fontWeight(.bold).scaleEffect(self.scaleValue(n, self.text.count))
}
}
}
func scaleValue(_ idx: Int, _ totalCharacters: Int) -> CGFloat {
// Normalized character position, to a value between 0 and 1
let x = Double(idx) / Double(totalCharacters)
// Get a number between 0 and 1, according to a sine wave
let y = (sin(2 * .pi * x - (.pi / 2)) + 1) / 2.0
// Return a scale value from 1 (normal) to 3 (3 times the size).
return 1 + 2 * CGFloat(y)
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)