5 graphics core spectrum ios swift
我一直试图了解如何在iOS中绘制Siri的波形效果并遇到了这个伟大的存储库.最终结果如下:
但是我很难理解生成wave的代码发生了什么.我可以生成一个静态正弦波,但是,我似乎并不理解.
特别是当我们计算y的值时,为什么它必须是:
let y = scaling * maxAmplitude * normedAmplitude *
sin(CGFloat(2 * M_PI) * self.frequency * (x / self.bounds.width) + self.phase) + self.bounds.height/2.0
源代码:
//MARK : Properties
let density : CGFloat = 1
let frequency : CGFloat = 1.5
var phase :CGFloat = 0
var phaseShift:CGFloat = -0.15
var numberOfWaves:Int = 6
var primaryLineWidth:CGFloat = 1.5
var idleAmplitude:CGFloat = 0.01
var waveColor:UIColor = UIColor.white
var amplitude:CGFloat = 1.0 {
didSet {
amplitude = max(amplitude, self.idleAmplitude)
self.setNeedsDisplay()
}
}
Run Code Online (Sandbox Code Playgroud)
方法
override open func draw(_ rect: CGRect) {
// Convenience function to draw the wave
func drawWave(_ index:Int, maxAmplitude:CGFloat, normedAmplitude:CGFloat) {
let path = UIBezierPath()
let mid = self.bounds.width/2.0
path.lineWidth = index == 0 ? self.primaryLineWidth : self.secondaryLineWidth
for x in Swift.stride(from:0, to:self.bounds.width + self.density, by:self.density) {
// Parabolic scaling
let scaling = -pow(1 / mid * (x - mid), 2) + 1
// The confusing part /////////////////////////////////////////
let y = scaling * maxAmplitude * normedAmplitude *
sin(CGFloat(2 * M_PI) * self.frequency * (x / self.bounds.width) + self.phase)
+ self.bounds.height/2.0
//////////////////////////////////////////////////////////////////
if x == 0 {
path.move(to: CGPoint(x:x, y:y))
} else {
path.addLine(to: CGPoint(x:x, y:y))
}
}
path.stroke()
}
let context = UIGraphicsGetCurrentContext()
context?.setAllowsAntialiasing(true)
self.backgroundColor?.set()
context?.fill(rect)
let halfHeight = self.bounds.height / 2.0
let maxAmplitude = halfHeight - self.primaryLineWidth
for i in 0 ..< self.numberOfWaves {
let progress = 1.0 - CGFloat(i) / CGFloat(self.numberOfWaves)
let normedAmplitude = (1.5 * progress - 0.8) * self.amplitude
let multiplier = min(1.0, (progress/3.0*2.0) + (1.0/3.0))
self.waveColor.withAlphaComponent(multiplier * self.waveColor.cgColor.alpha).set()
drawWave(i, maxAmplitude: maxAmplitude, normedAmplitude: normedAmplitude)
}
self.phase += self.phaseShift
}
Run Code Online (Sandbox Code Playgroud)
两个for循环看起来都很数学,我不知道那里发生了什么.提前致谢.
这是最内部循环的细分,循环x绘制波形.我将在我的解释中稍微详细一点,希望一些额外的信息可能对其他人有用.
for x in Swift.stride(from:0, to:self.bounds.width + self.density, by:self.density)
{
Run Code Online (Sandbox Code Playgroud)
循环以density递增的方式迭代UIView的宽度.这允许控制两个属性:(1)波形的"分辨率"和(2)生成UIBezierPath绘制的时间长度.简单地设置density为2(in ViewController.swift)会将计算次数减半,并生成一个路径,其中要绘制的元素数量减半.增加density一个完整的数量级(10)可能看起来太多了,但你会很难注意到视觉差异.100如果要查看三角波,请尝试将值设置为.
旁注:由于使用了stride(from:to:by:)如果视图的宽度不能被整除density,波形可能会停留在视图的右侧,因此+ self.density添加了.
// Parabolic scaling
let scaling = -pow(1 / mid * (x - mid), 2) + 1
Run Code Online (Sandbox Code Playgroud)
您是否注意到波形似乎如何附加到屏幕两侧的锚点?这就是抛物线缩放所做的事情.要更清楚地看到它,您可以将此公式插入Google的图形功能中以获取此信息:
在该范围内,y遵循曲线,是的,但注意如何y从0开始,在中心上升到正好1.0,然后回落到0.更具体地说,它在x0到1 的范围内这样做.这是关键,因为我们将此曲线映射到视图的宽度,屏幕的左边缘映射到该视图,屏幕x=0的右边缘映射到x=1.
如果我们将此曲线映射到我们的屏幕波形并使用它来缩放幅度(幅度:波形相对于其中心线的大小),您将看到波形的左右端点将具有幅度为0(我们的锚点),波形的大小逐渐增加到中心的全尺寸(1.0).
要查看此缩放的完整效果,请尝试将该行更改为let scaling = CGFloat(1.0).
此时,我们已准备好绘制波形图.这是OP询问的原始代码行:
let y = scaling * maxAmplitude * normedAmplitude *
sin(CGFloat(2 * M_PI) * self.frequency * (x / self.bounds.width) + self.phase)
+ self.bounds.height/2.0
Run Code Online (Sandbox Code Playgroud)
这一切都很重要.这段代码做了同样的事情,但我把它拆分成具有适当名称的临时变量,以帮助理解正在发生的事情:
let unitWidth = x / self.bounds.width
var wave = CGFloat(2 * M_PI)
wave *= unitWidth
wave *= self.frequency
let wavePosition = wave + self.phase
let waveUnitValue = sin(wavePosition)
var amplitude = waveUnitValue * maxAmplitude
amplitude *= scaling
amplitude *= normedAmplitude
let y = amplitude + self.bounds.height/2.0
Run Code Online (Sandbox Code Playgroud)
好吧,让我们一次解决这个问题.我们先从开始unitWidth.还记得当我提到我们要将曲线映射到屏幕的宽度时吗?这就是unitWidth计算的作用:x范围从0到0 self.bounds.width,unitWidth范围从0到1.
接下来是wave.重要的是要注意,该值旨在用于计算正弦波.请注意,该sin函数在Radians中起作用,这意味着正弦波的整个周期范围为0到2π,因此我们将从那里开始(CGFloat(2 * M_PI)).
然后我们应用我们unitWidth来wave确定在x视图中给定位置的正弦波内的位置.想象一下:沿着视图的左侧,unitWidth是0,所以这个乘法结果为0(正弦波的开始.)沿着视图的右侧,unitWidth是1.0(给我们全值2π - 正弦波的结束.)如果我们在视图的中间,unitWidth将是0.5,这将使我们在完整的正弦波周期中途.以及介于两者之间的一切.这称为插值.重要的是要理解我们没有移动正弦波,我们正在踩踏它.
接下来,我们申请self.frequency到wave.这会缩放正弦波,使得较高的值具有更多的山丘和山谷.频率为1不会做任何事情,我们将遵循自然的正弦波.但这很无聊,所以频率增加了一点(1.5),以提供更好的视觉外观.像盐一样,适应口味.这是频率的3倍:
到目前为止,我们已经定义了我们的正弦波相对于我们所绘制的视图的看法.我们的下一个任务是提出动议.为此,我们将添加self.phase到wave.这称为"相位",因为相位是波形内的不同周期.通过连续更改self.phase动画的每个帧,绘图将从波形内的不同位置开始,使其看起来移过屏幕.
最后,我们wavePosition用来计算实际的正弦波值(let waveUnitValue = sin(wavePosition)).我之所以这样称呼waveUnitValue是因为sin()的结果是一个从-1到+1的值.如果我们按原样绘制它,我们的波浪将非常无聊,类似于一条平坦的线条:
"我有需要......需要振幅"
- 没有人
我们amplitude开始通过应用maxAmplitude到waveUnitValue垂直拉伸.为什么从最大值开始?如果我们回到scaling变量的计算,我们会被提醒这是一个单位值 - 一个从0到1的值 - 这意味着它只能减小幅度(或保持不变)但不增加它.
而这正是我们接下来要做的,应用我们的scaling价值.这导致我们的波形在末端具有0的幅度,在中心逐渐增加到全幅度.没有这个,我们会有这样的东西:
最后,我们有normedAmplitude.如果您遵循代码,您将看到drawWave在循环中调用该函数以便将多个波绘制到视图中(这是那些次级或"阴影"波形进入的位置.)normedAmplitude用于选择不同的幅度对于作为整体效果的一部分绘制的每个波形.
值得注意的是,normedAmplitude可以为负,这允许阴影波形垂直翻转,填充波形的空白区域.尝试更改normedAmplitude原始代码中的使用,abs(normedAmplitude)您将看到类似的内容(结合3倍频率示例以突出显示差异):
最后一步是将波形置于视图(amplitude + self.bounds.height/2.0)中心,这将成为y我们用于绘制波形的最终值.
所以,嗯.而已.
| 归档时间: |
|
| 查看次数: |
2020 次 |
| 最近记录: |