iOS SwiftUI 数据表单Form从菜鸟到高手的四种实现方案

采集数据、处理数据、展示数据是一个程序的最基本功能。今天我们来学习一下如何使用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<Value>: 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:3365059189
SwiftUI技术交流QQ群:518696470

推荐文章

CoreData篇

TextField篇

JSON文件篇


一篇文章系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270