SwiftUI四绘制路径和形状

代码下载

创建徽章视图

创建徽章前需要使用SwiftUI的矢量绘画API创建一个徽章视图

1、新建HexagonParameters.swift文件。HexagonParameters结构体定义了绘制徽章六边形形状的控制点参数。仅仅使用这些数据指定绘制徽章形状时,线段和曲线的控制点位置:

复制代码
import SwiftUI

struct HexagonParameters {
    struct Segment {
        let useWidth: (CGFloat, CGFloat, CGFloat)
        let xFactors: (CGFloat, CGFloat, CGFloat)
        let useHeight: (CGFloat, CGFloat, CGFloat)
        let yFactors: (CGFloat, CGFloat, CGFloat)
    }
    
    static let adjustment: CGFloat = 0.085
    static let points = [
        Segment(
            useWidth:  (1.00, 1.00, 1.00),
            xFactors:  (0.60, 0.40, 0.50),
            useHeight: (1.00, 1.00, 0.00),
            yFactors:  (0.05, 0.05, 0.00)
        ),
        Segment(
            useWidth:  (1.00, 1.00, 0.00),
            xFactors:  (0.05, 0.00, 0.00),
            useHeight: (1.00, 1.00, 1.00),
            yFactors:  (0.20 + adjustment, 0.30 + adjustment, 0.25 + adjustment)
        ),
        Segment(
            useWidth:  (1.00, 1.00, 0.00),
            xFactors:  (0.00, 0.05, 0.00),
            useHeight: (1.00, 1.00, 1.00),
            yFactors:  (0.70 - adjustment, 0.80 - adjustment, 0.75 - adjustment)
        ),
        Segment(
            useWidth:  (1.00, 1.00, 1.00),
            xFactors:  (0.40, 0.60, 0.50),
            useHeight: (1.00, 1.00, 1.00),
            yFactors:  (0.95, 0.95, 1.00)
        ),
        Segment(
            useWidth:  (1.00, 1.00, 1.00),
            xFactors:  (0.95, 1.00, 1.00),
            useHeight: (1.00, 1.00, 1.00),
            yFactors:  (0.80 - adjustment, 0.70 - adjustment, 0.75 - adjustment)
        ),
        Segment(
            useWidth:  (1.00, 1.00, 1.00),
            xFactors:  (1.00, 0.95, 1.00),
            useHeight: (1.00, 1.00, 1.00),
            yFactors:  (0.30 + adjustment, 0.20 + adjustment, 0.25 + adjustment)
        )
    ]
}

2、选择文件->新建->文件,然后从iOS文件模板列表中选择SwiftUI View。点击下一步(Next),输入文件名Badge后点击创建(Create)。在Badge.swift文件中,绘制徽章的形状并使用fill修改器给六边形填充颜色,形成一个视图。使用路径可以把多条直线、曲线或其它绘制形状的基本笔划连成一个复杂的图形,就像形成徽章六边形背景这样:

复制代码
struct Badge: View {
    var body: some View {
        Path({ path in
            
        })
    }
}

#Preview {
    Badge()
}

3、给路径添加起点,move(to:)方法可以把绘图光标移动到绘图中的一点,准备绘制的起点。使用六边形的绘制参数数据HexagonParameters,依次绘制六边形的边,形成大致轮廓.addLine(to:)方法会使用当前绘图光标所在点为起点,方法参数中指定的点为终点绘制直线。目前六边形看起来有点问题,不过不要担心,这是意料中的事:

复制代码
struct Badge: View {
    var body: some View {
        Path({ path in
            let width: CGFloat = 100
            let height = width
            path.move(to: CGPoint(x: width*0.95, y: height*0.2))
            
            HexagonParameters.points.forEach {
                path.addLine(to: CGPoint(x: width*$0.useWidth.0*$0.xFactors.0, y: height*$0.useHeight.0*$0.yFactors.0))
            }
        }).fill(Color(.black))
    }
}

4、使用addQuadCurve(to:control:)方法绘制贝塞尔曲线,让六边形的角变的更圆润些。

5、把徽章路径包裹在一个Geometry Reader中,这样徽章可以使用容器的大小,定义自己绘制的尺寸,这样就不需要硬编码绘制尺寸了(100)。当绘制区域不是正方形时,使用绘制区域的最小边长(长宽中哪个最小使用哪个)作为绘制徽章背景的边长,并保持徽章背景的长宽比为1:1。

6、使用xScale和xOffset参数调整变量,把徽章几何绘图区域居中绘制出来。

复制代码
struct Badge: View {
    var body: some View {
        GeometryReader { geometry in
            Path({ path in
                var width: CGFloat = min(geometry.size.width, geometry.size.height)
                let height = width
                
                let xScale: CGFloat = 0.832
                let xOffset: CGFloat = (width*(1.0 - xScale))/2.0
                width *= xScale
                
                path.move(to: CGPoint(x: xOffset + width*0.95, y: height*(0.2 + HexagonParameters.adjustment)))
                
                HexagonParameters.points.forEach {
                    path.addLine(to: CGPoint(x: xOffset + width*$0.useWidth.0*$0.xFactors.0, y: height*$0.useHeight.0*$0.yFactors.0))
                    
                    path.addQuadCurve(to: CGPoint(x: xOffset + width*$0.useWidth.1*$0.xFactors.1, y: height*$0.useHeight.1*$0.yFactors.1), control: CGPoint(x: xOffset + width*$0.useWidth.2*$0.xFactors.2, y: height*$0.useHeight.2*$0.yFactors.2))
                }
            }).fill(Color(.black))
        }
    }
}

7、把黑色实心填充色改为渐变色,使徽章看上去和开始设计的样式一致。渐变色上再使用aspectRatio(_:contentMode:)修改器,让渐变色按内容宽高比进行成比例渐变填充。保持1:1的长宽比,徽章背景可以保持居中在徽章视图中,不管徽章视图本身是不是正方形:

复制代码
struct Badge: View {
    var body: some View {
        GeometryReader { geometry in
            Path({ path in
                var width: CGFloat = min(geometry.size.width, geometry.size.height)
                let height = width
                
                let xScale: CGFloat = 0.832
                let xOffset: CGFloat = (width*(1.0 - xScale))/2.0
                width *= xScale
                
                path.move(to: CGPoint(x: xOffset + width*0.95, y: height*(0.2 + HexagonParameters.adjustment)))
                
                HexagonParameters.points.forEach {
                    path.addLine(to: CGPoint(x: xOffset + width*$0.useWidth.0*$0.xFactors.0, y: height*$0.useHeight.0*$0.yFactors.0))
                    
                    path.addQuadCurve(to: CGPoint(x: xOffset + width*$0.useWidth.1*$0.xFactors.1, y: height*$0.useHeight.1*$0.yFactors.1), control: CGPoint(x: xOffset + width*$0.useWidth.2*$0.xFactors.2, y: height*$0.useHeight.2*$0.yFactors.2))
                }
            }).fill(LinearGradient(colors: [Badge.gradientStart, Badge.gradientEnd], startPoint: UnitPoint(x: 0.5, y: 0), endPoint: UnitPoint(x: 0.5, y: 0.6)))
                .aspectRatio(1, contentMode: .fit)
        }
    }
    
    static let gradientStart = Color(red: 239.0/255, green: 120.0/255, blue: 221.0/255)
    static let gradientEnd = Color(red: 239.0/255, green: 172.0/255, blue: 120.0/255)
}

绘制徽章符号

地标徽章中心有一个以地标App图标中的山峰图形改造形成的标志。山峰这个符号由两个形状组成,一个是表示山顶被雪覆盖的部分,另一个是山体。这里会使用有一定间距的两个局部三角形形状绘制这个徽章符号

1、把之前的徽章视图形状抽出来单独形成一个BadgeBackground视图,并生成一个新的视图文件BadgeBackground.swift:

复制代码
struct BadgeBackground: View {
    var body: some View {
        GeometryReader { geometry in
            Path({ path in
                var width: CGFloat = min(geometry.size.width, geometry.size.height)
                let height = width
                
                let xScale: CGFloat = 0.832
                let xOffset: CGFloat = (width*(1.0 - xScale))/2.0
                width *= xScale
                
                path.move(to: CGPoint(x: xOffset + width*0.95, y: height*(0.2 + HexagonParameters.adjustment)))
                
                HexagonParameters.points.forEach {
                    path.addLine(to: CGPoint(x: xOffset + width*$0.useWidth.0*$0.xFactors.0, y: height*$0.useHeight.0*$0.yFactors.0))
                    
                    path.addQuadCurve(to: CGPoint(x: xOffset + width*$0.useWidth.1*$0.xFactors.1, y: height*$0.useHeight.1*$0.yFactors.1), control: CGPoint(x: xOffset + width*$0.useWidth.2*$0.xFactors.2, y: height*$0.useHeight.2*$0.yFactors.2))
                }
            }).fill(LinearGradient(colors: [BadgeBackground.gradientStart, BadgeBackground.gradientEnd], startPoint: UnitPoint(x: 0.5, y: 0), endPoint: UnitPoint(x: 0.5, y: 0.6)))
                .aspectRatio(1, contentMode: .fit)
        }
    }
    
    static let gradientStart = Color(red: 239.0/255, green: 120.0/255, blue: 221.0/255)
    static let gradientEnd = Color(red: 239.0/255, green: 172.0/255, blue: 120.0/255)
}

2、把BadgeBackground放在Badge的body属性中:

复制代码
struct Badge: View {
    var body: some View {
        BadgeBackground()
    }
}

3、创建名为BadgeSymbol的自定义视图,这个视图是一个山峰的形状,把这个形状复制多次并按一定角度旋转多次拼成一个徽章的图案。使用pathAPI来绘制徽章符号的上半部分,试着调节spacing、topWidth、topHeight的系数,观察这些系数是怎么影响图形绘制的结果的。绘制徽章图案的下半部分,使用move(to:)把绘图光标移到另一个图形绘制的起点,绘制新的形状,用紫色填充徽章符号:

复制代码
struct BadgeSymbol: View {
    static let symbolColor = Color(red: 79.0/255, green: 79.0/255, blue: 191.0/255)
    var body: some View {
        GeometryReader { geometry in
            Path { path in let
                width = min(geometry.size.width, geometry.size.height)
                let height = width*0.75
                let spacing = width*0.030
                let middle = width/2
                let topWidth = 0.226*width
                let topHeight = 0.488*height
                
                path.addLines([
                    CGPoint(x: middle, y: spacing),
                    CGPoint(x: middle - topWidth, y: topHeight - spacing),
                    CGPoint(x: middle, y: topHeight/2 + spacing),
                    CGPoint(x: middle + topWidth, y: topHeight - spacing),
                    CGPoint(x: middle, y: spacing)
                ])
                
                path.move(to: CGPoint(x: middle, y: topHeight / 2 + spacing * 3))
                path.addLines([
                    CGPoint(x: middle - topWidth, y: topHeight + spacing),
                    CGPoint(x: spacing, y: height - spacing),
                    CGPoint(x: width - spacing, y: height - spacing),
                    CGPoint(x: middle + topWidth, y: topHeight + spacing),
                    CGPoint(x: middle, y: topHeight/2 + spacing*3)
                ])
            }.fill(BadgeSymbol.symbolColor)
        }
    }
}

组合徽章的前景符号和背景形状

徽章设计思路是在背景形状上面再绘制多个有固定旋转角度的山峰符号。定义一个新的类型用于展示旋转一定角度的徽章符号,使用ForEach生成不同旋转角度的山峰符号,绘制在徽章背景上,从而形成最终的徽章。

1、创建RotatedBadgeSymbol视图封装旋转徽章符号,调整旋转的角度,并在预览视图中查看效果:

复制代码
struct RotatedBadgeSymbol: View {
    let angle: Angle
    var body: some View {
        BadgeSymbol().padding(-60)
            .rotationEffect(angle, anchor: .bottom)
    }
}

#Preview {
    RotatedBadgeSymbol(angle: Angle(degrees: 10))
}

2、在Badge.swift中,使用ZStack把徽章图标放在徽章背景层上面。此时会发现,徽章符号的尺寸相比徽章背景大了许多,这不符合最初设计的预期,缩放符号尺寸到合适的大小。使用ForEach复制多个徽章图标,按360度周解均分,每一个徽章符号都比前一个多旋转45度,这种就会形成一个类似太阳和徽章图标:

复制代码
struct Badge: View {
    var badgeSysbols: some View {
        ForEach(0..<8) { i in
            RotatedBadgeSymbol(angle: Angle(degrees: Double(i*45)))
        }.opacity(0.5)
        
    }
    var body: some View {
        ZStack {
            BadgeBackground()
            
            GeometryReader { geometry in
                badgeSysbols.scaleEffect(1.0/4, anchor: .top)
                    .position(x: geometry.size.width/2, y: (3.0/4)*geometry.size.height)
            }
        }.scaledToFit()
    }
}
相关推荐
若水无华12 小时前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"13 小时前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy1 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克1 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨1 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆1 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂2 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T2 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20252 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz2 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频