76

I've implemented a toggle after following Apple's tutorial on user input. Currently, it looks like this:

This is the code that produces this UI:

NavigationView {
    List {
        Toggle(isOn: $showFavoritesOnly) {
            Text("Show Favorites only")
        }
    }
}

Now, I'd like the Toggle's on-color to be blue instead of green.
I tried:

Toggle(isOn: $showFavoritesOnly) {
    Text("Show Favorites only")
}
.accentColor(.blue)
.foregroundColor(.blue)
.background(Color.blue)

None of these worked and I wasn't able to find any other modifiers, such as tintColor.

How do I change the color of a Toggle?

LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
  • Based on the documentation, it seems like `.accentColor` should change the `Toggle`'s color. Maybe file a bug report with Apple and see what they say? – RPatel99 Jun 06 '19 at 17:24
  • Will do. Where did you read that? @RPatel99 – LinusGeffarth Jun 06 '19 at 19:02
  • 1
    https://developer.apple.com/documentation/swiftui/toggle. It seems like the closest option to UIKit’s tintColor in SwiftUI, which is why I said that it seems like it should change Toggle’s color. If you file a bug report about it then hopefully whoever responds will either confirm that it is a bug or that there is another SwiftUI way to do it. – RPatel99 Jun 06 '19 at 19:58
  • 1
    I think this is a bug. It should change with `.accentColor`. I filed **FB6158727** and I'll let you know if Apple says anything about it. :) – Clifton Labrum Jun 16 '19 at 00:34
  • 1
    @CliftonLabrum did you get message from Apple ? – Karen Karapetyan Dec 11 '19 at 15:40
  • @KarenKarapetyan Apple tells me this was fixed in Xcode 11 beta 4, but I haven't confirmed that yet. – Clifton Labrum Dec 11 '19 at 20:59
  • 1
    I am using 11.3 and I have not been able to get ```.accentColor``` to work. – Spencer Connaughton Dec 29 '19 at 02:19

12 Answers12

168

SwiftUI 3.0

Using tint

A new modifier was introduced that can also change the Toggle color:

Toggle(isOn: $isToggleOn) {
    Text("Red")
    Image(systemName: "paintpalette")
}
.tint(.red)

Toggle(isOn: $isToggleOn) {
    Text("Orange")
    Image(systemName: "paintpalette")
}
.tint(.orange)

Toggle Tint Color

SwiftUI 2.0

Using SwitchToggleStyle

You can now set a tint color for the on position only in SwiftUI 2.0:

Toggle(isOn: $isToggleOn) {
    Text("Red")
    Image(systemName: "paintpalette")
}
.toggleStyle(SwitchToggleStyle(tint: Color.red))

Toggle(isOn: $isToggleOn) {
    Text("Orange")
    Image(systemName: "paintpalette")
}
.toggleStyle(SwitchToggleStyle(tint: Color.orange))

Toggle Tint Color

SwiftUI 1.0

Using ToggleStyle

I created a new ToggleStyle to change the three colors of the Toggle (on color, off color, and the thumb).

struct ColoredToggleStyle: ToggleStyle {
    var label = ""
    var onColor = Color(UIColor.green)
    var offColor = Color(UIColor.systemGray5)
    var thumbColor = Color.white
    
    func makeBody(configuration: Self.Configuration) -> some View {
        HStack {
            Text(label)
            Spacer()
            Button(action: { configuration.isOn.toggle() } )
            {
                RoundedRectangle(cornerRadius: 16, style: .circular)
                    .fill(configuration.isOn ? onColor : offColor)
                    .frame(width: 50, height: 29)
                    .overlay(
                        Circle()
                            .fill(thumbColor)
                            .shadow(radius: 1, x: 0, y: 1)
                            .padding(1.5)
                            .offset(x: configuration.isOn ? 10 : -10))
                    .animation(Animation.easeInOut(duration: 0.1))
            }
        }
        .font(.title)
        .padding(.horizontal)
    }
}

Examples of Use

Toggle("", isOn: $toggleState)
    .toggleStyle(
        ColoredToggleStyle(label: "My Colored Toggle",
                           onColor: .green,
                           offColor: .red,
                           thumbColor: Color(UIColor.systemTeal)))

Toggle("", isOn: $toggleState2)
    .toggleStyle(
        ColoredToggleStyle(label: "My Colored Toggle",
                           onColor: .purple))

From the SwiftUI Book

Toggle Example

Mark Moeykens
  • 15,915
  • 6
  • 63
  • 62
18

Just use UIAppearance APIs:

UISwitch.appearance().onTintColor = UIColor.blue

It'll of course by default change the appearance of all the instances of UISwitch, as per UIAppearance documentation.

NOTE: Tested as of Xcode 11 beta 5.

Karol Kulesza
  • 909
  • 8
  • 10
13

SwiftUI 2.0 (Post WWDC-2020)

Using the new SwiftUI enhancements you can use the .toggleStyle modifier.

// Switch tinting

Toggle(isOn: $order.notifyWhenReady) {
    Text("Send notification when ready")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))

Note this only works for iOS14/iPadOS14/macOS11 and above.

Ever Uribe
  • 639
  • 6
  • 13
9

I haven't found a way to directly change a Toggle color yet but an alternative way to have a blue switch or any other custom views, is to create a custom view of your own. To make a custom blue toggle in its simplest form:

struct BlueToggle : UIViewRepresentable {
  func makeUIView(context: Context) -> UISwitch {
    UISwitch()
  }

  func updateUIView(_ uiView: UISwitch, context: Context) {
    uiView.onTintColor = UIColor.blue
  }
}

struct ContentView : View {
    var body: some View {
      BlueToggle()
    }
}

Result:

enter image description here

M Reza
  • 18,350
  • 14
  • 66
  • 71
  • Thanks for this. I'll wait with accepting the answer to see if there's another answer coming up. – LinusGeffarth Jun 06 '19 at 16:22
  • How would I use this custom toggle with the same init `Toggle` has: `init(isOn: Binding, label: () -> Label)`? – LinusGeffarth Jun 06 '19 at 16:34
  • @LinusGeffarth To achieve that I think it's needed to also use `UIViewControllerRepresentable` but not sure. Check this example: https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit it's not exactly what you asked but it's a more complicated way of dealing with custom views! – M Reza Jun 06 '19 at 17:07
7

You can modify the global onTintColor for all UISwitch objects inside init().

@State var enable_dhcp = true

init()
{
    UISwitch.appearance().onTintColor = .red
}

var body: some View
{
    Toggle("DHCP", isOn: $enable_dhcp)
}

Toggle demo UIColor(red: 226.3/255.0, green: 37.6/255.0, blue: 40.7/255.0, alpha: 1.0)

George Valkov
  • 1,217
  • 12
  • 10
6

Building off @mohammad-reza-farahani 's solution, here is a fully uncompromising approach to getting the configurability of UISwitch with the implementation protocols if SwiftUI.

First wrap a UISwitch in a UIViewRepresentable and set the colors as you wish:

final class CustomToggleWrapper: UIViewRepresentable {
    var isOn: Binding<Bool>

    init(isOn: Binding<Bool>) {
        self.isOn = isOn
    }

    func makeUIView(context: Context) -> UISwitch {
        UISwitch()
    }

    func updateUIView(_ uiView: UISwitch, context: Context) {
        // On color
        uiView.onTintColor = UIColor.blue
        // Off color
        uiView.tintColor = UIColor.red
        uiView.layer.cornerRadius = uiView.frame.height / 2
        uiView.backgroundColor = UIColor.red
        uiView.isOn = isOn.wrappedValue

        // Update bound boolean
        uiView.addTarget(self, action: #selector(switchIsChanged(_:)), for: .valueChanged)
    }

    @objc
    func switchIsChanged(_ sender: UISwitch) {
        isOn.wrappedValue = sender.isOn
    }
}

Second, create a custom toggle style using the wrapped UISwitch:

struct CustomToggleStyle: ToggleStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        let toggle = CustomToggleWrapper(isOn: configuration.$isOn)

        return HStack {
            configuration.label
            Spacer()
            toggle
        }
    }
}

Implement a Toggle as you normally would, and apply your CustomToggleStyle:

struct TestView: View {
    @State private var isOn: Bool = true

    var body: some View {
        Toggle(
            isOn: $isOn
        ) {
            Text("Test: \(String(isOn))")
        }.toggleStyle(CustomToggleStyle()).padding()
    }
}
  • This works well. You can animate the changes with `uiView.setOn(isOn.wrappedValue, animated: context.transaction.animation != nil && !context.transaction.disablesAnimations)` inside the `updateUIView` function. – Matthew Sep 23 '21 at 08:02
  • FYI, this approach was great at first but recently failed with the latest XCode 14 (mine was 14.2), XCode 14 demand that `UIViewRepresentable` is to used on a `struct` instead of a `class`, the crash will happen at runtime – L N Jan 25 '23 at 01:35
4

Karol Kulesza and George Valkov have provided a very easy to implement solution. I just wanted to add that you can place the code below inside the app delegate's didFinishLaunching method as well.

UISwitch.appearance().onTintColor = .blue

You can also create more specific appearance configurations with

appearance(whenContainedInInstancesOf:)

See https://www.hackingwithswift.com/example-code/uikit/what-is-the-uiappearance-proxy

Peyman Mohamadpour
  • 17,954
  • 24
  • 89
  • 100
3

You can change the toggle color in IOS 15.0 using a tint modifier.

Toggle(isOn: $isToggleOn) {
       Text("Toggle")
    }.tint(.red)

and below IOS 15.0, You can use toggleStyle modifier to change the toggle color but it will be depreciated in the future.

 Toggle(isOn: $isToggleOn) {
    Text("Toggle")
 }.toggleStyle(SwitchToggleStyle(tint: .red))
Muhammad Farooq
  • 263
  • 3
  • 10
2

As the original question was just about changing the toggle on colour and not full Toggle visual customisation, I think something like this would do:

import SwiftUI

struct CustomToggle: UIViewRepresentable {
  @Binding var isOn: Bool

  func makeCoordinator() -> CustomToggle.Coordinator {
    Coordinator(isOn: $isOn)
  }

  func makeUIView(context: Context) -> UISwitch {
    let view = UISwitch()
    view.onTintColor = UIColor.red
    view.addTarget(context.coordinator, action: #selector(Coordinator.switchIsChanged(_:)), for: .valueChanged)

    return view
  }

  func updateUIView(_ uiView: UISwitch, context: Context) {
    uiView.isOn = isOn
  }

  class Coordinator: NSObject {
    @Binding private var isOn: Bool

    init(isOn: Binding<Bool>) {
      _isOn = isOn
    }

    @objc func switchIsChanged(_ sender: UISwitch) {
      _isOn.wrappedValue = sender.isOn
    }
  }
}

// MARK: - Previews

struct CustomToggle_Previews: PreviewProvider {
  static var previews: some View {
    ViewWrapper()
  }

  struct ViewWrapper: View {
    @State(initialValue: false) var isOn: Bool

    var body: some View {
      CustomToggle(isOn: $isOn)
        .previewLayout(.fixed(width: 100, height: 100))
    }
  }
}
NeverwinterMoon
  • 2,363
  • 21
  • 24
2
  1. The easist way is setting UISwitch.appearance().onTintColor = UIColor.red before using toggle and use SwiftUI Toggle like below.
UISwitch.appearance().onTintColor = UIColor.red
...

let toggle = Toggle(isOn: $vm.dataUsePermission, label: {
    Text(I18N.permit_data_usage)
        .font(SwiftUI.Font.system(size: 16, weight: .regular))
})

if #available(iOS 14.0, *) {
    toggle.toggleStyle(
        SwitchToggleStyle(tint: Color(UIColor.m.blue500))
    )
} else {
    toggle.toggleStyle(SwitchToggleStyle())
}

...
  1. You can alse use same Toggle interface in SwiftUI but different name, and change tint color.

enter image description here


TintableSwitch(isOn: .constant(true), label: {
    Text("Switch")
})

Toggle(isOn: .constant(true), label: {
    Text("Switch")
})

If only need Toggle without Label, then

TintableUISwitch(isOn: .constant(true))

Use below code.

import SwiftUI

public struct TintableSwitch<Label>: View where Label: View {

    @Binding var isOn: Bool

    var label: Label

    public init(isOn: Binding<Bool>, @ViewBuilder label: () -> Label) {
        self._isOn = isOn
        self.label = label()
    }

    public var body: some View {
        HStack {
            label
            Spacer()
            TintableUISwitch(isOn: $isOn, onTintColor: .red) //  CHANGE HERE
        }
    }
}

public struct TintableUISwitch: UIViewRepresentable {

    @Binding var isOn: Bool
    private var onTintColor: UIColor

    public init(isOn: Binding<Bool>, onTintColor: UIColor = UIColor.m.blue500) {
        self._isOn = isOn
        self.onTintColor = onTintColor
    }

    public func makeUIView(context: Context) -> UISwitch {
        let uiSwitch = UISwitch()
        uiSwitch.addTarget(
            context.coordinator,
            action: #selector(Coordinator.valueChanged(_:)),
            for: .valueChanged
        )
        uiSwitch.onTintColor = onTintColor
        uiSwitch.isOn = isOn
        return uiSwitch
    }

    public func updateUIView(_ uiView: UISwitch, context: Context) {
        uiView.isOn = isOn
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    public class Coordinator: NSObject {

        var tintableSwitch: TintableUISwitch

        init(_ tintableSwitch: TintableUISwitch) {
            self.tintableSwitch = tintableSwitch
        }

        @objc
        func valueChanged(_ sender: UISwitch) {
            tintableSwitch.isOn = sender.isOn
        }
    }
}

struct TintableSwitch_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            TintableSwitch(isOn: .constant(true), label: {
                Text("Switch")
            })

            Toggle(isOn: .constant(true), label: {
                Text("Switch")
            })
        }
    }
}

struct TintableUISwitch_Previews: PreviewProvider {
    static var previews: some View {
        TintableUISwitch(isOn: .constant(true))
    }
}

Won
  • 1,107
  • 9
  • 16
1

I would change @Mark Moeykens answer a little bit to avoid having the button tap animation. A better solution would be:

@available(iOS 13.0, *)
struct ColoredToggleStyle: ToggleStyle {
    var label = ""
    var onColor = UIColor.proacPrimaryBlue.suColor
    var offColor = UIColor.systemGray5.suColor
    var thumbColor = Color.white

    func makeBody(configuration: Self.Configuration) -> some View {
        HStack {
            Text(label)
            Spacer()
            RoundedRectangle(cornerRadius: 16, style: .circular)
                .fill(configuration.isOn ? onColor : offColor)
                .frame(width: 50, height: 29)
                .overlay(
                    Circle()
                        .fill(thumbColor)
                        .shadow(radius: 1, x: 0, y: 1)
                        .padding(1.5)
                        .offset(x: configuration.isOn ? 10 : -10))
                .animation(Animation.easeInOut(duration: 0.1))
                .onTapGesture {
                    configuration.isOn.toggle()
                }
        }
        .font(.title)
        .padding(.horizontal)
    }
}
Reshad
  • 2,570
  • 8
  • 45
  • 86
0

This https://stackoverflow.com/a/56480720/5941807 (for now whit Xcode 11 beta 6) is a solution. To switch between to option a fast way is using the boolean instead of if/else:

showFavoritesOnly ? .red : .blue

for foreground:

Toggle(isOn: $showGreeting) {
  Text("Show Favorites only").foregroundColor(showFavoritesOnly ? .blue : .gray)
}

for tint:

uiView.onTintColor = showFavoritesOnly ? UIColor.blue : UIColor.gray

In addition for custom colors: https://stackoverflow.com/a/57744208/5941807

Joannes
  • 2,569
  • 3
  • 17
  • 39