SwiftUI 学习笔记(四):常见的 View 和 Modifier 解析(三)

DatePicker

DatePicker 为日期选择器,对应 UIKit 中的 UIDatePicker。我们需要绑定一个 Date 类型的变量来记录当前选择的日期。

struct ContentView: View {
    
    @State private var birthDay: Date = Date() //绑定日期
    
    var body: some View {
        //第一个参数为绑定的参数,第二个参数为显示的日期内容
        DatePicker(selection: $birthDay, displayedComponents: .date) {
            Text("出生日期")
        }.environment(\.locale, Locale(identifier: "zh_CN")) //默认为英文选择器,这里指定为中文
    }
}

当然我们实际使用的时候要求大多不会那么简单,下面给一个较为全面的使用案例。

struct ContentView: View {
    
    @State var selectedDate = Date()

    //日期格式化
    var formatter: DateFormatter {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
        return dateFormatter
    }
    //日期选择范围
    var dateClosedRange: ClosedRange<Date> {
        let min = Calendar.current.date(byAdding: .day, value: -10, to: Date())!
        let max = Calendar.current.date(byAdding: .day, value: 10, to: Date())!
        return min...max
    }
    var body: some View {
        VStack{
            //传入多种参数构造日期选择器
            DatePicker(
                selection: $selectedDate,
                in: dateClosedRange,
                displayedComponents: [.hourAndMinute, .date], //显示日期内容
                label: { Text("选择日期") }
            ).padding()
            Text(formatter.string(from: selectedDate))
        }
    }
}

onTapGesture()

和 UIKit 类似,Text 和 Image 不能接收点击事件,但是我们可以通过 .onTapGesture 修饰符添加点击事件,类似 UIKit 中的 addGestureRecognizer 方法。

struct ContentView: View {
    
    var body: some View {        
        Text("文本")
            .onTapGesture {
                print("单击")
        }
    }
}
struct ContentView: View {
    
    var body: some View {
        Image(systemName: "clock")
            .onTapGesture(count: 2) {
                print("双击")
        }
    }
}

SwiftUI View 的生命周期

在先前 UIKit 框架中,视图控制器 UIViewController 有很多 UIView 的生命周期函数,例如 viewDidLoad、viewDidAppear 等,而目前在 SwiftUI 中仅提供了 onAppear 和 onDisappear。

struct ContentView: View {
    
    var body: some View {
        Text("Hello SwiftUI")
            .onAppear() { //Text("Hello SwiftUI")显示在屏幕上时触发
                print("onAppear")
        }
            .onDisappear() { //Text("Hello SwiftUI")在屏幕上消失时触发
                print("onDisappear")
        }
    }
}

Group

Group 是 SwiftUI 中新增的,它可以把很多的 View 组合成一个 View(类似 ppt 中的组合)。Group 本身没有效果,只是一个容器,且容器内的元素不能超过 10,否则会报错。

struct ContentView: View {
    
    var body: some View {
        VStack {
            Group {
                Text("1")
                Text("2")
                Text("3")
                Text("4")
                Text("5")
            }
            Group {
                Text("6")
                Text("7")
                Text("8")
                Text("9")
                Text("10")
            }
        }
    }
}

与 Group相同,VStack、HStack、ZStack、List也有 10个元素内容的限制。

不同于 VStack/HStack/ZStack,Group 是完全透明的。将 Group 嵌入 VStack 中时,它的行为就像 VStack 一样,按照垂直方向排列子 View;嵌入到 HStack 中时,按照水平方向排列子 View。

此外,Group 的 Modifier 会作用于里面每个 View。

struct ContentView: View {
    
    var body: some View {
        VStack {
            Group {
                Text("Hello")
                Text("SwiftUI")
                Image(systemName: "heart")
            }
            .foregroundColor(Color.red)
            .padding()
        }
    }
}

ScrollView

ScrollView 对应 UIKit 中的 UIScrollView,用于处理滑动的 UI 逻辑。

struct ContentView: View {
    
    var body: some View {
        
        //参数1:滚动方向(此处为垂直滚动),参数2:是否显示滚动条,参数3:滚动内容
        ScrollView(.vertical, showsIndicators: false, content: {
            //放置具体滚动内容
            Text("SwiftUI").padding(20)
            
            Divider()
            
            Rectangle()
                .foregroundColor(.orange)
                .frame(width: UIScreen.main.bounds.size.width * 0.5, height: 1500, alignment: .center)
            
            Divider()
            
            Text("Example")
        })
    }
}
struct ContentView: View {
    
    let imageNames = ["img","img","img","img","img","img","img"] //图片资源
    
    var body: some View {
        
        //横向滚动
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                //展示一组圆角图片
                ForEach(imageNames, id: \.self) { imageName in
                    Image(imageName)
                        .resizable()
                        .cornerRadius(15)
                        .frame(width: 100, height: 100)
                }.padding(.trailing, 10)
            }
        }
    }
}

NavigationView

NavigationView 对应 UIKit 中的 NavigationController,为 iOS 顶部的导航栏控件。导航的标题使用 .navigationBarTitle 设置,默认显示的是大标题样式,可以在构造函数中设置 displayMode: .inline 变为传统标题。

struct ContentView: View {
    
    var body: some View {
        NavigationView {
            Text("SwiftUI")
                .navigationBarTitle("标题")
//             .navigationBarTitle("标题", displayMode: .inline)
        }
    }
}

navigationBarTitle一定要设置给 NavigationView中最外层的那个 View。

导航栏上的按钮我们同样可以用 navigationBarItems 来设置。

struct ContentView: View {
    
    var body: some View {
        NavigationView {
            Text("SwiftUI")
                .navigationBarItems(leading: Button("设置"){
                    }, trailing: Button("编辑"){
                }).navigationBarTitle("标题", displayMode: .inline)
        }
    }
}

若我们不想在某个界面看到导航栏,可以设置 .navigationBarHidden(true) 将导航栏隐藏起来。

struct ContentView: View {
    
    var body: some View {
        NavigationView {
            Text("SwiftUI")
                .navigationBarTitle("标题")
                .navigationBarHidden(true)
        }
    }
}

TabView

TabView 为标签栏控件,对应 UIKit 的 UITabBarController。每一个 tabItem 代表一个标签栏,可以设置图片和文字。

struct ContentView: View {
    
    var body: some View {
        TabView {
            Text("微信")
                .tabItem {
                    Image(systemName: "message")
                    Text("微信")
            }
            
            Text("通讯录")
                .tabItem {
                    Image(systemName: "person.2")
                    Text("通讯录")
            }
        }
    }
}

若我们要设置默认选中,则先要设置 tag。

struct ContentView: View {
    
    @State var selected = 1
    
    var body: some View {
        TabView (selection: $selected){
            Text("微信")
                .tabItem {
                    Image(systemName: "message")
                    Text("微信")
            }.tag(0)
            
            Text("通讯录")
                .tabItem {
                    Image(systemName: "person.2")
                    Text("通讯录")
            }.tag(1)
        }
    }
}

TabView 嵌套 NavigationView

熟悉 UIKit 开发的朋友想必都写过 TabBarController 嵌套 NavigationController 的代码,因为这是大多数 App 的业务逻辑。下面我们就以微信为例,用 SwiftUI 来实现相同的逻辑。

struct ContentView: View {
    
    //借助UIKit设置导航栏和标签栏颜色
    init() {
        UITabBar.appearance().barTintColor = UIColor.lightGray
        //只针对inline模式有效
        UINavigationBar.appearance().barTintColor = .green
    }
    
    var body: some View {
        TabView {
            NavigationView{
                Text("微信").navigationBarTitle("微信", displayMode: .inline)
            }
            .tabItem {
                Image(systemName: "message")
                Text("微信")
            }.tag(0)
            NavigationView{
                Text("通讯录").navigationBarTitle("通讯录")
            }
            .tabItem {
                Image(systemName: "person.2")
                Text("通讯录")
            }.tag(1)
            NavigationView{
                Text("发现").navigationBarTitle("发现")
            }
            .tabItem {
                Image(systemName: "safari")
                Text("发现")
            }.tag(2)
            NavigationView{
                Text("我").navigationBarTitle("我")
            }
            .tabItem {
                Image(systemName: "person")
                Text("我")
            }.tag(3)
        }.accentColor(Color(red: 34/255.0, green: 172/255.0, blue: 37/255.0))
    }
}

如果我们要设置导航栏和标签栏颜色,目前仍需要借助于 UIKit 实现。

EmptyView

顾名思义,EmptyView 为空白 View,常用于占位。

struct ContentView: View {
    
    var body: some View {
        EmptyView()
    }
}

需要注意,在不设置背景(不显示)的情况下,是不能够响应事件的。

struct ContentView: View {
    
    var body: some View {
        EmptyView()
            .frame(width: 300, height: 300)
            .background(Color.red)
            .clipShape(Circle())
            .onTapGesture {
                print("onTap")
        }
    }
}

AnyView

AnyView 表示任意一个 View 的实例,常用于抹除具体的 View 类型。

前面我们讨论过,body 中只能返回一种类型的 View,但是可以用 AnyView 包装不同的 View 之后返回。

struct ContentView: View {
    
    @State var isLogin: Bool = false
    
    var body: some View {
        if isLogin {
            return AnyView(
                Text("您已登录")
            )
        } else {
            return AnyView(
                Button(action: {
                    self.isLogin.toggle()
                }, label: {
                    Text("您未登录")
                })
            )
        }
    } //若不用AnyView包装则会报错
}

ContextMenu

ContextMenu 用于创建弹出式菜单。在 iOS 中通过 3D Touch 或长按触发,在 macOS 中通过单击鼠标右键触发。

struct ContentView: View {
    
    @State var backgroundColor: Color = Color.orange
    
    var body: some View {
        NavigationView {
            ZStack {
                backgroundColor //背景色
                
                Text("SwiftUI")
            }
            .navigationBarItems(leading: Button("设置") {
                print("点击了设置")
            }.contextMenu {
                //第一个
                Button(action: {
                    self.backgroundColor = Color.blue
                }) {
                    Text("更新")
                    
                    Image(systemName: "pencil")
                }
                
                //第二个
                Button(action: {
                    self.backgroundColor = Color.red
                }) {
                    Text("删除")
                    
                    Image(systemName: "trash")
                }
                
                //第三个
                Button(action: {
                    self.backgroundColor = Color.green
                }) {
                    Text("添加")
                    
                    Image(systemName: "plus")
                }
            })
                .navigationBarTitle("标题", displayMode: .inline)
        }
    }
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注