SwiftUI学习笔记[path绘制]

2,907 阅读8分钟

前言

绘制的底层是强大的,我们所用的各端语言只是在现代UI追求的步伐中和用户喜好的交互中求同存异,抽取封装出自成个性风格的UI控件,当然面对万亿级别的客户各个平台的UI库出也不可能满足所有的客户需求,当然一门语言的可制定性也意味着其强大,几乎每个平台都提供了接口让开发者创造其UI的可能性,更可能的能满足客户需求。

一,Path 绘制【直线、圆弧、圆锥曲线、贝塞尔曲线等】

1.Path

  • SwiftUI两种方式来自定义绘图:路径(path)和形状(shaper)。路径是一系列绘图指令,例如:从这里开始moveTo到哪里lineTo,在哪里添加形状例如增加矩形,圆圈,椭圆,曲线其他的线段等addRect,addCircle,addPath...”,所有这些都使用绝对坐标。形状是用路径构建的,所以一旦你理解了路径,形状就很容易了。而且,就像路径、颜色和渐变一样,形状也是视图,这意味着我们可以将它们与文本视图、图像等一起配合使用。

    方法作用案例
    path绘制的基本单位path.很多方法【move,addLine,addLines,addArc....】
    move即从哪一点开始path.move(to: CGPoint(x:0, y:0))
    addLine连接到某一点path.addLine(to: CGPoint(x:100, y:100))

1.0 在SwiftUI直接可在视图中进行Path绘制即当作是视图来使用:

struct SwiftUI_Path01: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //直接绘制
        Path{path in
           
        }.stroke(Color.red, lineWidth:3)
        .border(Color.blue,width:2)
    }
}

2.0 SwiftUI中可以实现Shape来进行绘制:

//实现Shape
struct DaoShaper:Shape{
    func path(in rect: CGRect) -> Path {
        var path=Path()
        //...相关绘制操作。
        return path
    }
    
}
//调用
struct SwiftUI_Path01: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //直接绘制
       DaoShaper().stroke(Color.red, lineWidth:3)
        .border(Color.blue,width:2)
    }
}

2.Path画线

绘制会在视图中进行,写过Android和Flutter以及H5的都知道,默认的绘制坐标系原点默认都在画布canvas的左上角,如果画布占满屏幕也就是屏幕的左上角。我们绘制一个从(0,0)开始到(100,100)的线段,我们来测验是否同其他端一样:

struct SwiftUI_Path01: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //由于Path也是视图我们可以直接在View里面进行绘制如下。
        Path{path in
            path.move(to: CGPoint(x:0, y:0))
            path.addLine(to: CGPoint(x:100, y:100))
        }.stroke(Color.red, lineWidth:3)//stroke设置path颜色?
        .border(Color.blue,width:2)//border给画布绘制边框
    }
}


struct SwiftUI_Path01_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUI_Path01()
    }
}

3.Path画网格

坐标系如下:左上角原点,右下方方向也就意味着屏幕左上角坐标为(0,0),屏幕左下角坐标为(0,screenHeight),屏幕右上角为(0,scrrenWidth),屏幕右下角为(screenWidth,scrrenHeight),相当于数学坐标系的第一象限

接下来我们来绘制一个网格,这样我们可以明确的看到我们的线所经过的点,后面的学习也会很好的定位了。什么是网格呢如下:

  • 我们先来一个左上角为圆心的坐标系。首先我们明确圆心在左上角。按照间距20来画,然后我们横着画线的根数=height(高度)/20,竖着画的根数=width(宽度)/20,如下图,循环绘制横线和竖线即可生成方格坐标系。这里需要知道获取当前控件内部宽高的容器控件GeometryReader如果这里使用屏幕宽和高度就需要用UIScreen.main.bounds

首先我们来绘制横线代码如下,我们画一把刀最好有坐标系可以根据坐标点进行精确的定位。

import SwiftUI

struct SwiftUIView_Path02: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //看了Api也知道Path也是视图我们可以直接在View里面进行绘制如下。
        let mwidth=screenBounds.width;
        let mheight=screenBounds.height;
        let vcount=mheight/20
        Path{path in
            for i in 0..<Int(vcount){
                path.move(to: CGPoint(x:0,y:i*20))
                path.addLine(to:CGPoint(x:Double(mwidth),y:(20.0*Double(i))))
             
            }
        }.stroke(Color.gray, lineWidth:1)//stroke设置path颜色?
        .border(Color.blue,width:2)//border给画布绘制边框
    }
}

struct SwiftUIView_Path02_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView_Path02()
    }
}

效果如下

  • 同样方法的画竖着的线代码如下。
struct SwiftUIView_Path02: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //看了Api也知道Path也是视图我们可以直接在View里面进行绘制如下。
        let mwidth=screenBounds.width;
        let mheight=screenBounds.height;
        let vcount=mheight/20
        let hcount=mwidth/20
        Path{path in
            //画横着的线
            for i in 0..<Int(vcount){
                path.move(to: CGPoint(x:0,y:i*20))
                path.addLine(to:CGPoint(x:Double(mwidth),y:(20.0*Double(i))))
             
            }
            
            //画竖着的线
            for i in 0..<Int(hcount+1){
                path.move(to: CGPoint(x:i*20,y:0))
                
                path.addLine(to:CGPoint(x:20*i,y:Int(mheight)))
             
            }
            
            
        }.stroke(Color.gray.opacity(0.5), lineWidth:1)//stroke设置path颜色?
        .border(Color.blue,width:2)//border给画布绘制边框
    }
}
  • 展示如下

4.Path画刀

1.绘制刀: lineTo

接下来我们在所画的坐标系中进行绘制一把用矩形绘制的刀。至于我们的弧度要求等我们学习完塞贝尔曲线后面再来。下面零时画来个刀...其实我的画画水平不错的😂。

  • 代码开始 首先我们找一点,刀把的左上角开始(x:180,y:50),依次根据感觉画出坐标点即可代码如下:
import SwiftUI

struct SwiftUIView_Path02: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //看了Api也知道Path也是视图我们可以直接在View里面进行绘制如下。
        let mwidth=screenBounds.width;
        let mheight=screenBounds.height;
        let vcount=mheight/20
        let hcount=mwidth/20
        ZStack{
        Path{path in
            //画横着的线
            for i in 0..<Int(vcount){
                path.move(to: CGPoint(x:0,y:i*20))
                path.addLine(to:CGPoint(x:Double(mwidth),y:(20.0*Double(i))))
             
            }
            
            //画竖着的线
            for i in 0..<Int(hcount+1){
                path.move(to: CGPoint(x:i*20,y:0))
                path.addLine(to:CGPoint(x:20*i,y:Int(mheight)))
             
            }
            //刀的开始
            path.move(to:CGPoint(x:180,y:40))
            path.addLine(to: CGPoint(x: 240, y:20))
            path.addLine(to: CGPoint(x:260,y:40))
            path.addLine(to: CGPoint(x:225,y:40))
            path.addLine(to: CGPoint(x:225,y:140))
            path.addLine(to: CGPoint(x:280,y:180))
            path.addLine(to: CGPoint(x:270,y:200))
            path.addLine(to: CGPoint(x:225,y:190))
            path.addLine(to: CGPoint(x:255,y:700))
            path.addLine(to: CGPoint(x:180,y:600))
            path.addLine(to: CGPoint(x:190,y:180))
            path.addLine(to: CGPoint(x:170,y:170))
            path.addLine(to: CGPoint(x:180,y:140))
            path.addLine(to: CGPoint(x:198,y:130))
            path.addLine(to: CGPoint(x:199,y:40))
            path.addLine(to: CGPoint(x:180,y:40))
            
        }.stroke(Color.gray.opacity(0.5), lineWidth:1)//stroke设置path颜色?
        .border(Color.blue,width:2)//border给画布绘制边框

        }
    }
}

struct SwiftUIView_Path02_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView_Path02()
    }
}

效果如下:

2.改变Path的颜色 .stroke()

我们发现这个上一个图中刀的路径和坐标网格都是灰色,能不能搞个其他的颜色或者贴个材质,ok完全没问题,我们慢慢来。

  • 这里我算是比较头大的,至少相对比Flutter和Android,Swift在绘制中将Path提升为视图级别而且没有paint,反而开发者做了大量的无用且多余的工作。我们在Flutter中通过在一个画布canvas上可以用任意多个path我们只需要修改画笔的颜色既可以修改任意线段的颜色,代码集中其好理解。而Swift中我们无法修改在一个Path绘制的多条链接线段有不同的颜色。例如:我在上面同一个Path里同时面绘制了底图坐标系,我只是想修改一下刀的颜色和底图坐标系设置不一样,但是并没有提供画笔,一条path有且只有一个颜色,这点我认为没错。但是我们是间断的绘制线段却无法修改想要修改线段的颜色或者粗细等....这里让我在绘制中产生了抵触。

但是我们能不能实现底图灰色,刀红色的效果了,毫无疑问是可以的。只要你喜欢探索和想象可能度娘上没有的你也可以创造,这里我是想到Path既然是视图那么我们可以用ZStack来当作画布,那么ZStack里面可以扔无数的Path路径视图了。所以我们将底图坐标系部分和刀部分分开扔到ZStack里面代码如下:

import SwiftUI

struct SwiftUIView_Path02: View {
    var screenBounds:CGRect = UIScreen.main.bounds//mainScreen().bounds
    var body: some View {
        //看了Api也知道Path也是视图我们可以直接在View里面进行绘制如下。
        let mwidth=screenBounds.width;
        let mheight=screenBounds.height;
        let vcount=mheight/20
        let hcount=mwidth/20
        ZStack{
            //一坐标系部分
            Path{path in
                //画横着的线
                for i in 0..<Int(vcount){
                    path.move(to: CGPoint(x:0,y:i*20))
                    path.addLine(to:CGPoint(x:Double(mwidth),y:(20.0*Double(i))))
                 
                }
                
                //画竖着的线
                for i in 0..<Int(hcount+1){
                    path.move(to: CGPoint(x:i*20,y:0))
                    path.addLine(to:CGPoint(x:20*i,y:Int(mheight)))
                 
                }
               
            }.stroke(Color.gray.opacity(0.5), lineWidth:1)//stroke设置path颜色?
            .border(Color.blue,width:2)//border给画布绘制边框
            //2.刀部分的path
            Path{path in
                path.move(to:CGPoint(x:180,y:40))
                path.addLine(to: CGPoint(x: 240, y:20))
                path.addLine(to: CGPoint(x:260,y:40))
                path.addLine(to: CGPoint(x:225,y:40))
                path.addLine(to: CGPoint(x:225,y:140))
                path.addLine(to: CGPoint(x:280,y:180))
                path.addLine(to: CGPoint(x:270,y:200))
                path.addLine(to: CGPoint(x:225,y:190))
                path.addLine(to: CGPoint(x:255,y:700))
                path.addLine(to: CGPoint(x:180,y:600))
                path.addLine(to: CGPoint(x:190,y:180))
                path.addLine(to: CGPoint(x:170,y:170))
                path.addLine(to: CGPoint(x:180,y:140))
                path.addLine(to: CGPoint(x:198,y:130))
                path.addLine(to: CGPoint(x:199,y:40))
                path.addLine(to: CGPoint(x:180,y:40))
                
            }.stroke(Color.red.opacity(0.5), lineWidth:11)//stroke设置path颜色?
            .border(Color.blue,width:2)//border给画布绘制边框
           
        }
    }
}

struct SwiftUIView_Path02_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView_Path02()
    }
}

3.Path闭合区域填冲颜色或者渐变色:.fill(..)

可能有人需要给这个区域填充颜色那么我们只需要.fill进行填充代码如下:

  • 记住fill和stroke不能共存
    //给路径包裹区域填充渐变或者颜色
    Path{path in

    }.fill(LinearGradient(gradient: Gradient(colors: [.white,.pink,.clear,.gray,.green,.purple,.black,.blue,.red,.orange]), startPoint:.top, endPoint:.bottom))

4.给Path路径或者Path闭合区域添加阴影:.shandow(..)

 //给Path路径添加颜色
 Path{path in
                path.move(to:CGPoint(x:180,y:40))
                path.addLine(to: CGPoint(x: 240, y:20))
                path.addLine(to: CGPoint(x:260,y:40))
                path.addLine(to: CGPoint(x:225,y:40))
                path.addLine(to: CGPoint(x:225,y:140))
                path.addLine(to: CGPoint(x:280,y:180))
                path.addLine(to: CGPoint(x:270,y:200))
                path.addLine(to: CGPoint(x:225,y:190))
                path.addLine(to: CGPoint(x:255,y:700))
                path.addLine(to: CGPoint(x:180,y:600))
                path.addLine(to: CGPoint(x:190,y:180))
                path.addLine(to: CGPoint(x:170,y:170))
                path.addLine(to: CGPoint(x:180,y:140))
                path.addLine(to: CGPoint(x:198,y:130))
                path.addLine(to: CGPoint(x:199,y:40))
                path.addLine(to: CGPoint(x:180,y:40))
            }.stroke(LinearGradient(gradient: Gradient(colors: [.white,.pink,.clear,.gray,.green,.purple,.black,.blue,.red,.orange]), startPoint:.top, endPoint:.bottom),lineWidth:3)
            .shadow(color: Color.pink,radius: 12.0,x: CGFloat(11),y: CGFloat(11))
            
//给Path闭合区域添加渐变色
  Path{path in
                path.move(to:CGPoint(x:180,y:40))
                path.addLine(to: CGPoint(x: 240, y:20))
                path.addLine(to: CGPoint(x:260,y:40))
                path.addLine(to: CGPoint(x:225,y:40))
                path.addLine(to: CGPoint(x:225,y:140))
                path.addLine(to: CGPoint(x:280,y:180))
                path.addLine(to: CGPoint(x:270,y:200))
                path.addLine(to: CGPoint(x:225,y:190))
                path.addLine(to: CGPoint(x:255,y:700))
                path.addLine(to: CGPoint(x:180,y:600))
                path.addLine(to: CGPoint(x:190,y:180))
                path.addLine(to: CGPoint(x:170,y:170))
                path.addLine(to: CGPoint(x:180,y:140))
                path.addLine(to: CGPoint(x:198,y:130))
                path.addLine(to: CGPoint(x:199,y:40))
                path.addLine(to: CGPoint(x:180,y:40))

            }.fill(LinearGradient(gradient: Gradient(colors: [.white,.pink,.clear,.gray,.green,.purple,.black,.blue,.red,.orange]), startPoint:.top, endPoint:.bottom))//.stroke(Color.red.opacity(0.5)
            .shadow(color: Color.pink,radius: 12.0,x: CGFloat(11),y: CGFloat(11))

  • 小总结:Path作为视图我们可以通过stroke(_ content: S, lineWidth: CGFloat = 1)来设置路径的颜色和粗细,.fill(color)来设置填充色等。填补了无Flutter以及Android里的Paint的缺陷。到此我们可以总结相关的属性如下表:

    方法作用案例
    .fill通过颜色或者渐变来填充路径闭合区域.fill(Color.red)fill(LinearGradient(gradient: Gradient(colors: [.white,.pink,.clear,.gray,.green,.purple,.black,.blue,.red,.orange]), startPoint:.top, endPoint:.bottom))
    .stroke通过颜色或渐变来填充路径颜色.stroke(Color.red,lineWidth:3)stroke(LinearGradient(gradient: Gradient(colors:[.white,.pink,.clear,.gray,.green,.purple,.black,.blue,.red,.orange])
    .shandow为路径或路径闭合区域绘制阴影.shadow(color:Color.black.opacity(0.4),radius: 20,x:33,y:10)

接下来QQ群有小伙伴提出,能不能画一个有凸起的按钮。我们绘制已经学了路径绘制,填充渐变色,也有了ZStack代替canvas等,所以我们可以做很多东西接下来我们来看看效果去实现它:

慢慢写....晚上回家看老婆让不让写了...明天继续