采集数据、处理数据、展示数据是一个程序的最基本功能。今天我们来学习一下如何使用SwiftUI的Form来采集数据。
本期我们以最基础的用户注册界面为例子,向大家介绍一下数据收集的四种不同水平的实现方式。
初学者方式
相信长期看我专栏的朋友,应该可以快速搭建起下面的界面。非常简单就是VStack、HStack、TextField等组件的简单组合而已。
import SwiftUI
struct SignUpForm0: View {
@State var username = ""
@State var email = ""
var body: some View {
NavigationView{
Form {
Text("初学者做的用户注册界面").font(.headline)
HStack {
Image(systemName: "person.circle.fill")
TextField("Username", text: $username)
}
HStack {
Image(systemName: "envelope.circle.fill")
TextField("Email", text: $email)
}
Button(
action: { print("here") },
label: { Text("提交") }
)
}.navigationBarTitle(Text("用户注册界面"))
}
}
}
入门级方式
入门级的界面和初学者的完全一样,但是在构建界面时却开始使用模块化思想了。
初学者阶段,我们使用了两次相同的HStack + Image + TextField组合,但这并不一定是问题,因为我们对两个文本字段的配置都大不相同。但假设我们想在其他地方也复用这些代码,该如何操作呢。其实非常简单,我们将这些代码封装成一个独立的组件即可。
为了实现这个想法,我们先创建一个struct视图,然后将Image和Text都封装进入,最后通过@Bingding来进行数值的传递。
struct IconPrefixedTextField: View {
var iconName: String
var title: String
@Binding var text: String
var body: some View {
HStack {
Image(systemName: iconName)
TextField(title, text: $text)
}
}
}
在主界面中,使用新创建的组件进行数据收集。怎么样代码变得更简洁了吧。
import SwiftUI
struct SignUpForm1: View {
@State var username = ""
@State var email = ""
var body: some View {
NavigationView{
Form {
Text("入门后做的用户注册界面").font(.headline)
IconPrefixedTextField(
iconName: "person.circle.fill",
title: "Username",
text: $username
)
IconPrefixedTextField(
iconName: "envelope.circle.fill",
title: "Email",
text: $email
)
Button(
action: { print("here") },
label: { Text("提交") }
)
}.navigationBarTitle(Text("用户注册界面"))
}
}
}
中级水平
刚才,我们看过初学者快速的构建界面,入门级的采用模块化编程思想,那么中级选手会如何实现呢。
中级选手会告诉我们上面两种实现方式都不是SwiftUI都佳实现模式。尽管上述更改将使我们能够在SignUpForm之外重用我们新的IconPrefixedTextField类型,但是否最终改善了我们的原始代码却值得怀疑。毕竟,我们并没有真正简化注册表单的实现过程。实际上,调用方式上看起来比以前还复杂了很多。
我们还是从SwiftUI自己的API设计中汲取一些灵感吧,看看如果将文本视图配置代码改为View扩展来实现的话,会是什么样子。
extension View {
func prefixedWithIcon(named name: String) -> some View {
HStack {
Image(systemName: name)
self
}
}
}
上面代码非常简单,我们就是对View视图进行一下拓展,丰富一下View组合。extension做好后,我们就可以用SwiftUI的链式调用方法来进行界面配置了。
TextField("Username", text: $username)
.prefixedWithIcon(named: "person.circle.fill")
完成上述操作后,我们现在可以将任何SF Symbols图标直接添加到SwiftUI中的TextField视图或其他任何视图中.
下面我们来看看项目的完整代码
import SwiftUI
struct SignUpForm2: View {
@State var username = ""
@State var email = ""
var body: some View {
NavigationView{
Form {
Text("中级水平做的用户注册界面").font(.headline)
TextField("Username", text: $username)
.prefixedWithIcon(named: "person.circle.fill")
TextField("Email", text: $email)
.prefixedWithIcon(named: "envelope.circle.fill")
Button(
action: { print("here") },
label: { Text("提交") }
)
}.navigationBarTitle(Text("用户注册界面"))
}
}
}
中高级水平
中级水平已经对界面进行SwiftUI式的改造,中高级选手就开始将注意力集中在业务逻辑方面了。例如,对输入内容验证方面。我们先看看下面的动图。
假如我们想要求用户的名称必须大于5位,邮件必须是邮件格式,并且只有满足上面两个条件后才会出现提交按钮,这类稍稍复杂的效果该如何实现呢?
实现方式有很多种,但是最简洁、最灵活、最高效的实现方式是什么呢?
答案当然是SwiftUI式的编码方式。下面我就带领大家来实现一下吧!
第一步 编写个验证邮件的函数
我个人比较喜欢用NSPredicate来实现
func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: email)
}
第二步 创建 ViewModifier 当用户满足条件就让边框变为绿色
struct Validation: ViewModifier {
var value: Value
var validator: (Value) -> Bool
func body(content: Content) -> some View {
// Here we use Group to perform type erasure, to give our
// method a single return type, as applying the 'border'
// modifier causes a different type to be returned:
Group {
if validator(value) {
content.border(Color.green)
} else {
content
}
}
}
}
主页面
struct SignUpForm3: View {
@State var username = ""
@State var email = ""
@State var uFlag = false
@State var eFlag = false
var body: some View {
NavigationView{
Form {
Text("中高级做的用户注册界面").font(.headline)
TextField("Username", text: $username)
.modifier(Validation(value: username) { name in
self.uFlag = name.count > 4
return self.uFlag
})
.prefixedWithIcon(named: "person.circle.fill")
TextField("Email", text: $email)
.modifier(Validation(value: email) { name in
self.eFlag = isValidEmail(name)
return self.eFlag
})
.prefixedWithIcon(named: "envelope.circle.fill")
if (self.uFlag && self.eFlag){
Button(
action: {print("here")},
label: { Text("提交") }
)
}
}.navigationBarTitle(Text("用户注册界面"))
}
}
}
总结
本文带着大家领略了从菜鸟到高手不同层次选手完成相同任务时的不同思考模式:
- 初学者模式
简单使用HStack + Image + TextField组合,快速实现需求。得益于SwiftUI强大功能,界面可以达到苹果的设计水准
- 入门级水平
对代码整洁度和代码复用提出了要求,开始效仿SwiftUI对代码进行模块化改造。
- 中级水平
开始不满足于简单模块化改造,开始从SwiftUI的API设计中汲取灵感,尝试采用链式编码。
- 中高级水平
中高级选手开始将注意力集中在业务逻辑方面了。
- 顶级高手呢
很遗憾,俺的水平有限只能介绍到上面的水平了。
如果需要项目完整源码,可以加我QQ。
QQ:3365059189
SwiftUI技术交流QQ群:518696470
- 请关注我的专栏icloudend, SwiftUI教程与源码