Form
Form 是 SwiftUI 中新增的,用类似 UITableView 的风格创建一个表单,多用于 App 的设置界面。
struct ContentView: View {
@State var enableLocation = false
var body: some View {
NavigationView {
Form {
Text("louyu")
Toggle(isOn: $enableLocation) {
Text("开启通知权限")
}
Button("确定") {
}
}.navigationBarTitle(Text("设置"))
}
}
}
同样我们可以设置 Section 来实现分组效果。
struct ContentView: View {
@State var enableLocation = false
var body: some View {
NavigationView {
Form {
Section(header: Text("第一部分header"), footer: Text("第一部分footer")) {
Text("louyu")
Toggle(isOn: $enableLocation) {
Text("开启通知权限")
}
}
Section(header: Text("第二部分header")) {
Button("确定") {
}
}
}.navigationBarTitle(Text("设置"))
}
}
}
实现对 Form 输入内容的验证,可以通过设置 Section 的 disable 来解决。
struct ContentView: View {
@State private var username = ""
@State private var password = ""
//验证条件
var validation: Bool {
username.count < 6 || password.count < 6
}
var body: some View {
Form {
Section {
TextField("用户名", text: $username)
SecureField("密码", text: $password)
}
Section {
Button("登录") {
}
}.disabled(validation) // 如果不满足条件,按钮无法点击
}
}
}
Alert
在 UIKit 中,Alert 和 ActionSheet 为 UIAlertController 的两种显示模式;而 SwiftUI 将他们分成了两个单独的控件。
struct ContentView: View {
//需要定义某种可绑定条件,以确定Alert是否可见
@State var isError: Bool = false
var body: some View {
Button("Alert") {
self.isError.toggle()
}.alert(isPresented: $isError, content: {
Alert(title: Text("提示"),
message: Text("错误"),
dismissButton: .default(Text("OK")))
})
}
}
和 UIKit不同的是,SwiftUI中 Alert弹出时需要绑定一个 Bool类型的数据,仅当它为 true时才会弹窗。
对于一个弹窗,我们往往会设置多个按钮,实现如下。
struct ContentView: View {
@State var isError: Bool = false
var body: some View {
Button("Alert") {
self.isError.toggle()
}.alert(isPresented: $isError, content: {
Alert(title: Text("提示"),
message: Text("是否继续?"),
primaryButton: .default(Text("确定"), action: {
}),
secondaryButton: .cancel(Text("取消"))
)
})
}
}
primaryButton和 secondaryButton必须成对出现。
ActionSheet
ActionSheet 的用法与 Alert 类似,这里只给出使用示例,其他不再赘述。
struct ContentView: View {
@State var isSheet: Bool = false
//计算属性
var actionSheet: ActionSheet {
ActionSheet(title: Text("提示"),
message: Text("获取照片"),
buttons: [
.default(Text("拍照"), action: {
}),
.destructive(Text("相册"), action: {
}),
.cancel(Text("取消"))
]
)
}
var body: some View {
Button("Action Sheet") {
self.isSheet = true
}.actionSheet(isPresented: $isSheet, content: {
self.actionSheet
})
}
}
界面跳转
之前我们所实现的都是单个界面,在 SwiftUI 中若要实现多个界面跳转主要有两种方式—— Sheet 跳转和 NavigationLink 导航跳转。
Sheet 跳转
Sheet 跳转类似 UIKit 中在 controller 中使用 self.present。
struct ContentView: View {
@State var isModal: Bool = false
var modal: some View {
Text("新界面")
}
var body: some View {
Button("Modal") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
//执行跳转
self.modal
})
}
}
iOS 13 开始,Sheet 方式弹出的界面并不是全屏显示,这种显示可以通过下拉的方式让界面 dismiss。若我们希望手动让界面 dismiss,可以按如下方式处理。
import SwiftUI
struct ContentView: View {
@State var isModal: Bool = false
var body: some View {
Button("跳转") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
//跳转出来的界面
NewView()
})
}
}
struct NewView: View {
//通过presentationMode来dismiss
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("新界面")
Button("Dismiss") {
//手动dismiss
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
NavigationLink 导航跳转
NavigationLink 导航跳转类似于 UIKit 中 self.navigationController.pushViewController。
struct ContentView: View {
var body: some View {
NavigationView {
//借助NavigationLink通过按钮跳转
NavigationLink(destination: Text("下一个界面")) {
Text("跳转")
}.navigationBarTitle("导航栏")
}
}
}
我们也可以和 Sheet 跳转一样,绑定一个 Bool 变量来控制跳转。
import SwiftUI
struct ContentView: View {
//声明isActive
@State var isActive = false
var body: some View {
NavigationView {
//NavigationLink在按下按钮时设置为true,在适当的地方设置为false即可返回
NavigationLink(destination: NextView(isActive: $isActive), isActive: $isActive) {
Text("跳转")
}.navigationBarTitle("导航栏")
}
}
}
struct NextView: View {
@Binding var isActive: Bool
var body: some View {
Button("返回"){
//将isActive设为false
self.isActive = false
}
}
}
再举一个在 List 中点击 Row 跳转的例子。
import SwiftUI
struct User: Identifiable {
var id = UUID()
var name: String
}
struct ContentView: View {
//数据源
let users = [User(name:"ZhangSan"), User(name:"LiSi"), User(name:"WangWu")]
var body: some View {
//用数据源构造列表
NavigationView {
List(users) { user in
NavigationLink(destination: Text(user.name)) {
Text(user.name)
}
}.navigationBarTitle("Users")
}
}
}
NavigationLink 与 Button 共存
如果一个 List 和 Row 中既有 NavigationLink 又有 Button,且 Button 存在多个的情况下,如何分开处理他们的事件呢?
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<10) { row in
HStack {
//按钮1
Button(action: {
print("按钮1:\(row) Clicked")
}) {
Text("按钮1:\(row)")
}.buttonStyle(BorderlessButtonStyle()) // 必须设置,否则无法分别响应
.padding()
//按钮2
Button(action: {
print("按钮2:\(row) Clicked")
}) {
Text("按钮2:\(row)")
}.buttonStyle(BorderlessButtonStyle())
.padding()
// NavigationLink
NavigationLink(destination: Text("List Row\(row)")
) {
Text("NavigationLink\(row)")
}
}
}
}.navigationBarTitle("List")
}
}
}
跳转时执行额外操作
从前面的案例可以看出,通过 NavigationLink 可以很轻松地完成导航式界面跳转,但是如果跳转的同时还可以执行额外操作,目前只依靠 NavigationLink 是无法完成的,只能通过间接的方式实现。下面介绍两种常用的方式。
● NavigationLink 显示的内容为一个 EmptyView(空视图),用 Button 做真正的操作,适用于按钮点击式跳转。这里面还有个小细节,默认情况下,NavigationLink 是有个向右的小箭头,如果不显示箭头,可以里面包含一个 EmptyView。
struct ContentView: View {
@State private var presentMe = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Hello"), isActive: $presentMe) { EmptyView() }
Button(action: {
//执行一些额外操作
//最后再跳转
self.presentMe = true
}, label: {
Text("设置")
})
Spacer()
}.navigationBarTitle(Text("主界面"))
}
}
}
● 通过 simultaneousGesture 创建一个单击手势,适用于列表点击式跳转。
import SwiftUI
struct User: Identifiable {
var id = UUID()
var name: String
}
struct ContentView: View {
let users = [User(name:"ZhangSan"), User(name:"LiSi"), User(name:"WangWu")]
var body: some View {
NavigationView {
List(users) { user in
NavigationLink(destination: Text(user.name)) {
Text(user.name)
}.simultaneousGesture(TapGesture().onEnded{
print("执行额外操作")
})
}.navigationBarTitle("Users")
}
}
}
界面跳转中的一些其它操作
● 当 Image 被嵌入到 NavigationLink 中时,默认会被蒙上蓝色,为了避免遮挡,除了可以设置图片的渲染模式来解决,也可以通过在 NavigationLink 上设置 .buttonStyle(PlainButtonStyle()) 解决。
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
ForEach(1..<10) { index in
NavigationLink(destination: Text("Hello")) {
HStack {
Image("img")
.resizable()
.scaledToFit()
Text("\(index)")
}
}.buttonStyle(PlainButtonStyle())
}
}
}
}
}
● 用 NavigationLink 完成导航跳转以后,默认会是返回按钮显示为 Back。如果需要自定义导航返回按钮,需要在跳转后的 View 中做如下的处理:
① 通过 .navigationBarBackButtonHidden 隐藏原先的按钮。
② 通过 .navigationBarItems 添加一个 leading 按钮完成自定义,但是需要手动处理返回事件。
//设置跳转后的View
struct DetailView: View {
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
VStack {
Text("zhangsan")
}
.navigationBarBackButtonHidden(true) //隐藏原先返回的按钮
.navigationBarItems(leading: Button(action: {
self.mode.wrappedValue.dismiss()
}) {
Image(systemName: "arrow.left") //此时返回的按钮是一个箭头
})
}
}