ToggleStyle in SwiftUI

ToggleStyle allows us to customize the Toggle view. In order to build custom Toggle, we simply need to create a new type conforming to the ToggleStylemodifier which returns the custom view for toggle in makeBody(configuration:) function.

In this article, we will build a few custom toggles so let’s start with the most basic one to understand the protocol better.

We will start with the default Toggle in the view.

struct DevTechieToggleStyleExample: View {
    @State private var agree = false
    
    var body: some View {
        VStack {
            Text("DevTechie")
                .font(.largeTitle)
            Text("DevTechie.com helps me learn iOS Development by building real examples.")
                .padding()
                .background(agree ? Color.orange : Color.gray.opacity(0.5), in: RoundedRectangle(cornerRadius: 20))
                .animation(.easeInOut, value: agree)
            Toggle("Do you agree?", isOn: $agree)
        }
        .padding()
    }
}

Custom ToggleStyle 1

We will create a new struct conforming ToggleStyle protocol. As mentioned earlier, this protocol only requires one function called makeBody(configuration:) to be implemented.

Configuration passed inside the makeBody(configuration:) is ToggleStyleConfiguration type and has

Label : this is a view that describes the effect of switching the toggle between states.

$isOn : is a binding to let us listen to the changes in the toggle’s isOn property.

isOn : is the property which indicates whether the toggle is on or off.

With all this information, we will put together our custom toggleStyle.

struct DevTechieCustomToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            Capsule()
                .fill(Color.gray.opacity(0.4))
                .frame(width: 50, height: 30)
                .overlay(
                    Circle()
                        .foregroundColor(configuration.isOn ? Color.green : Color.red)
                        .padding(.all, 3)
                        .offset(x: configuration.isOn ? 10 : -10, y: 0)
                )
                .onTapGesture {
                    withAnimation(Animation.linear(duration: 0.1)) {
                        configuration.isOn.toggle()
                    }
                }
        }
    }
}

Using this style is easy, just call it inside the toggleStyle modifier.

struct DevTechieToggleStyleExample: View {
    @State private var agree = false
    
    var body: some View {
        VStack {
            Text("DevTechie")
                .font(.largeTitle)
            Text("DevTechie.com helps me learn iOS Development by building real examples.")
                .padding()
                .background(agree ? Color.orange : Color.gray.opacity(0.5), in: RoundedRectangle(cornerRadius: 20))
                .animation(.easeInOut, value: agree)
            Toggle("Do you agree?", isOn: $agree)
                .toggleStyle(DevTechieCustomToggleStyle())
        }
        .padding()
    }
}

Flashlight ToggleStyle

Let’s create another example. This time, we will use SF Symbol images and bit of SwiftUI view alignment technique to draw user’s attention onto our message.

struct DevTechieFlashLightStyle: ToggleStyle {
    
    static let backgroundColor = Color(.label)
    static let switchColor = Color(.systemBackground)
    
    func makeBody(configuration: Configuration) -> some View {
        VStack {
            ZStack {
                Image(systemName: "arrowtriangle.down.fill")
                    .frame(width: 30, height: 30, alignment: .top)
                    .font(.system(size: 50))
                    .offset(y: -30)
                    .foregroundColor(.yellow.opacity(0.5))
                    .opacity(configuration.isOn ? 1 : 0)
                Image(systemName: configuration.isOn ? "flashlight.on.fill": "flashlight.off.fill")
                    .font(.system(size: 50))
                    .opacity(configuration.isOn ? 1 : 0.7)
            }
            .onTapGesture(perform: {
                withAnimation(Animation.easeInOut) {
                    configuration.isOn.toggle()
                }
            })
        }
    }
    
}

Replace old style with new one.

Toggle("Do you agree?", isOn: $agree).toggleStyle(DevTechieFlashLightStyle())

Build and run

Day Night ToggleStyle

Let’s build one more toggle style with SF Symbols.

public struct DayNightToggleStyle: ToggleStyle {
    public func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            ZStack {
                ZStack {
                    Image(systemName: "sun.max.fill")
                        .foregroundColor(.yellow)
                        .opacity(configuration.isOn ? 1.0 : 0.0)
                    Image(systemName: "moon.fill")
                        .opacity(configuration.isOn ? 0.0 : 1.0)
                }
                .foregroundColor(Color.white)
                .offset(x: configuration.isOn ? 10 : -10)
                .frame(width: 20.0, height: 20.0)
                .shadow(color: Color.black.opacity(0.1), radius: 3, x: configuration.isOn ? -2 : 2, y: 1)
            }
            .frame(width: 50.0, height: 30.0)
            .background(background(configuration.isOn))
            .clipShape(Capsule())
            .offset(x: 2)
        }
        .onTapGesture {
            withAnimation(Animation.spring(response: 0.3, dampingFraction: 0.7, blendDuration: 0)) {
                configuration.isOn.toggle()
            }
        }
    }
    
    private func background(_ isOn: Bool) -> some View {
        LinearGradient(
          gradient: isOn ? lightGradient : darkGradient,
          startPoint: .top,
          endPoint: .bottom
        )
          .background(isOn ? Color.white : Color.black)
      }private var lightGradient: Gradient {
        Gradient(colors: [Color.blue.opacity(0.6), Color.blue.opacity(0.4)])
      }private var darkGradient: Gradient {
        Gradient(colors: [Color.blue.opacity(0.3), Color.blue.opacity(0.5)])
      }
}

Change the style

.toggleStyle(DayNightToggleStyle())