How to make button for Dark Mode - swift

I want to make possibility to switching between dark and light mode on my main screen. I don't want to make it with UISlider, but with just a button. You tap once and its dark mode, you tap again and its light. Is it possible to do?

UIKit
UPDATE
The updated version is more reliable, it checks the overrideUserInterfaceStyle and if it is .unspecified there is a fallback to UIScreen.main.traitCollection.userInterfaceStyle
//
// ViewController.swift
// ToggleInterfaceStyle
//
// Created by Sebastian on 23.09.22.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Here is the button
let toggleInterfaceStyleButton = UIButton()
toggleInterfaceStyleButton.setTitle("Toggle Color Scheme", for: .normal)
toggleInterfaceStyleButton.setTitleColor(.tintColor, for: .normal)
toggleInterfaceStyleButton.frame = CGRect(x: 15, y: -50, width: 250, height: 500)
toggleInterfaceStyleButton.addTarget(self, action: #selector(toggleInterfaceStyle), for: .touchUpInside)
self.view.addSubview(toggleInterfaceStyleButton)
}
#objc func toggleInterfaceStyle() {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
let interfaceStyle = window?.overrideUserInterfaceStyle == .unspecified ? UIScreen.main.traitCollection.userInterfaceStyle : window?.overrideUserInterfaceStyle
if interfaceStyle != .dark {
window?.overrideUserInterfaceStyle = .dark
} else {
window?.overrideUserInterfaceStyle = .light
}
}
}
SwiftUI
For SwiftUI, you can change the appearance (light mode / dark mode) with .preferredColorScheme, it changes the appearance for the entire app. In this example I added a second view and a navigationLink to show that the styles stays as selected:
import SwiftUI
struct ContentView: View {
#State private var scheme: ColorScheme = .light
#State private var isShowingNewAccountView = false
func toggleScheme() {
if scheme == .light {
scheme = .dark
} else {
scheme = .light
}
}
var body: some View {
NavigationView() {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
.padding()
Text("Hello, world!")
.padding()
NavigationLink(destination: SecondView()) {
Text("Second View")
}
Button(action: {
self.toggleScheme()
}) {
Text("Toggle Scheme")
}
.padding()
}
}
.preferredColorScheme(scheme)
}
}
struct SecondView: View {
var body: some View {
VStack {
Image(systemName: "airplane")
.imageScale(.large)
.foregroundColor(.accentColor)
.padding()
Text("Hello, second view!")
.padding()
}
}
}

Related

Swift - How to change badge color of UITabItem programatically?

I want to change color of badge of barItem programatically from default red color.
You need to modify it through UITabBar.appearance()
Example:
struct ContentView: View {
var body: some View {
TabView {
SomeContent()
.tabItem {
Label("Tab1", systemImage: "star")
}
SomeContent()
.tabItem {
Label("Tab2", systemImage: "star")
}
.badge(10)
}
.onAppear() {
let appearance = UITabBarAppearance()
//portrait
appearance.stackedLayoutAppearance.normal.badgeBackgroundColor = .green
//landscape
appearance.inlineLayoutAppearance.normal.badgeBackgroundColor = .blue
UITabBar.appearance().standardAppearance = appearance
}
}
}

How to dynamically change UINavigationBar color in Swiftui

I am working in SwiftUI, and I am trying to get Navigation Bar to update its color when a button is pressed.
I see that there are related questions, but it only allows for static colors in the navigation view.
In the provided example, I only see that the toolbar changes when the tab is changed after the button is pressed.
Is there a way to have the view update without changing tabs (which I believe requires creating a new navigationView).
struct ContentView: View {
var body: some View {
TabView {
First()
.tabItem{
Image(systemName: "1.square")
}
Second()
.tabItem{
Image(systemName: "2.square")
}
Third()
.tabItem{
Image(systemName: "3.square")
}
}
}//:Body
}//:ContentView
struct First: View {
var body: some View {
NavigationView {
HStack {
Button(action: {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = UIColor(.red)
UINavigationBar.appearance().barTintColor = UIColor(.red)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}, label: {
Text("red")
})//:Button
Button(action: {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = UIColor(.blue)
UINavigationBar.appearance().barTintColor = UIColor(.blue)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}, label: {
Text("blue")
})//:Button
}
.toolbar(content: {
ToolbarItem(placement: .principal, content: {
Text("Hello World 1")
})
})
}//:NavView
}
}
struct Second: View {
var body: some View {
NavigationView {
ScrollView {
Text("Don't use .appearance()!")
}
.navigationBarTitle("Try it!", displayMode: .inline)
.background(NavigationConfigurator { nc in
nc.navigationBar.barTintColor = .green
nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
})
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct NavigationConfigurator: UIViewControllerRepresentable {
var configure: (UINavigationController) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
if let nc = uiViewController.navigationController {
self.configure(nc)
}
}
}
struct Third: View {
#State var navigationBackground: UIColor = UIColor(.gray)
var body: some View {
NavigationView {
HStack {
Spacer()
Button(action: {
navigationBackground = UIColor(.purple)
}, label: {
Text("purple")
.foregroundColor(.purple)
})//:Button
Spacer()
Button(action: {
navigationBackground = UIColor(.black)
}, label: {
Text("black")
.foregroundColor(.black)
})//:Button
Spacer()
}
.toolbar(content: {
ToolbarItem(placement: .principal, content: {
Text("Hello World 1")
})
})
}//:NavView
.navigationBarColor(backgroundColor: navigationBackground, titleColor: UIColor(.red))
.navigationBarTitleDisplayMode(.inline)
}
}
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: UIColor?, titleColor: UIColor?) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UIToolbar.appearance().barTintColor = backgroundColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: UIColor?, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
The solutions that I tried are derivatives of answers from this post: SwiftUI update navigation bar title color
I just found a solution much later here
SwiftUI: Update Navigation Bar Color
The thing that seems to make everything work to force the change is an id property on the navigation view.
This worked for my use case.

Add shadow above SwiftUI's TabView

I'm trying to implement TabView in SwiftUI that has the same color as screen background but also has a shadow above it like in this picture:
So far I've been able to properly display color, but I don't know how to add the shadow. This is my code:
struct ContentView: View {
init() {
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
UITabBar.appearance().standardAppearance = appearance
}
var body: some View {
TabView {
Text("First View")
.tabItem {
Image(systemName: "square.and.arrow.down")
Text("First")
}
Text("Second View")
.tabItem {
Image(systemName: "square.and.arrow.up")
Text("Second")
}
}
}
}
Does anyone know how to do this? I would appreciate your help :)
Short answer
I found a solution. You can create your own shadow image and add it to the UITabBar appearance like this:
// load your custom shadow image
let shadowImage: UIImage = ...
//you also need to set backgroundImage, without it shadowImage is ignored
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().shadowImage = shadowImage
More detailed answer
Setting backgroundImage
Note that by setting
UITabBar.appearance().backgroundImage = UIImage()
you make your TabView transparent, so it is not ideal if you have content that can scroll below it. To overcome this, you can set TabView's color.
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor.systemGray6
UITabBar.appearance().standardAppearance = appearance
Setting shadowImage
I wanted to generate shadow image programatically. For that I've created an extension of UIImage. (code taken from here)
extension UIImage {
static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
And finally I styled my TabView like this:
let image = UIImage.gradientImageWithBounds(
bounds: CGRect( x: 0, y: 0, width: UIScreen.main.scale, height: 8),
colors: [
UIColor.clear.cgColor,
UIColor.black.withAlphaComponent(0.1).cgColor
]
)
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor.systemGray6
appearance.backgroundImage = UIImage()
appearance.shadowImage = image
UITabBar.appearance().standardAppearance = appearance
Result
Your best bet in order to achieve pretty much exactly what you wish is to create a custom TabView.
In fact, in SwiftUI you could use UITabBarAppearance().shadowColor, but that won't do much apart from drawing a 2px line on top of the TabView itself.
Instead, with the below code, you could create a custom TabView and achieve the desired graphical effect.
import SwiftUI
enum Tab {
case borrow,ret,device,you
}
struct TabView: View {
#Binding var tabIdx: Tab
var body: some View {
HStack {
Group {
Spacer()
Button (action: {
self.tabIdx = .borrow
}) {
VStack{
Image(systemName: "arrow.down.circle")
Text("Borrow")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .borrow ? .purple : .secondary)
Spacer()
Button (action: {
self.tabIdx = .ret
}) {
VStack{
Image(systemName: "arrow.up.circle")
Text("Return")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .ret ? .purple : .secondary)
Spacer()
Button (action: {
self.tabIdx = .device
}) {
VStack{
Image(systemName: "safari")
Text("Device")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .device ? .purple : .secondary)
Spacer()
Button (action: {
self.tabIdx = .you
}) {
VStack{
Image(systemName: "person.circle")
Text("You")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .you ? .purple : .secondary)
Spacer()
}
}
.padding(.bottom, 30)
.padding(.top, 10)
.background(Color(red: 0.95, green: 0.95, blue: 0.95))
.font(.system(size: 30))
.frame(height: 80)
}
}
struct ContentView: View {
#State var tabIdx: Tab = .borrow
var body: some View {
NavigationView {
VStack(spacing: 20) {
Spacer()
if tabIdx == .borrow {
Text("Borrow")
} else if tabIdx == .ret {
Text("Return")
} else if tabIdx == .device {
Text("Device")
} else if tabIdx == .you {
Text("You")
}
Spacer(minLength: 0)
TabView(tabIdx: self.$tabIdx)
.shadow(radius: 10)
}
.ignoresSafeArea()
}
}
}
Remember that when you do this, all your tabs are specified as cases within enum Tab {}, and the TabView() contains some Button elements, which will change the tab by using the #State var tabIdx. So you can adjust your actions by modifying the self.tabIdx = <yourtab> statement.
The active color is set with this statement after each button:
.foregroundColor(self.tabIdx == .borrow ? .purple : .secondary)
Here you can just change .purple to whatever suits you.
You can see that on the ContentView, the if-else if block catches the SubView. Here I have placed some Text() elements like Text("Borrow"), so you can replace them by calling something like BorrowView() or whatever you have.
I have added the shadow when I call the TabView from within the ContentView, but you could even add the .shadow(radius: 10) after the HStack of the TabView itself.
This would be the final output:
                 

UISwitch.appearance().onTintColor not working since iOS 14 [duplicate]

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?
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)
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))
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
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.
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.
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:
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)
}
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()
}
}
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
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))
}
}
}
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())
}
...
You can alse use same Toggle interface in SwiftUI but different name, and change tint color.
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))
}
}
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))
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)
}
}
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

Set Toggle color in SwiftUI

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?
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)
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))
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
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.
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.
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:
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)
}
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()
}
}
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
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))
}
}
}
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())
}
...
You can alse use same Toggle interface in SwiftUI but different name, and change tint color.
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))
}
}
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))
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)
}
}
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