博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[译] SwiftUI 官方教程 (五)
阅读量:7122 次
发布时间:2019-06-28

本文共 21932 字,大约阅读时间需要 73 分钟。

完整中文教程及代码请查看

绘制 Path 和 Shape

用户访问列表中的地标时应当获得徽章,为此,我们需要创建徽章。在本文中,我们将通过组合 pathsshapes 来创建徽章,然后把它和另一个表示位置的 shape 叠在一起。

我们可以尝试使用 overlaid 符号来给不同类型的地标创建多个徽章,修改它的重复次数或更改各种角度和比例。

下载项目文件并按照以下步骤操作,也可以打开已完成的项目自行浏览代码。

  • 预计完成时间:25 分钟
  • 项目文件:

1. 创建一个 Badge View

首先我们创建一个使用 SwiftUI 中矢量绘图 API 的徽章 view 。

1.1 选择 File > New > File ,从 iOS Templates 中选择 SwiftUI View 。单击 Next ,命名为 Badge ,单击 Create

1.2 在定义徽章的 shape 前,先让 Badge view 显示 Badge

Badge.swift

import SwiftUIstruct Badge: View {    var body: some View {        Text("Badge")    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

2. 绘制 Badge 的背景

SwiftUI 中的图形 API 绘制自定义的徽章 shape

2.1 查看 HexagonParameters.swift 文件中的代码。

HexagonParameters 结构体定义了绘制徽章的六边形 shape 的细节,我们不用修改这些数据,直接使用它们来指定绘制徽章的线条和曲线的控制点。

HexagonParameters.swift

import SwiftUIstruct 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.2 在 Badge.swift 中,给徽章添加一个 Path shape ,然后调用 fill() 方法把 shape 转换成一个 view 。

我们可以使用 path 组合直线、曲线和其他绘图单元来形成更复杂的形状,如这里徽章的六边形背景。

Badge.swift

import SwiftUIstruct Badge: View {    var body: some View {        Path { path in                    }        .fill(Color.black)    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

2.3 给 path 添加起始点。

move(to:) 方法把绘制光标移动到一个 shape 的边上,就像钢笔或铅笔悬停在该位置,等待开始绘制。

Badge.swift

import SwiftUIstruct Badge: View {    var body: some View {        Path { path in            var width: CGFloat = 100.0            let height = width            path.move(to: CGPoint(x: width * 0.95, y: height * 0.20))        }        .fill(Color.black)    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

2.4 绘制 shape 数据中的每一个点,创建一个大致的六边形 shape

addLine(to:) 拿到一点并绘制出来。连续调用 addLine(to:) 方法,从上个点到新的点之间画一条线。

Badge.swift

import SwiftUIstruct Badge: View {    var body: some View {        Path { path in            var width: CGFloat = 100.0            let height = width            path.move(to: CGPoint(x: width * 0.95, y: height * 0.20))                        HexagonParameters.points.forEach {                path.addLine(                    to: .init(                        x: width * $0.useWidth.0 * $0.xFactors.0,                        y: height * $0.useHeight.0 * $0.yFactors.0                    )                )            }        }        .fill(Color.black)    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

现在我们的六角形看起来不对劲,但这是正常的。在接下来的几个步骤中,我们会让六边形看起来更像本开文头所示的徽章形状。

2.5 使用 addQuadCurve(to:control:) 方法来给徽章的角绘制贝塞尔曲线。

Badge.swift

import SwiftUIstruct Badge: View {    var body: some View {        Path { path in            var width: CGFloat = 100.0            let height = width            path.move(                to: CGPoint(                    x: width * 0.95,                    y: height * (0.20 + HexagonParameters.adjustment)                )            )                        HexagonParameters.points.forEach {                path.addLine(                    to: .init(                        x: width * $0.useWidth.0 * $0.xFactors.0,                        y: height * $0.useHeight.0 * $0.yFactors.0                    )                )                                path.addQuadCurve(                    to: .init(                        x: width * $0.useWidth.1 * $0.xFactors.1,                        y: height * $0.useHeight.1 * $0.yFactors.1                    ),                    control: .init(                        x: width * $0.useWidth.2 * $0.xFactors.2,                        y: height * $0.useHeight.2 * $0.yFactors.2                    )                )            }        }        .fill(Color.black)    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

把徽章的 path 包装在一个 GeometryReader 中,这样徽章就不会用硬编码的大小(100)而是使用其所包含 view 的大小。

当徽章所包含的 view 不是正方形时,使用几何体最小的的两个维度可以在保证宽高比。

Badge.swift

import SwiftUIstruct 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                path.move(                    to: CGPoint(                        x: width * 0.95,                        y: height * (0.20 + HexagonParameters.adjustment)                    )                )                                HexagonParameters.points.forEach {                    path.addLine(                        to: .init(                            x: width * $0.useWidth.0 * $0.xFactors.0,                            y: height * $0.useHeight.0 * $0.yFactors.0                        )                    )                                        path.addQuadCurve(                        to: .init(                            x: width * $0.useWidth.1 * $0.xFactors.1,                            y: height * $0.useHeight.1 * $0.yFactors.1                        ),                        control: .init(                            x: width * $0.useWidth.2 * $0.xFactors.2,                            y: height * $0.useHeight.2 * $0.yFactors.2                        )                    )                }            }            .fill(Color.black)        }    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

2.7 使用 xScalexOffset 调整变量将徽章置于其几何体中心。

Badge.swift

import SwiftUIstruct 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 = (width * (1.0 - xScale)) / 2.0                width *= xScale                path.move(                    to: CGPoint(                        x: xOffset + width * 0.95,                        y: height * (0.20 + HexagonParameters.adjustment)                    )                )                                HexagonParameters.points.forEach {                    path.addLine(                        to: .init(                            x: xOffset + width * $0.useWidth.0 * $0.xFactors.0,                            y: height * $0.useHeight.0 * $0.yFactors.0                        )                    )                                        path.addQuadCurve(                        to: .init(                            x: xOffset + width * $0.useWidth.1 * $0.xFactors.1,                            y: height * $0.useHeight.1 * $0.yFactors.1                        ),                        control: .init(                            x: xOffset + width * $0.useWidth.2 * $0.xFactors.2,                            y: height * $0.useHeight.2 * $0.yFactors.2                        )                    )                }            }            .fill(Color.black)        }    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

参照设计,把徽章背景的纯黑色改成渐变色。

Badge.swift

import SwiftUIstruct 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 = (width * (1.0 - xScale)) / 2.0                width *= xScale                path.move(                    to: CGPoint(                        x: xOffset + width * 0.95,                        y: height * (0.20 + HexagonParameters.adjustment)                    )                )                                HexagonParameters.points.forEach {                    path.addLine(                        to: .init(                            x: xOffset + width * $0.useWidth.0 * $0.xFactors.0,                            y: height * $0.useHeight.0 * $0.yFactors.0                        )                    )                                        path.addQuadCurve(                        to: .init(                            x: xOffset + width * $0.useWidth.1 * $0.xFactors.1,                            y: height * $0.useHeight.1 * $0.yFactors.1                        ),                        control: .init(                            x: xOffset + width * $0.useWidth.2 * $0.xFactors.2,                            y: height * $0.useHeight.2 * $0.yFactors.2                        )                    )                }            }            .fill(LinearGradient(                gradient: .init(colors: [Self.gradientStart, Self.gradientEnd]),                startPoint: .init(x: 0.5, y: 0),                endPoint: .init(x: 0.5, y: 0.6)            ))        }    }    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)}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

2.9 把 aspectRatio(_:contentMode:) 方法应用到渐变的填充上。

即使徽章的父项不是正方形,也可以通过保持1:1的宽高比,让徽章处于视图中心的位置。

Badge.swift

import SwiftUIstruct 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 = (width * (1.0 - xScale)) / 2.0                width *= xScale                path.move(                    to: CGPoint(                        x: xOffset + width * 0.95,                        y: height * (0.20 + HexagonParameters.adjustment)                    )                )                                HexagonParameters.points.forEach {                    path.addLine(                        to: .init(                            x: xOffset + width * $0.useWidth.0 * $0.xFactors.0,                            y: height * $0.useHeight.0 * $0.yFactors.0                        )                    )                                        path.addQuadCurve(                        to: .init(                            x: xOffset + width * $0.useWidth.1 * $0.xFactors.1,                            y: height * $0.useHeight.1 * $0.yFactors.1                        ),                        control: .init(                            x: xOffset + width * $0.useWidth.2 * $0.xFactors.2,                            y: height * $0.useHeight.2 * $0.yFactors.2                        )                    )                }            }            .fill(LinearGradient(                gradient: .init(colors: [Self.gradientStart, Self.gradientEnd]),                startPoint: .init(x: 0.5, y: 0),                endPoint: .init(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)}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

3. 绘制 Badge Symbol

地标徽章的中心有一个自定义徽章,它由 Landmarks app icon 中的山峰转变而来。

山峰由两个形状组成:一个代表峰顶的雪盖,另一个代表沿途的植被。我们使用两个三角形的 shape 绘制它们,然后由一个小间隙分开。

3.1 创建一个名为 BadgeBackground.swift 的新文件,将徽章 view 的主体封装为新文件中的 BadgeBackground view,作为为其他视图创建 Badge view 的一部分。

BadgeBackground.swift

import SwiftUIstruct 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 = (width * (1.0 - xScale)) / 2.0                width *= xScale                path.move(                    to: CGPoint(                        x: xOffset + width * 0.95,                        y: height * (0.20 + HexagonParameters.adjustment)                    )                )                                HexagonParameters.points.forEach {                    path.addLine(                        to: .init(                            x: xOffset + width * $0.useWidth.0 * $0.xFactors.0,                            y: height * $0.useHeight.0 * $0.yFactors.0                        )                    )                                        path.addQuadCurve(                        to: .init(                            x: xOffset + width * $0.useWidth.1 * $0.xFactors.1,                            y: height * $0.useHeight.1 * $0.yFactors.1                        ),                        control: .init(                            x: xOffset + width * $0.useWidth.2 * $0.xFactors.2,                            y: height * $0.useHeight.2 * $0.yFactors.2                        )                    )                }            }            .fill(LinearGradient(                gradient: .init(colors: [Self.gradientStart, Self.gradientEnd]),                startPoint: .init(x: 0.5, y: 0),                endPoint: .init(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)}#if DEBUGstruct BadgeBackground_Previews: PreviewProvider {    static var previews: some View {        BadgeBackground()    }}#endif复制代码

3.2 将 BadgeBackground 放置在徽章的正文中来恢复徽章。

Badge.swift

import SwiftUIstruct Badge: View {    var body: some View {        BadgeBackground()    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

3.3 给设计中旋转样式的山峰 shape 创建一个新的自定义 view BadgeSymbol

BadgeSymbol.swift

import SwiftUIstruct BadgeSymbol: View {    var body: some View {        Text("Badge Symbol")    }}#if DEBUGstruct BadgeSymbol_Previews: PreviewProvider {    static var previews: some View {        BadgeSymbol()    }}# #endif复制代码

3.4 使用 path API 绘制 symbol 的顶部。

尝试调整一下与 spacingtopWidthtopHeight 常量关联的系数,了解它们是如何影响整体 shape 的。

BadgeSymbol.swift

import SwiftUIstruct BadgeSymbol: View {    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)                ])            }        }    }}#if DEBUGstruct BadgeSymbol_Previews: PreviewProvider {    static var previews: some View {        BadgeSymbol()    }}#endif复制代码

3.5 绘制 symbol 的底部。

使用 move(to:) 方法在同一 path 中的多个 shapes 之间插入间隙。

BadgeSymbol.swift

import SwiftUIstruct BadgeSymbol: View {    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)                ])            }        }    }}#if DEBUGstruct BadgeSymbol_Previews: PreviewProvider {    static var previews: some View {        BadgeSymbol()    }}#endif复制代码

3.6 按照设计,给 symbol 填充颜色。

BadgeSymbol.swift

import SwiftUIstruct 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(Self.symbolColor)        }    }}#if DEBUGstruct BadgeSymbol_Previews: PreviewProvider {    static var previews: some View {        BadgeSymbol()    }}#endif复制代码

4. 组合 Badge 的前景和背景

设计中要求在徽章的背景上旋转并重复多次山峰的 shape

我们来定义一个新的旋转类型,并利用 ForEach view 让山峰 shape 的多个副本保持相同的设置 。

4.1 创建一个新的 RotatedBadgeSymbol view 来封装旋转的 symbol

在预览中调整角度来测试旋转的效果。

RotatedBadgeSymbol.swift

import SwiftUIstruct RotatedBadgeSymbol: View {    let angle: Angle        var body: some View {        BadgeSymbol()            .padding(-60)            .rotationEffect(angle, anchor: .bottom)    }}struct RotatedBadgeSymbol_Previews: PreviewProvider {    static var previews: some View {        RotatedBadgeSymbol(angle: .init(degrees: 5))    }}复制代码

4.2 在 Badge.swift 中,在一个 ZStack 中把徽章的 symbol 叠加在徽章的背景上。

Badge.swift

import SwiftUIstruct Badge: View {    var badgeSymbols: some View {        RotatedBadgeSymbol(angle: .init(degrees: 0))            .opacity(0.5)    }        var body: some View {        ZStack {            BadgeBackground()                        self.badgeSymbols        }    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

现在徽章 symbol 与预期的设计相比,它与背景的比例太大。

4.3 通过读取周围的几何图形并缩放 symbol 来修改徽章 symbol 的大小。

Badge.swift

import SwiftUIstruct Badge: View {    var badgeSymbols: some View {        RotatedBadgeSymbol(angle: .init(degrees: 0))            .opacity(0.5)    }        var body: some View {        ZStack {            BadgeBackground()                        GeometryReader { geometry in                self.badgeSymbols                    .scaleEffect(1.0 / 4.0, anchor: .top)                    .position(x: geometry.size.width / 2.0, y: (3.0 / 4.0) * geometry.size.height)            }        }    }}#if DEBUGstruct Badge_Previews: PreviewProvider {    static var previews: some View {        Badge()    }}#endif复制代码

4.4 添加 ForEach view 来旋转并显示徽章 symbol 的副本。

完整的 360° 旋转分为八个部分,通过重复山峰 symbol 来创建一个类似太阳的样式。

Badge.swift

import SwiftUIstruct Badge: View {    static let rotationCount = 8        var badgeSymbols: some View {        ForEach(0..

转载于:https://juejin.im/post/5cfb825f6fb9a07ed52491c8

你可能感兴趣的文章
.htaccess更改目录下的默认主页
查看>>
Android WindowManager实现悬浮窗效果 (一)——与当前Activity绑定
查看>>
hdu 4717 Tree2cycle(树形DP)
查看>>
镜像的使用(6-13)
查看>>
SQL Server 时间戳与时间格式互相转换
查看>>
RabbitMQ入门-Topic模式
查看>>
多线程面试体系列(13):多线程同步内功心法——PV操作下
查看>>
Work
查看>>
[开源]快速构建文件下载,支持文件加密,自定义限速
查看>>
Mac系统搭建java开发环境
查看>>
菜鸟对新技术的一点看法
查看>>
2016年2月23日----Javascript全局变量和局部变量
查看>>
iOS开发基础知识-多线程概念深入浅出
查看>>
论PHP框架设计模式及MVC的缺陷
查看>>
立flag(java)
查看>>
7-38 数列求和-加强版(20 分)
查看>>
python----字典
查看>>
开发环境eclipse for Mac 下的常用快捷键汇总(基本参照Win系,将Ctrl换为Command)
查看>>
tree与GridView交互
查看>>
zz 鸡汤穷三代,励志毁一生
查看>>