75142913在线留言
CS193P2021学习笔记第二课:了解更多的SwiftUI信息_IOS开发_网络人

CS193P2021学习笔记第二课:了解更多的SwiftUI信息

Kwok 发表于:2021-06-29 16:29:41 点击:78 评论: 0

笔记内容中,其实最重要的是理解课堂代码,想一下老师为什么要这样写代码,及代码的演变过程,对于以后的编程会很大的帮助。这节课有以下几个要点:

一、视图重复利用

前一课中使用ZStack布局的方式把一个圆角矩形做为背影放到了文字的下面,如果我们项目中有N个这一样的形状我们是否需要复制N份这样的代码,这肯定不是编程的好习惯,在未来的编程中,只要超过1份需要重复使用的代码,我们就开始尽可能的使其打包成一个结构体或者函数来使用。

var body: some View{
    HStack{
        ZStack{
            RoundedRectangle(cornerRadius: 20)
                .stroke(lineWidth: 3)
            Text("Hello").foregroundColor(.orange)
        }
        ZStack{
            RoundedRectangle(cornerRadius: 20)
                .stroke(lineWidth: 3)
            Text("Hello").foregroundColor(.orange)
        }
        //下面复制了20次ZStack
        //。。。。。。
    }.padding(.horizontal)
    .foregroundColor(.red)
}

代码打包成结构体以后:

struct ContentView: View {
    var body: some View{
        HStack{
            CardView()
            CardView()
            //下面将复制20次CardView()
            //...........
        }.padding(.horizontal)
        .foregroundColor(.red)
    }
}
//我们将上面需要重复使用的代码打包成了一个新的视图
struct CardView:View {
    var body: some View{
        ZStack{
            RoundedRectangle(cornerRadius: 20)
                .stroke(lineWidth: 3)
            Text("Hello").foregroundColor(.orange)
        }
    }
}

虽然我们还是需要复制20次来调用CardView,但是代码量是不是少了很多,而且我们只需要针对修改调用的代码就可以同时修改所有的CardView。

二、判断卡片状态是否向上

我们通过需要一个布尔值为判断卡片当前是朝上还是朝下,所以我们对代码进行了一些小的改动如下:

struct CardView:View {
    var isFaceUp:Bool
    var body: some View{
        ZStack{
            //下面使用let新增一个形状变量,长代码变短并可重复使用
            let shape = RoundedRectangle(cornerRadius: 20)
            if isFaceUp{
                shape.fill().foregroundColor(.white) //背景填充为白色
                shape.stroke(lineWidth: 3)//在暗黑模式下stroke背影是透明的
                Text("🐤").font(.largeTitle)//放入表情并将字体设置会大号
            }else{
                shape.fill()//卡片朝下不显示Text的内容
            }
        }
    }
}

isFaceUp 没有指定默认的值,所以需要在调用CardView的时候指定这个参数,如果指定了默认值,在调用时也指定了参数,那么参数将覆盖掉默认值,和其它语言中的参数调用是一样的。

三、点击手势与@State的使用

下面的代码中我们将被点击手势onTapGesture来修改isFaceUp的值,如果我们直接修改isFaceUp的值,会提示 “   Cannot assign to property: 'self' is immutable      “,无法分配属性给:self 是不可变的。基本上就是告诉我们,视图(self)是不能修改的,因为self下的isFaceUp是我们整个CardView视图的一部分,在SwiftUI里视图是不能被修改的,只能被重建。所以你看到的视图的每一次变化都是新的视图,而老的视图会结束生命周期被系统回收掉。这也是SwiftUI的特点,可以高效的重建UI以更新整个视图。

所以在就算我们定义的是var isFaceUp一但它被创建者初始化后或者指定了默认的值后(可以被调用者覆盖1次)也是无法修改其值的。这个值改变后视图将会立即重建被新的视图所替换(isFaceUp也被替换掉了,修改无意义),因为isFaceUp属于视图的一部分,同时将被系统回收掉,所以这个值是不能修改的,要解决这个问题,就是把这个值放到视图的外面去保存,使用一个@State修饰后,这个变量将保存到视图的外部,这里做为引用变量,关于数据流的介绍可以查看这里的详细介绍:http://www.55mx.com/ios/114.html。 

struct CardView:View {
   @State var isFaceUp:Bool //使用@State修饰后此变量将放到外部
    var body: some View{
        ZStack{
            //.... 这里代码与上面一样
        }.onTapGesture{
            isFaceUp.toggle() //isFaceUp = !isFaceUp
        }
    }
}

 在演示中尝试点击卡片可以看到已可以正常翻转。这里主要理解@State修饰属性,在实际开发中作为局部变量修饰使用,使用的机会并不多,在后面的课程里可以看到其它类似更好用的修饰。

四、修改卡片的内容

上面所有的卡片内容都是一只小黄鸭🐤,我们需要通过传参来实现每个卡片不同的内容。我们将分解成下列步骤来完成内容的增加。

1、在CardView视图里增加一个只读属性变量为content类型为String,即:

var content:String

2、替换 Text("🐤")为Text(content),这样我们卡片里就将会显示传入的参数。

Text(content).font(.largeTitle)

3、调用参数更新即:

CardView(isFaceUp: true, content: "🪰")
CardView(isFaceUp: true, content: "🦘")
CardView(isFaceUp: true, content: "🐌")
CardView(isFaceUp: true, content: "🐤")

这样卡片就会显示不同的内容了。 

CS193P2021学习笔记第二课了解更多的SwiftUI信息

但是在实际开发中我们永远不会把CardView复制多次,我们需要计算有多少显示显示的content内容来生成相同数量的CardView,我们有多种方式,这里课程中使用最基础的Array来存储content里的内容。

1、在ContentView里先定义一个数组emojis:Array用于存储表情:

var emojis:Array = ["🪰","🦘","🐌","🐤"]

上面的代码可以简写为:

var emojis:[String] = ["🪰","🦘","🐌","🐤"]

进一步简写:

var emojis = ["🪰","🦘","🐌","🐤"]

2、将CardView里的表情替换为数组里的值:

CardView(isFaceUp: true, content: emojis[0])
CardView(isFaceUp: true, content: emojis[1])
CardView(isFaceUp: true, content: emojis[2])
CardView(isFaceUp: true, content: emojis[3])

更新视图后可以看到显示的结果是一样的。

3、遍历emojis数组以创建CardView:

swiftUI里返回视图的遍历需要使用ForEach,使用ForEach有一个重点,放入进去的数据需要符合Identifiable协议,在swift里所以的struct都可以符合这个协议,只需要增加一个id属性即可。为什么Foreach需要遍历的数据符合Identifiable呢?这是因为显示的子视图需要重新排序,或者向其中添加新的内容,大概都就增删改查CRUD操作,既然有操作就需要知道对谁操作。这样我们就通过ID来识别被操作的对象。

所以Foreach需要知道Array中的哪些内容发生了变化。然后相应地调整视图。通过符合Identifiable协议来识别数组里的内容。下面的代码中我们将使用.self来做为识别ID。

ForEach(emojis,id:.self){ emoji in
    CardView(isFaceUp: true, content: emoji)
}

但遗憾的是String没有可识别ID,但字串一样的时候ID将变得不再唯一,这里就会出现新的问题,如果我们在数组里放入相同的2个字符串,通过Foreach遍历后生成的视图在识别上就会出现问题:

struct ContentView: View {
    var emojis = ["🪰","🪰","🦘","🐌","🐤"]
    var body: some View{
        HStack{
            ForEach(emojis,id:.self){ emoji in
                CardView(isFaceUp: true, content: emoji)
            }
        }.padding(.horizontal)
        .foregroundColor(.red)
    }
}

在预览里点击🪰会发现2个卡片同时被翻转了,说明Foreach无法区分相同的字符串。在后面代码里我们将解决这个问题。

五、实现手工增加、减少卡片

将增加一个按钮来实现对卡片的增加与删除功能。

1、增加一个大数组,里面有很多的表情:

var emojis = ["🪰","🦘","🐌","🐤","🦎","🐶","🐱","🐭","🐝","🏓","🥎","🏏","⛹🏼‍♀️","🚗","🦯","✈️","🚅","🚆","🚊","🚜","🛳","🚈","📀","🧭","🖨","⌚️","📡"]

2、限制Foreach的读取范围:

ForEach(emojis[0..<4],id:.self){ emoji in
    CardView(isFaceUp: true, content: emoji)
}

我们通过区间运算符来读取数组的范围,基础介绍:http://www.55mx.com/ios/99.html

3、让区间运算符的范围可变

我们定义一个区间运算的变量,让其可变来调整Foreach的范围:

@State var emojiCount = 6

而emojis的数组范围修改为emojis[0..<emojiCount]。

4、在底部增加一个点击按钮

var body: some View{
    VStack{ //增加一个VStack以达到按钮排列到底部的目的
        HStack{
            ForEach(emojis[0..<emojiCount],id:.self){ emoji in
                CardView(isFaceUp: true, content: emoji)
            }
        }.padding(.horizontal)
        .foregroundColor(.red)
        HStack{
            Button(action: {
                emojiCount -= 1 //点击减少数组范围
            }, label: {
                Text("删除")
            })
            Spacer()
            Button(action: {
                emojiCount += 1 //点击增加数组范围
            }, label: {
                Text("增加")
            })
        }.padding(.horizontal)        
    }
}

这样的代码会显示太比较臃肿,不适合排版,我们优化一下代码,将”删除“与”增加“按钮包装到一个变量里,在swiftUI里,如果没有参数传入的情况下,我们尽量使用var来定义视图。就像var body: some View一样,我们定义 var remove:some View与 var add:some View。

5、SF Symbols的使用

SF Symbols是swiftUI内置的图标库,通过官方下载查看里有数千个常用的图标,我们只需要像下面代码一样调用图片的名称就能使用了。综合上面合并优化后增加SF Symbols图标的代码如下:

//定义删除按钮
var remove:some View{
    Button(action: {
       if emojiCount > 1{ //限制取值范围不能小于1
           emojiCount -= 1
       }
    }){
        Image(systemName: "minus.circle")//显示为"减号"
    }
}
//定义增加按钮
var add:some View{
    Button{
       if emojiCount < emojis.count{ //限制取值范围不能大于数组值的总数
           emojiCount += 1
       }
     } label:{
        Image(systemName: "plus.circle") //图标来源SF Symbols库
    }
}
//注意看remove与add的Button写法是2种不同的简写方式

顶上的代码改成了:

HStack{
    remove
    Spacer()
    add
}
.font(.largeTitle)//可修改SF Symbols库里的图标大小
.padding(.horizontal)

六、使用LazyVGrid让卡片以网络方式排列

LazyVGrid是swiftUI 2.0提供的新功能,在上一版的课程中,使用的是逻辑判断的方式来使用网络化排序,这次swiftUI带来了方便好用的LazyVGrid与LazyHGrid:http://www.55mx.com/ios/118.html

1、使用网络化排列

我只需要将卡片的上一层HStack替换成下面的代码即可:

LazyVGrid(columns: [GridItem(.fixed(200)),GridItem(),GridItem()])

得到了下面的样子:

CS193P2021学习笔记第二课了解更多的SwiftUI信息

columns指定了一行有3列,其中第一列使用了固定宽度.fixed(200)。可以尝试将这个值改为 .flexible(minimum: 100, maximum: 200)试试。

2、调整卡片的大小比例

上面图片中可以看到卡片被压缩得比较矮了,我们如果指定固定的高度情况下并不能匹配各种设备,这里就需要一个按照比较自动调整的修饰器,.aspectRatio是一个强大的修饰器,可以让视图按照纵横比显示。我们只需要在CardView后面增加一个.aspectRatio(2/3,contentMode: .fit)就以2(宽):3(高)的方式显示卡片。

CardView(isFaceUp: true, content: emoji).aspectRatio(2/3,contentMode: .fit)

CS193P2021学习笔记第二课了解更多的SwiftUI信息

3、利用ScrollView正常显示卡片

当卡片大于一定数量会,下面的”加、减“按钮会被挤走,我们需要在LazyVGrid的上面一层增加一个ScrollView(滚动视图)来防止按钮被挤出屏幕以外。

4、使用strokeBorder替换掉stroke防止边框溢出到频幕外。

strokeBorder 返回一个视图,该视图是用前景色填充self的宽度大小的边框(又称内描边)的结果。这相当于用width / 2插入self,并以width作为线宽来描划结果形状。

stroke 返回一个新形状,它是self的描边副本,其行宽由lineWidth定义,而StrokeStyle的所有其他属性具有默认值。

5、使用.adaptive让卡片适应横屏

. adaptive可以让多个项目在一个灵活项目的空间排列。这种大小情况将一个或多个项放入分配给单个灵活项的空格中,使用提供的边界和间距来确定适合多少项。这种方法倾向于插入尽可能多的最小大小的项,但让它们增加到最大大小。

所以我们只支持把GridItem()替换掉即可,下面是本课最终代码为:

struct ContentView: View {
    var emojis = ["🪰","🦘","🐌","🐤","🦎","🐶","🐱","🐭","🐝","🏓","🥎","🏏","⛹🏼‍♀️","🚗","🦯","✈️","🚅","🚆","🚊","🚜","🛳","🚈","📀","🧭","🖨","⌚️","📡"]
    @State var emojiCount = 6
    var body: some View{
        VStack{
            ScrollView{
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 65))]){
                    ForEach(emojis[0..<emojiCount],id:.self){ emoji in
                        CardView(isFaceUp: true, content: emoji)
                            .aspectRatio(2/3,contentMode: .fit)
                    }
                }
                .foregroundColor(.red)
            }
            Spacer()
            HStack{
                remove
                Spacer()
                add
            }
            .font(.largeTitle)//可修改SF Symbols库里的图标大小
            .padding(.horizontal)
        }.padding(.horizontal)
    }
    var remove:some View{
        Button(action: {
            if emojiCount > 1{ //限制取值范围
                emojiCount -= 1
            }
        }){
            Image(systemName: "minus.circle")
        }
    }
    var add:some View{
        Button{
            if emojiCount < emojis.count{ //限制取值范围
                emojiCount += 1
            }
        } label:{
            Image(systemName: "plus.circle")
        }
    }
}
//我们将上面需要重复使用的代码打包成了一个新的视图
struct CardView:View {
    @State var isFaceUp:Bool //使用@State修饰后此变量将放到外部
    var content:String
    var body: some View{
        ZStack{
            //下面使用let新增一个形状变量,长代码变短并可重复使用
            let shape = RoundedRectangle(cornerRadius: 20)
            if isFaceUp{
                shape.fill().foregroundColor(.white) //背景填充为白色
                shape.strokeBorder(lineWidth: 3)//在暗黑模式下stroke背影是透明的
                Text(content).font(.largeTitle)//放入表情并将字体设置会大号
            }else{
                shape.fill()//卡片朝下不显示Text的内容
            }
        }.onTapGesture{
            isFaceUp.toggle() //isFaceUp = !isFaceUp
        }
    }
}

七、课后总结

在这节课里我们学习了视图重复利用,视图是重建的并不是可修改的、手势、状态等,通过代码的演变一步一步完善代码以实现增加、删除卡片。以网络化显示卡片等。

我们要熟悉本课的代码,重点是Lazy系列的使用,还有超实用的按纵横比指定大小参数(可以说是一个惊艳的修饰器)。老师一步一步的演示开发中怎么减少优化代码,合并代码等。学习完了前2课基本上对swiftUI有了初步的了解。后面的课程将能体验到更多的功能。

除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/145
标签:笔记SwiftUIcs193pKwok最后编辑于:2021-08-09 16:09:00
1
感谢打赏!

《CS193P2021学习笔记第二课:了解更多的SwiftUI信息》的网友评论(0)

本站推荐阅读

热门点击文章