How to Add a SwiftUI Form to Your iOS and macOS apps

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:

example screen for a create new account registration from on macOs using SwiftUI Form

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.

example screen for a create new account registration from on iOS using VStack

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:

example for a settings window on macOS using a SwiftUI Form

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")
        }
    }
}
example screen for a settings view on iOS for a note taking app

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 roadmap

Feeling Lost in SwiftUI?

This SwiftUI roadmap shows you what to learn next.

  • Key concepts at a glance
  • Spot your knowledge gaps
  • Guide your learning path

Ideal for beginners and self-learners.

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:

form used inside an inspector presentation 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:

form used inside an inspector presentation on iOS

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.

2 thoughts on “How to Add a SwiftUI Form to Your iOS and macOS apps”

  1. 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!

    Reply

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form