In this blog post, I’ll show you how to easily add a SwiftUI form to your iOS and macOS apps. Forms play a crucial role in gathering user input. Luckily, SwiftUI makes it a breeze to create and customize forms with its intuitive syntax and powerful features.
Throughout this tutorial, I’ll guide you through the process of creating two practical examples: a user registration form for both iOS and macOS, and a settings form for macOS and iOS. By the end, you’ll have a solid understanding of how to implement forms in your apps using SwiftUI.
Basics of SwiftUI Form
To create a basic form in SwiftUI, you can use the Form view. Here’s an example of how you can create a simple form:
struct ContentView: View {
@State private var name = ""
@State private var email = ""
var body: some View {
Form {
Section {
TextField("Name", text: $name)
TextField("Email", text: $email)
}
}
}
}
To add form fields, I use the Section view. In this example, I have a single section containing two TextField views for the name and email inputs. The text parameter of each TextField is bound to a corresponding @State property, which allows us to read and update the values entered by the user.
By wrapping our form fields in a Section, we can group related inputs together. This helps to organize the form and improve its visual structure.
Form vs List
SwiftUI has two very similar container views: List and Form. If you only work on an iOS app, you probably will not see any difference. However, Form view will look different on macOS e.g. for a settings window or in an inspector area.
In the next sections, you will explore how to create more advanced forms for specific use cases, such as user registration and settings configuration, on both iOS and macOS platforms.
Creating a User Registration Form for macOS
To create a user registration form for macOS using SwiftUI, you can follow these steps:
Start by creating a new SwiftUI view called RegistrationView. You can use the following code as a starting point:
struct RegistrationView: View {
@State private var username = ""
@State private var password = ""
@State private var confirmPassword = ""
var body: some View {
Form {
Text("Create a New Account")
.font(.title2)
TextField("Username", text: $username)
SecureField("Password", text: $password)
SecureField("Confirm Password", text: $confirmPassword)
HStack {
Button("Cancel", role: .cancel) {
}
Button("Register") {
// Perform registration logic
}
.buttonStyle(.borderedProminent)
}
.padding(.top)
}
.frame(maxWidth: 400)
}
}
In the RegistrationView struct, we define three @State
variables: username, password, and confirmPassword. These variables will hold the values entered by the user in the respective form fields.
Inside the body property, we create a Form view to encapsulate the user registration form. The form provides a structured layout for our form elements.
I added two buttons: “Cancel” and “Register”. You can handle the action for each button by adding appropriate code inside the closure.
The “Register” button has a buttonStyle modifier applied to it, setting it to .borderedProminent
to give it a distinct appearance.
Form {
...
}.formStyle(.grouped)
Per default Form uses GroupedFormStyle
but you can also try ColumnFormStyle
:
Creating a User Registration Form for iOS
I don’t think the form looks good for iOS if you want to show a user registration form. You have much more design freedom using a simple VStack:
struct RegistrationIosView: View {
@State private var email = ""
@State private var password = ""
@State private var confirmPassword = ""
var registerISDisabled: Bool {
email.isEmpty || password.isEmpty || confirmPassword.isEmpty
}
var body: some View {
NavigationStack {
ScrollView {
VStack(alignment: .leading) {
Text("Enter Your Email")
TextField("Email", text: $email)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.padding(.bottom, 30)
.keyboardType(.emailAddress)
Text("Enter Your Password")
SecureField("Password", text: $password)
SecureField("Confirm Password", text: $confirmPassword)
.padding(.bottom, 30)
HStack {
Button {
} label: {
Text("Cancel")
.frame(maxWidth: .infinity)
} .buttonStyle(.bordered)
Button {
} label: {
Text("Register")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.disabled(registerISDisabled)
}
.padding(.top)
}
}
.textFieldStyle(.roundedBorder)
.padding()
.background(Color.mint.gradient.opacity(0.7))
.navigationTitle("Create New Account")
.accentColor(.indigo)
}
}
}
I added a mint gradient background to make it look more fancy. I also added a rounded border text style to highlight the text field and secure text field for the email and password entries.
Settings Window for macOS
A good use case for form is for a Settings Window on macOS. You would add it to the main app file like:
struct MyProjectApp: App {
var body: some Scene {
WindowGroup {
ContentView
}
#if os(macOS)
Settings {
SettingsView()
}
#endif
}
}
Using a Form inside the SettingsView, will give you the typical macOS styling:
In the above window, you can see a 2 column like layout. I uses LabledContent view for the sections:
struct SettingsView: View {
var body: some View {
Form {
LabeledContent("macOS Version", value: "2.2.1")
.font(.headline)
...
}
.padding()
}
}
In order to add a text to the left side of the “Show Line Numbers” toggle, I also used LabeledContent:
struct SettingsView: View {
var body: some View {
Form {
...
LabeledContent("Display") {
Toggle(isOn: $showLineNumbers, label: {
Text("Show Line Numbers")
})
}
Toggle(isOn: $showPreview, label: {
Text("Show Preview")
})
}
.padding()
}
}
You would typically use different kinds of control views like toggles, textfields and pickers. The following is an example of a picker with a RadioGroupPickerStyle:
@AppStorage("selectedAppearance") var selectedAppearance = 0
...
Picker("Color Scheme", selection: $selectedAppearance) {
Text("Default System")
.tag(0)
Text("Light")
.tag(1)
Text("Dark")
.tag(2)
}
.pickerStyle(.radioGroup)
How to Add a Settings Form for iOS
If I add a similar settings view for iOS, I will use Sections with titles to structure the form:
struct SettingsIOSView: View {
@AppStorage("selectedAppearance") var selectedAppearance = 0
@State private var fontSize: CGFloat = 15
@State private var showLineNumbers = false
@State private var showPreview = true
var body: some View {
NavigationStack {
Form {
LabeledContent("iOS Version", value: "2.2.1")
Picker("Color Scheme", selection: $selectedAppearance) {
Text("Default System")
.tag(0)
Text("Light")
.tag(1)
Text("Dark")
.tag(2)
}
.pickerStyle(.inline)
Section("Notes Font Size \(Int(fontSize))") {
Slider(value: $fontSize, in: 10...40, step: 1) {
Text("Point Size \(Int(fontSize))")
}
}
Section("Display") {
Toggle(isOn: $showLineNumbers, label: {
Text("Show Line Numbers")
})
Toggle(isOn: $showPreview, label: {
Text("Show Preview")
})
}
}
.navigationTitle("Settings")
}
}
}
In this case I used a picker style of InlinePickerStyle. These kind of control views change their appearance depending on the environment they are used with. If you would use a NavigationLinkPickerStyle, the picker would only show its title in the main view and look like a link. If you press it, it would push a new view where you can select from the picker options. What kind of styling you want to use depends on the situation and how few or many individual controls you want to show.
SwiftUI Inspector for a Sidebar View on macOS
New with iOS 17 and macOS 14 is the SwiftUI inspector. Form will give you a typical detail sidebar look on macOS:
In the above example, I have a simple notes app with a NoteDetailView on the left. If you toggle the toolbar button the inspector area opens with additional settings. The inspector modifier takes a binding to a boolean property that determines if the inspector is open or closed. In the following, I am using 2 different icons for the inspector toggle button on iOS and macOS:
struct NoteDetailView: View {
@State private var note = Note(title: "Meeting Notes", content: "- Discuss project timeline\n- Assign tasks to team members\n- Set next meeting date")
@State private var fontSize: CGFloat = 15
@State private var isPresentedInspector = false
var sidebarIcon: String {
#if os(iOS)
"slider.horizontal.3"
#else
"sidebar.trailing"
#endif
}
var body: some View {
NavigationStack {
VStack(alignment: .leading) {
Text(note.title)
.font(.headline)
TextEditor(text: $note.content)
.font(.system(size: fontSize))
}
.padding()
.toolbar(content: {
Button(action: {
isPresentedInspector.toggle()
}, label: {
Label("Show Note Settings", systemImage: sidebarIcon)
})
})
.navigationTitle("My Note")
.inspector(isPresented: $isPresentedInspector) {
NoteSettingsView(fontSize: $fontSize)
}
}
}
}
In the above code example, I am passing data (the point size property) between the inspector and notes area. I am showing 3 control views for the note text font size, show line numbers and show preview properties in the NoteSettingsView:
struct FormExampleView: View {
@Binding var fontSize: CGFloat
@State private var showLineNumbers = false
@State private var showPreview = true
var body: some View {
Form {
Section("Notes Font Size \(Int(fontSize))") {
Slider(value: $fontSize, in: 10...40, step: 1) {
Text("Point Size \(Int(fontSize))")
}
}
Section("Display") {
Toggle(isOn: $showLineNumbers, label: {
Text("Show Line Numbers")
})
Toggle(isOn: $showPreview, label: {
Text("Show Preview")
})
}
}
}
}
The Inspector presenter adapts automatically to the platform it is used in. You saw above, that on macOS it is shown as a trailing sidebar. On iOS, the inspector content will be shown as a sheet:
Conclusion
I hope this tutorial has provided you with a clear understanding of how to add a SwiftUI form to your iOS and macOS apps. By following the steps outlined in this blog post, you can easily create user registration forms and settings forms for both platforms.
With SwiftUI, you have the power to design and implement forms with ease, thanks to its declarative syntax and built-in form elements. Whether you’re developing for iOS or macOS, SwiftUI offers a consistent and efficient way to create intuitive user interfaces.
Great post! I’m glad to see your blogs are closer to real world apps than most blogs. Just showcasing one feature is easy, but getting everything to work together is what is the most difficult I find.
So I’m learning a lot by reading your posts. Many thanks!
Excellent! Comparing macOS and iOS is very useful and a lot of blog posts only look at either. Thank you!