美文网首页SwiftUI - 基础到实战
SwiftUI - 常用Views / Modifies(三)

SwiftUI - 常用Views / Modifies(三)

作者: Lcr111 | 来源:发表于2023-02-24 15:03 被阅读0次

前言

今天没有前言,直接进入主题吧...😁

1、Form

Form表单,新建一个表单,发现和List并无太大差别,只是界面背景置灰了,显示内容会自动有个margin

Form{
       Section(header: Text("第一组"), footer: Text("Footer")) {
              Text("form")
              Text("form")
              Text("form")
       }
       Section() {
                Text("form")
                Text("form")
                Text("form")
       }
}

样式看起来和List差不多。但Form真正比较实用的地方类似于设置功能界面。

@State var pick = 0
@State var toggle1 = false
@State var toggle2 = true

NavigationView {
            Form {
                Section(header: Text("自动下载"), footer: Text("自动下载在其他的设备上新购买的项目(含免费项目)。")) {
                    Toggle("App", isOn: $toggle1)
                    Toggle("App更新", isOn: $toggle1)
                }
                Section(header: Text("蜂窝数据"), footer: Text("仅允许 200 MB以下的 App 使用蜂窝数据自动下载。")) {
                    Toggle("自动下载", isOn: $toggle1)
                    Picker("App 下载", selection: $pick) {
                        Text("始终允许").tag(0)
                        Text("超过 200 MB时询问").tag(1)
                        Text("始终询问").tag(2)
                    }
                }
                Section(footer: Text("在App Store中自动播放 App 预览视频。")) {
                    Picker("视频自动播放", selection: $pick) {
                        Text("开").tag(0)
                        Text("仅限 Wi-Fi").tag(1)
                        Text("关").tag(2)
                    }
                }
                Section(footer: Text("允许 App 询问产品使用反馈,以帮助开发者和其他用户了解您的想法。")) {
                    Toggle("App 内评分及评论", isOn: $toggle2)
                }
                Section(header: Text("沙盒账户"), footer: Text("此账户将仅用于本地开发时,测试 App 内购买项目。您当前的 App Store 账户将用于 TestFlight App。")) {
                    Button("Apple ID:LogicEducation@qq.com") {}
                }
                Section(header: Text("隐私")) {
                    Button("App Store 与 Arcade 隐私") {}
                    Button("个性化推荐") {}
                }
            }.navigationTitle("App Store").navigationBarTitleDisplayMode(.inline)
        }

50行代码就能实现设置内功能界面,代码量少,但功能一个不落。


Form

查看Form的视图层级,发现它内部也是一个UITableViewWrapperView,也是对UITableView的包装,难怪和List那么相似。你可以试着将Form改成List,就会发现毫无变化。

2、ScrollView

ScrollView在使用上和List没什么差别,而且在底层cell的复用机制上也和UITableView一样。

struct customView: View {
    var text: String
    var body: some View {
        Text(text)
    }
    init(_ text: String) {
        print("create", text)
        self.text = text
    }
}
ScrollView{
     VStack {
          ForEach(0..<100){
              customView("Cell \($0)").font(.title)
          }
    }
 }

如上代码,运行后打印了100条,说明没有用到复用机制,当我们去掉当前容器VStack层后只打印18条,说明是VStack强制让子视图一次性加载。
ScrollViewReader SwiftUI提供了这么一个代理来帮助我们实现比如点击按钮滑动到列表底部这样的功能:

ScrollViewReader { proxy in
            Button("gotoBottom"){
                // 99 底部cell的ID
                proxy.scrollTo(99)
            }
            ScrollView{
                VStack {
                    //id 添加维一标识
                    ForEach(0..<100){
                        customView("Cell \($0)").font(.title).id($0)
                    }
                }
            }
        }
3、Grid

表格,在使用类似于之前的UICollectionView的效果。

LazyVGrid(columns: [GridItem(),GridItem()],spacing: 10){
            ForEach(0..<100,id: \.self) {
                Text("\($0)").frame(height: 100).frame(maxWidth: .infinity).background(.orange)
            }.padding(10).font(.title2)
        }

即可实现两列列表,但是此时不能上下滑动,我们需要将Grid放入ScrollView中才能滑动,这两者在使用上基本形影不离。
如果是宽度不规则的瀑布流样式,可以改变宽度范围来控制列数:

ScrollView{
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 150,maximum: 200))], alignment: .leading,spacing: 10){
             ForEach(0..<100,id: \.self) {
                   Text("\($0)").frame(height: 100).frame(width:CGFloat(arc4random() % 10)*10 + 100).background(.orange)
             }.padding(10).font(.title2)
      } 
 }
Grid使用
4、VideoPlayer

视频播放器需要引入AVKit框架,可直接使用VideoPlayer结构体构造简单播放器。

import AVKit

let url = "https://video.yinyuetai.com/e65a400cf89941439cf1175f69737800.mp4"
    var body: some View {
        VStack{
            VideoPlayer(player: AVPlayer(url: URL.init(string: url)!),videoOverlay: {
                Text("play").font(.largeTitle).bold().foregroundColor(.white)
            }).ignoresSafeArea().frame(width: .infinity,height: 260)
            Spacer()
        }
    }

设置一定的尺寸就能满足简单的播放器需求。如果想实现另外的一些复杂功能,用法基本上和UIKit里是一样的。

5、Alert / AlertController

Alert提示弹框,样式还是和之前一样的,写法有所不同。

@State var show = false

Button ("show Alert") {
            show.toggle()
        }.alert(isPresented: $show) {
            Alert.init(title: Text("title"), message: Text("message"), primaryButton: .default(Text("cancle"),action: {
                print("cancle")
            }), secondaryButton: .destructive(Text("sure"),action: {
                print("sure")
            }))
        }

即可实现两个按钮的提示框,并监听按钮点击事件用以处理下一步操作。
当需要添加多个按钮,类似于UIAlertController的alert样式的弹出框,就需要像下面这样添加:

Button ("show Alert") {
            show.toggle()
        }.alert("title" ,isPresented: $show) {
            Button("btn1"){}
            Button("btn2"){}
            Button("btn3"){}
        }

此种写法,当不给添加按钮时候,默认样式是一个带ok按钮的提示弹窗。
那如果想实现底部弹出类似Sheet的样式,就需要用到.confirmationDialogActionSheet已经废弃不用。

Button ("show Alert") {
            show.toggle()
        }.confirmationDialog("sheet", isPresented: $show) {
            Button("btn1"){}
            Button("btn2"){}
            Button("btn3"){}
        }

底部弹出会自带一个cancle按钮。

6、显式动画
Button("animation"){
            withAnimation {
                scaleAmount += 44
            }
        }.font(.title).padding().background(.orange)
            .cornerRadius(20)
            .rotation3DEffect(.degrees(scaleAmount), axis: (x: 1, y: 0, z: 0))

创建一个按钮,在3D视角上,点击时就沿x轴旋转一定的角度。

Button("animation"){
            withAnimation(.easeInOut) {
                scaleAmount.toggle()
            }
        }
        .font(.title).padding().background(scaleAmount ? .white : .orange)
        //背景变换
        .animation(.easeInOut.repeatForever(autoreverses:true), value: scaleAmount)
        .cornerRadius(20)
        //缩放
        .scaleEffect(scaleAmount ? 1.1 : 1)
        .animation(.easeInOut.repeatForever(), value: scaleAmount)
        //边框发散
        .overlay{
                RoundedRectangle(cornerRadius: 20)
                .stroke(.red)
                .scaleEffect(scaleAmount ? 2 : 1)
                .opacity(scaleAmount ? 0 : 1)
                .animation(.easeInOut(duration: 1).repeatForever(autoreverses: false), value: scaleAmount)
            }

复杂动画,就是后面的动画不会影响前面的动画。
下面再写个关于改变位置的动画效果:

@State var isVstack:Bool = false
@Namespace var nameSpace

Group {
            if isVstack{
                VStack{
                    Image(uiImage: UIImage.init(named: "2222")!).resizable().frame(width: 150, height: 150).matchedGeometryEffect(id: "image", in: nameSpace)
                    Text("Hello Lcr").font(.largeTitle).matchedGeometryEffect(id: "text", in: nameSpace)
                }
            }else{
                HStack{
                    Text("Hello Lcr").font(.largeTitle).matchedGeometryEffect(id: "text", in: nameSpace)
                    Image(uiImage: UIImage.init(named: "2222")!).resizable().frame(width: 150, height: 150).matchedGeometryEffect(id: "image", in: nameSpace)
                }
            }
        }.onTapGesture {
            withAnimation {
                isVstack.toggle()
            }
        }

如果不加上.matchedGeometryEffect,效果就只是更改位置而没有动画效果。

动画效果图
7、手势

一些基本的手势如点击(.onTapGesture)长按(.onLongPressGesture)等使用比较简单,大家可以自己试试,另外的旋转缩放拖动稍微复杂点,需要搭配.gesture()来使用,下面写个例子来看看缩放效果:

@State var currentAmount: CGFloat = 0
@State var finalAmount: CGFloat = 1

Text("Hello Lcr").bold().font(.title).padding().background(.green).scaleEffect(currentAmount + finalAmount)
            .gesture(
                MagnificationGesture()
                    .onChanged({ amount in
                        print(amount)
                        currentAmount = amount - 1
                    }).onEnded({ amount in
                        finalAmount += currentAmount
                        currentAmount = 0
                    })
            
            )

SwiftUI里点击事件的响应也是和UIKit中一样的,子视图比父视图响应的优先级更高,我们可以利用.highPriorityGesture来将响应者更改,可以理解为高优先级的手势:

.highPriorityGesture(
            TapGesture().onEnded({ _ in
                print("VStack tap")
            })
        )

这样就能使响应优先级反转。
需求一:父子视图同时响应点击事件,那么可将highPriorityGesture替换成simultaneousGesture即可。
需求二:长按3秒之后可拖动,我们就需要借助于.sequenced序列来对两种手势进行组合:

let combined = pressGesture.sequenced{before: dragGesture}

SwiftUI手势使用中,我们需要利用.onChanged.onEnded等来获取手势的状态来实现一些功能上的操作。以及利用withAnimation{}来结合手势去完成一些动画效果。

最后我们来实现一个比较有意思的例子:

let words = Array("Lcr is a gentleman")
    @State var enabled = false
    @State var dragAmount = CGSize.zero
    var body: some View {
        HStack(spacing: 0) {
            ForEach(0..<words.count) {num in
                Text(String(words[num])).bold().font(.title).foregroundColor(.white)
                    .padding(2)
                    .background(enabled ? .orange : .red)
                    .offset(dragAmount)
                    .animation(Animation.default.delay(Double(num) / 20), value: dragAmount)
            }
        }.gesture(
            DragGesture()
                .onChanged {
                    dragAmount = $0.translation
                }
                .onEnded {_ in
                    dragAmount = .zero
                    enabled.toggle()
                }
        )
    }
手势动画
8、ToolBar

给界面顶部导航条添加按钮,之前用的是.navigationBarItems(),现在推荐使用.toolbar{}:

NavigationView {
            Text("Hello Lcr").font(.title)
                .navigationTitle("navi")
                //方式一 已废弃
                //.navigationBarItems(trailing: EditButton())
                //方式二
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("Edit"){}
                    }
                }
        }

.toolbar方式可以很轻松自定义一些自己想要的按钮效果,文字图片都行。然后可以切换placement不同样式,查看按钮位置的不一样,以及多个ToolbarItem按钮之间的优先级(布局上的)。

9、fullScreenCover

.sheet()展示效果类似于representedViewController,不能铺满全屏,如果需要铺满,可以使用.fullScreenCover()即可:

@State var show = false

VStack {
            Text("Hello Lcr").fullScreenCover(isPresented: $show) {
                Text("Detail")
            }
            Button("show"){
                show.toggle()
            }
        }

也可用sheet替代fullScreenCover看看不一样的效果。

我们通过三篇文章简单介绍SwiftUI中一些常用的Views和Modifies,暂时也有个印象,项目中用一用,敲一敲就更熟悉了。

相关文章

网友评论

    本文标题:SwiftUI - 常用Views / Modifies(三)

    本文链接:https://www.haomeiwen.com/subject/obaakdtx.html