You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SwiftUI中基于单条曲线路径实现书法效果

在SwiftUI中如何将单条曲线路径转换为书法风格的Shape?

核心思路

我认为实现书法风格的基本逻辑应该是:

  • 复制原路径
  • 平移复制后的路径
  • 首尾连接两条路径,合并成闭合的填充形状

比如斜握扁平笔从左到右书写的效果:

未连接状态:两条平行的波浪曲线
连接完成后:两条曲线首尾相连,形成类似书法笔触的闭合形状

但我要处理的AsemicWord_Squiggle形状由复杂贝塞尔曲线构成,并非简单的点序列,直接用想象中的便捷方法(比如路径平移、合并)行不通,因为SwiftUI的Path没有对应的API。


原Shape示例与实现

使用示例

AsemicWord_Squiggle()
    .stroke(Color.blue, lineWidth: 2)
    .frame(width: 200, height: 100)
    .background(Color.gray).opacity(0.3)

效果:一条从左到右振幅逐渐减小、波长逐渐增大的随机波浪曲线

AsemicWord_Squiggle实现代码

struct AsemicWord_Squiggle: Shape {
    
    func path(in rect: CGRect) -> Path {
        
        var path = Path()
        
        // 振幅衰减速率,值越小衰减越快
        let waveAmplitudePowerSeed = CGFloat.random(in: 0.7...0.95)
        // 波长增长速率,值越大增长越多
        let waveLengthPowerSeed = CGFloat.random(in: 1.1...1.3)

        // 起始点
        let startingX = rect.minX
        let startingY = rect.midY
        path.move(to: CGPoint(x: rect.minX, y: startingY))
        
        // 初始波幅(用于第一个控制点的Y值)
        var waveAmplitude = CGFloat.random(in: rect.height * 0.4...rect.height * 0.6)
        // 初始波长
        var waveLength = CGFloat.random(in: rect.width * 0.1...rect.width * 0.2)
        
        let firstWaveControlY = CGFloat.random(in: rect.midY - waveAmplitude...rect.midY + waveAmplitude)
        
        // 绘制第一段曲线
        let firstWaveLength = waveLength
        let firstNextX = startingX + firstWaveLength
        let firstControl1 = CGPoint(x: startingX + firstWaveLength / 4, y: firstWaveControlY)
        let firstControl2 = CGPoint(x: startingX + 3 * firstWaveLength / 4, y: startingY - waveAmplitude)
        let firstNextPoint = CGPoint(x: firstNextX, y: startingY)
        
        path.addCurve(to: firstNextPoint, control1: firstControl1, control2: firstControl2)
        
        // 绘制剩余曲线段
        var currentX = firstNextX
        while currentX < rect.maxX/2 {
            let nextX = currentX + waveLength
            let control1 = CGPoint(x: currentX + waveLength / 4, y: startingY + waveAmplitude)
            let control2 = CGPoint(x: currentX + 3 * waveLength / 4, y: startingY - waveAmplitude)
            let nextPoint = CGPoint(x: nextX, y: startingY)
            
            waveAmplitude = pow(waveAmplitude, waveAmplitudePowerSeed)
            waveLength = pow(waveLength, waveLengthPowerSeed)
            path.addCurve(to: nextPoint, control1: control1, control2: control2)
            currentX = nextX
        }
        
        return path
    }
}

尝试过的无效方法

我原本想在返回路径前直接做类似以下的操作,但这些方法在SwiftUI中并不存在:

let path2 = path
path2.translateBy(x,y)
path.weld(path2)
return path

如果路径是点序列的话,可以用下面的伪代码实现,但当前路径是贝塞尔曲线构成,无法直接提取所有点:

let copyA:[CGPoint] = path.getPoints
let copyB:[CGPoint] = path.getPoints.translatedBy(x,y)

let result = Path()
result.moveTo(copyA.first)
for points in copyA { point in
    result.lineTo(point) 
}
result.moveTo(copyB.last)
for points in copyB.reversed() { point in
    result.lineTo(point)
}
result.lineTo(copyA.first)

问题需求

以AsemicWord_Squiggle为例:

  1. 如何实现书法风格的Shape?
  2. 能否在SwiftUI视图修饰符层面实现?
  3. 如果不能,最简单的实现方法是什么?

内容的提问来源于stack exchange,提问作者Nerdy Bunz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:13:27