Sei sulla pagina 1di 19

Forms made easy with SwiftUI

Maarek Jul 1 ・5 min read

#swift #swiftui #form #ios

In this post, we will build a registration form


with SwiftUI.
Let's start by creating a new Xcode Project :
Choose a single view app, and dont forget to
check "Use SwiftUI".

Xcode creates a ContentView for you, we'll work


with this struct as our main view. First, we will
add our registration fields values. Let's say
name, email, and password. We need to use the
property wrapper @State for our values. If you
don't know what @State property wrapper is, I
highly recommande to read my post about
Properties Wrappers.
This is what we should have by now :

struct ContentView : View {

@State private var name: String = ""


@State private var email: String = ""
@State private var password: String = ""

var body: some View {


Text("Hello World!")
}
}

Now in our view, we want to make a


NavigationView that will contain a Form.

struct ContentView : View {

@State private var name: String = ""


@State private var email: String = ""
@State private var password: String = ""

var body: some View {


NavigationView {
Form {
Text("Hello World!")
}
.navigationBarTitle(Text("Registration Form")
}
}
}

By now, if you build and run you should see this


:
As you can see, the form object creates a List
and every View in it will be a cell, just like our
text here, Hello World! .
In this form, we will remove the Hello World
text and add instead some sections, three
sections actually. One that we will use for the
name and mail text fields, another for the
password and the last one for our call to action
validation button.
A Section takes 3 parameters : a header, a footer
and the content (View). Here, we will set
headers to our 2 first sections :

struct ContentView : View {

@State private var name: String = ""


@State private var email: String = ""
@State private var password: String = ""

var body: some View {


NavigationView {
Form {
Section(header: Text("Your Info")) {
TextField($name, placeholder: Text("N
TextField($email, placeholder: Text("
}
Section(header: Text("Password")) {
TextField($password, placeholder: Tex
}
Section {
Button(action: {
print("register account")
}) {
Text("OK")
}
}
}
.navigationBarTitle(Text("Registration Form")
}
}
}

Which would result into that :


That's it. We now have a simple form made with
SwiftUI. Note that we passe the fields values
with a $ so that we have a 2 ways binding : the
textfields read and write the value we pass to
them.

Now, let's try to add a few more things to this...


I would like to add an indicator for the security
level of the password.

First, we are going to create an enum for the


password security level :

enum PasswordLevel: Int {


case none = 0
case weak = 1
case ok = 2
case strong = 3
}

And a view :

struct SecureLevelView : View {


var level: PasswordLevel
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 8).foregroundC
RoundedRectangle(cornerRadius: 8).foregroundC
RoundedRectangle(cornerRadius: 8).foregroundC
}
}

func getColors() -> [Color] {


switch self.level {
case .none:
return [.clear, .clear, .clear]
case .weak:
return [.red, .clear, .clear]
case .ok:
return [.red, .orange, .clear]
case .strong:
return [.red, .orange, .green]
}
}
}

We can init our view with a level and depending


on the level it will display the right colors like so
:
From the top to the bottom : .none (no colors),
.weak, .ok, .strong.

Now instead of having a simple value for the


password, we will make a class that will handle
holding and checking the password.
We need this class to be bindable so it can notify
the view for the changes of the protection level.

class PasswordChecker: BindableObject {


public let didChange = PassthroughSubject<PasswordChe
var password: String = "" {
didSet {
self.checkForPassword(password: self.password
}
}

var level: PasswordLevel = .none {


didSet {
self.didChange.send(self)
}
}

func checkForPassword(password: String) {


if password.count == 0 {
self.level = .none
} else if password.count < 2 {
self.level = .weak
} else if password.count < 6 {
self.level = .ok
} else {
self.level = .strong
}
}
}

Don't forget to import Combine.

The class has 2 attributes : the password and the


level.
It also has a didChange property to conform to
BindableObject. I you don't know what a
BindableObject is, here is the post where I
explain what this is and how it works.
What we want here is to update the password
level on the didSet of the password, so that
when the password's value is updated, we set
the level according to the setted password.
When the level is setted, we want to notify the
view that the password level has changed so it
can update the SecureLevelView.

So here, in the checkForPassword method, I


choose to make simple rules : the protection
level depends on the number of characters in the
password, but you might need to use actual
rules, checking on capitalized letters, numbers
or special characters with a regexp.

Now we want to use that class in our view, we


just need to remove the password property and
replace it with a PasswordChecker .

struct ContentView : View {

@State private var name: String = ""


@State private var email: String = ""
@ObjectBinding var passwordChecker: PasswordChecker =

var body: some View {


NavigationView {
Form {
Section(header: Text("Your Info")) {
TextField($name, placeholder: Text("N
TextField($email, placeholder: Text("
}
Section(header: Text("Password")) {
TextField($passwordChecker.password,
if !self.passwordChecker.password.isE
SecureLevelView(level: self.passw
}
}
Section {
Button(action: {
print("register account")
}) {
Text("OK")
}
}
}
.navigationBarTitle(Text("Registration Form")
}
}
}

I chose to hide the SecureLevelView when the


password is empty.

Now this is what we have :

The last feature I'd like to have is a toggle to


make the user accepts the terms and conditions.
I added a new @State Boolean property :
@State private var terms: Bool = false

and in the section :

if self.passwordChecker.level.rawValu
Toggle(isOn: $terms) {
Text("Accept the terms and co
}
if self.terms {
Button(action: {
print("register account")
}) {
Text("OK")
}
}
}

Meaning : if the password protection level is


higher than ok ( ok or strong ), we show the
"Accept the terms and conditions" Toggle and
only if the toggle is on, then show the validation
button.

I agree, this is a terrible UX. But now you know


how to build easy forms and dynamically show
info as user types in, using combine.
Hope you enjoyed this little post. You can
download the full source here on my github.

Happy coding !

Maarek + FOLLOW
Swift developer Space geek Photography nerd
@kevinmaarek kevinmaarek kevinmaarek.fr

Add to the discussion

PREVIEW SUBMIT

Jul 2
Loris Maz

awesome thanks!

3 REPLY

code of conduct - report abuse

Classic DEV Post from Oct 30

The Divergence of Open Source


Maintainer From Software Engineer
Jacob Herrington (he/him)

55

Another Post You Might Like

9 best open-source findings, October


2019
Nikita Sobolev

183 5

Another Post You Might Like

Using Levenshtein Distances to Find


Similar Strings
Nicky Marino

How do you calculate a Levenshtein distance?


70 5

SwiftUI for Mac - Part 1


TrozWare - Dec 19
SwiftUI for Mac - Part 2
TrozWare - Dec 19

SwiftUI: An Introduction
marinbenc - Dec 23

Declarative Networking with Combine


Ritesh Gupta - Dec 17

Home About Privacy Policy Terms of Use Contact

Code of Conduct

DEV Community copyright 2016 - 2019

Potrebbero piacerti anche