在 SwiftUI 中设置切换颜色

Set Toggle color in SwiftUI

我跟随 Apple 的 tutorial on user input 实现了一个切换。目前,它看起来像这样:

这是生成此 UI 的代码:

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

现在,我希望 Toggle 颜色为蓝色而不是绿色。
我试过了:

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

None 其中有效,我找不到任何其他修饰符,例如 tintColor.

如何更改 Toggle 的颜色?

我还没有找到直接更改 Toggle 颜色的方法,但是使用蓝色开关或任何其他自定义视图的另一种方法是创建您自己的自定义视图。要以最简单的形式制作自定义蓝色开关:

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()
    }
}

结果:

只需使用 UIAppearance API:

UISwitch.appearance().onTintColor = UIColor.blue

根据 UIAppearance 文档,它当然会默认更改 UISwitch 所有实例的外观。

注意:从 Xcode 11 beta 5 开始测试。

这个 (现在是 Xcode 11 beta 6)是一个解决方案。要在选项之间切换,一种快速的方法是使用布尔值而不是 if/else:

showFavoritesOnly ? .red : .blue

前景:

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

色调:

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

另外自定义颜色:

SwiftUI 3.0

使用色调

引入了一个新的修改器,它也可以改变切换颜色:

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

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

SwiftUI 2.0

使用 SwitchToggleStyle

您现在只能在 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))

SwiftUI 1.0

使用 ToggleStyle

我创建了一个新的 ToggleStyle 来更改 Toggle 的三种颜色(打开颜色、关闭颜色和拇指)。

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)
    }
}

使用示例

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))

来自 SwiftUI 书籍

您可以在 init() 中修改所有 UISwitch 对象的全局 onTintColor。

@State var enable_dhcp = true

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

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

建立在@mohammad-reza-farahani 的解决方案之上,这是一种完全不妥协的方法,可以通过 SwiftUI 的实现协议获得 UISwitch 的可配置性。

首先将 UISwitch 包裹在 UIViewRepresentable 中,然后根据需要设置颜色:

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
    }
}

其次,使用包装的 UISwitch:

创建一个 custom toggle style
struct CustomToggleStyle: ToggleStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        let toggle = CustomToggleWrapper(isOn: configuration.$isOn)

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

像往常一样实施 Toggle,并应用您的 CustomToggleStyle:

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

    var body: some View {
        Toggle(
            isOn: $isOn
        ) {
            Text("Test: \(String(isOn))")
        }.toggleStyle(CustomToggleStyle()).padding()
    }
}

因为最初的问题只是关于改变颜色的切换而不是完整的 Toggle 视觉定制,我认为这样的事情会做:

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))
    }
  }
}

Karol Kulesza 和 George Valkov 提供了一个非常易于实施的解决方案。我只是想补充一点,您也可以将下面的代码放在应用程序委托的 didFinishLaunching 方法中。

UISwitch.appearance().onTintColor = .blue

您还可以使用

创建更具体的外观配置
appearance(whenContainedInInstancesOf:)

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

SwiftUI 2.0(Post WWDC-2020)

使用新的 SwiftUI 增强功能,您可以使用 .toggleStyle 修饰符。

// Switch tinting

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

请注意,这仅适用于 iOS14/iPadOS14/macOS11 及以上。

我会稍微更改@Mark Moeykens 的回答以避免出现按钮点击动画。更好的解决方案是:

@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)
    }
}
  1. 最简单的方法是在使用 toggle 之前设置 UISwitch.appearance().onTintColor = UIColor.red 并像下面这样使用 SwiftUI Toggle。
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. 您也可以在 SwiftUI 中使用相同的 Toggle 界面但名称不同,并更改色调颜色。


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

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

如果只需要Toggle而不需要Label,那么

TintableUISwitch(isOn: .constant(true))

使用下面的代码。

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))
    }
}

您可以使用色调修改器在 IOS 15.0 中更改切换颜色。

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

IOS15.0及以下,可以使用toggleStyle修饰符改变切换颜色,但以后会贬值

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