【问题标题】:Set Toggle color in SwiftUI在 SwiftUI 中设置切换颜色
【发布时间】:2019-10-22 02:19:05
【问题描述】:

在关注 Apple 的 tutorial on user input 之后,我实现了切换。目前,它看起来像这样:

这是生成此 UI 的代码:

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

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

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

这些都不起作用,我找不到任何其他修饰符,例如tintColor

如何更改Toggle 的颜色?

【问题讨论】:

  • 根据文档,.accentColor 似乎应该更改Toggle 的颜色。也许向 Apple 提交错误报告,看看他们怎么说?
  • developer.apple.com/documentation/swiftui/toggle。它似乎是 SwiftUI 中最接近 UIKit 的 tintColor 的选项,这就是为什么我说它似乎应该改变 Toggle 的颜色。如果您提交有关它的错误报告,那么希望任何回复的人都会确认这是一个错误,或者有另一种 SwiftUI 方法可以做到这一点。
  • 我认为这是一个错误。它应该随着.accentColor 而改变。我提交了 FB6158727,如果 Apple 对此有任何说明,我会通知您。 :)
  • @CliftonLabrum 你收到 Apple 的消息了吗?
  • 我使用的是 11.3,但我无法让 .accentColor 工作。

标签: swift toggle swiftui


【解决方案1】:

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 中为 on 位置设置色调颜色:

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 来更改 Toggle 的三种颜色(on color、off color 和 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)
    }
}

使用示例

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 手册

【讨论】:

  • 这是一个非常好的工作,似乎是目前唯一可行的解​​决方案。谢谢!
  • 你在这里做了足够多的工作,创建一个独特的切换而不是用样式修饰符实例化他们破坏的切换是有意义的。使用会更干净
  • 非常好!!!我想知道为什么他们摆脱了 IOS 中的单选按钮。这非常有用。谢谢
  • @EverUribe,感谢您的提醒。更新了答案。
  • @MarkMoeykens 绝对出色的工作。不知道ToggleStyleConfiguration有没有变化,但是在HStack中,不用Text(label),可以直接用configuration.label
【解决方案2】:

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

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

IOS 15.0以下,你可以使用toggleStyle修饰符来改变toggle的颜色,但以后会被贬值。

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

【讨论】:

    【解决方案3】:
    1. 最简单的方法是在使用切换之前设置UISwitch.appearance().onTintColor = UIColor.red,然后使用如下所示的 SwiftUI 切换。
    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")
    })
    
    

    如果只需要不带标签的切换,那么

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

    【讨论】:

      【解决方案4】:

      我会稍微更改@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)
          }
      }
      

      【讨论】:

        【解决方案5】:

        SwiftUI 2.0(WWDC-2020 后)

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

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

        请注意,这只适用于 iOS14/iPadOS14/macOS11 及更高版本。

        【讨论】:

          【解决方案6】:

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

          UISwitch.appearance().onTintColor = .blue
          

          您还可以使用

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

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

          【讨论】:

            【解决方案7】:

            由于最初的问题只是关于更改颜色切换而不是完整的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))
                }
              }
            }
            

            【讨论】:

              【解决方案8】:

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

              【讨论】:

              • 这很好用。您可以在 updateUIView 函数中使用 uiView.setOn(isOn.wrappedValue, animated: context.transaction.animation != nil &amp;&amp; !context.transaction.disablesAnimations) 为更改设置动画。
              【解决方案9】:

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

              @State var enable_dhcp = true
              
              init()
              {
                  UISwitch.appearance().onTintColor = .red
              }
              
              var body: some View
              {
                  Toggle("DHCP", isOn: $enable_dhcp)
              }
              

              【讨论】:

                【解决方案10】:

                这个https://stackoverflow.com/a/56480720/5941807(现在是 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
                

                除了自定义颜色:https://stackoverflow.com/a/57744208/5941807

                【讨论】:

                  【解决方案11】:

                  只需使用UIAppearance API:

                  UISwitch.appearance().onTintColor = UIColor.blue
                  

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

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

                  【讨论】:

                  • 这似乎在 iOS 14/Xcode 12 Beta 3 中被破坏了。
                  • 它一开始似乎可以工作,但如果你关闭并重新打开,颜色会重置为默认值
                  【解决方案12】:

                  我还没有找到直接更改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()
                      }
                  }
                  

                  结果:

                  【讨论】:

                  • 谢谢。我会等待接受答案,看看是否还有其他答案。
                  • 我将如何使用具有相同 init Toggle 的自定义切换:init(isOn: Binding&lt;Bool&gt;, label: () -&gt; Label)
                  • @LinusGeffarth 要实现这一点,我认为还需要使用UIViewControllerRepresentable,但不确定。检查这个例子:developer.apple.com/tutorials/swiftui/interfacing-with-uikit这不是你问的,但它是一种处理自定义视图的更复杂的方式!
                  猜你喜欢
                  • 1970-01-01
                  • 2018-10-20
                  • 2011-10-15
                  • 1970-01-01
                  • 2019-12-21
                  • 2019-11-02
                  • 2021-08-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多