New in SwiftUI 4 : Toggle Collection & Mixed Stage Toggle

DevTechie Inc
Jun 28

Photo by Arthur Mazi on Unsplash

Starting SwiftUI 4, Toggle view is now capable of binding to multiple values.

Now we can create a toggle for collection of values with following overloaded initializer:

init(isOn:label:)
Where:

  • isOn: is a collection of bindings that determines whether the toggle is on, off or mixed.
  • Label: is a view that describes the purpose of toggle.
There are two additional overloaded inits:

init(_:isOn:)
Where first parameter is titleKey ( the key for the toggle’s localized title to describe the purpose of the toggle) and second parameter is isOn (a collection of bindings that determines whether the toggle is on, off or mixed)

init(_:isOn:)
Above initializer is similar to the previous one except that instead of titleKey init expects a title string.

Multiple bindings
As mentioned, toggle supports multiple value binding now so let’s take a look at an example. 

We will start with a data structure to represent HomeSecurity use-case.

struct HomeSecurity: Hashable, Identifiable {
    var id = UUID()
    var armed: Bool
    var name: String
}
Let’s add an extension to this struct and add some example data to work with:

extension HomeSecurity {
    static var exampleData: [HomeSecurity] = [
        .init(armed: false, name: "Garage Door"),
        .init(armed: true, name: "Front Door"),
        .init(armed: true, name: "Back Door"),
        .init(armed: false, name: "Camera 1"),
        .init(armed: false, name: "Camera 2"),
        .init(armed: true, name: "Motion Sensor"),
    ]
}
Now, we will work on our view, so let’s add a State property for our example data.

@State var homeSecurity = HomeSecurity.exampleData
Next, we will iterate over the example data and put them on to our view with following code:

ForEach(homeSecurity) { sec in
    HStack {
        Text(sec.name)
        Spacer()
        Image(systemName: sec.armed ? "checkmark.circle" : "circle")
            .foregroundColor(sec.armed ? .green : .red)
    }
}
Let’s add the Toggle view, which will take all security alarm armed states:

Toggle(isOn: $homeSecurity.lazy.map(\.armed)) {
    Text("Home Security")
        .bold()
}
Our entire view should look like this:

struct ContentView: View {
    
    @State var homeSecurity = HomeSecurity.exampleData
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            Divider()
            ForEach(homeSecurity) { sec in
                HStack {
                    Text(sec.name)
                    Spacer()
                    Image(systemName: sec.armed ? "checkmark.circle" : "circle")
                        .foregroundColor(sec.armed ? .green : .red)
                }
            }
            Toggle(isOn: $homeSecurity.lazy.map(\.armed)) {
                Text("Home Security")
                    .bold()
            }
        }
        .padding()
    }
}
Output:

Mixed Stage Toggle in DisclosureGroup
Mixed stage toggle works even better inside a DisclosureGroup so we will convert our example and add DisclosureGroup.

Let’s start by replacing HStack with a Toggle view for each HomeSecurity item.

struct ContentView: View {
    
    @State var homeSecurity = HomeSecurity.exampleData
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            Divider()
            ForEach($homeSecurity) { sec in
                Toggle(isOn: sec.armed) {
                    Text(sec.name.wrappedValue)
                }
            }
            Toggle(isOn: $homeSecurity.lazy.map(\.armed)) {
                Text("Home Security")
                    .bold()
            }
        }
        .padding()
    }
}
Next, we will introduce DisclosureGroup and move Toggle that takes collection of binding values into the label part of DisclosureGroup:

struct ContentView: View {
    
    @State var homeSecurity = HomeSecurity.exampleData
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            Divider()
            DisclosureGroup {
                ForEach($homeSecurity) { sec in
                    Toggle(isOn: sec.armed) {
                        Text(sec.name.wrappedValue)
                    }
                    .padding()
                }
            } label: {
                Toggle(isOn: $homeSecurity.lazy.map(\.armed)) {
                    Text("Home Security")
                        .bold()
                }
            }
        }
        .padding()
    }
}
With that change, we have a collapsable toggle group.



With that we have reached the end of this article. Thank you once again for reading. Subscribe our newsletter at https://www.devtechie.com