`ColorPicker` with active label? - swift

Consider the following code:
struct ContentView: View {
#State var color: Color = .blue
var body: some View {
ColorPicker(selection: $color) {
Label("Pallete", systemImage: "paintpalette")
}
}
}
It brings up a color picker modal view if you tap on color circle. I would like the same to happen also for taps on the label.

These is way to use the fancy system color picker any way we like, but as of iOS 15 it will require bringing with UIKit.
Create a new view struct like this:
import SwiftUI
struct ColorPickerPanel: UIViewControllerRepresentable {
#Binding var color: Color
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIColorPickerViewController {
let picker = UIColorPickerViewController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ picker: UIColorPickerViewController, context: Context) {
picker.selectedColor = UIColor(color)
}
class Coordinator: NSObject, UIColorPickerViewControllerDelegate {
var parent: ColorPickerPanel
init(_ pageViewController: ColorPickerPanel) {
self.parent = pageViewController
}
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
parent.color = Color(uiColor: viewController.selectedColor)
}
}
}
Then use it like this:
struct ContentView: View {
#State var color: Color = .accentColor
#State var isColorPickerPresented = false
var body: some View {
VStack {
Button {
isColorPickerPresented = true
} label: {
ColorPicker(selection: $color) {
Label("Pallete", systemImage: "paintpalette")
.allowsHitTesting(true)
.accessibilityAddTraits(.isButton)
}
}
}
.sheet(isPresented: $isColorPickerPresented) {
ZStack (alignment: .topTrailing) {
ColorPickerPanel(color: $color)
Button {
isColorPickerPresented = false
} label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.tint, .secondary)
.font(.title)
}
.offset(x: -10, y: 10)
}
}
}
}
You may provide another to dismiss picker, of course.

Related

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 TabBar Item badge count with SwiftUI

Is it possible to show TabItem badge with SwiftUI?
It is easy to achieve with UIKit like described here ->
How to set badge value in Tab bar?
I didn't find a way to do this with a SwiftUI.
The only possible way is to access to UITabBarController using scene rootViewController and modify its tab bar items directly.
func setBadgeCount(_ count: Int) {
UIApplication.shared.applicationIconBadgeNumber = count
guard let delegate = app.connectedScenes.first?.delegate as? SceneDelegate else {
return
}
if let tabBarController = delegate.window?.rootViewController?.children.first {
tabBarController.viewControllers?.first?.tabBarItem.badgeValue = "\(count)"
}
}
Any ideas how to do this with native SwiftUI approach?
Currently, SwiftUI don't have badge feature so we must custom.
Reference HERE I create My tabar with badge
struct ContentView: View {
private var badgePosition: CGFloat = 2
private var tabsCount: CGFloat = 2
#State var selectedView = 0
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
TabView {
Text("First View")
.tabItem {
Image(systemName: "list.dash")
Text("First")
}.tag(0)
Text("Second View")
.tabItem {
Image(systemName: "star")
Text("Second")
}.tag(1)
}
ZStack {
Circle()
.foregroundColor(.red)
Text("3")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
.frame(width: 15, height: 15)
.offset(x: ( ( 2 * self.badgePosition) - 0.95 ) * ( geometry.size.width / ( 2 * self.tabsCount ) ) + 2, y: -30)
.opacity(1.0)
}
}
}
}
Now in SwiftUI 3 they added a .badge() modifier
Source: HackingWithSwift
TabView {
Text("Your home screen here")
.tabItem {
Label("Home", systemImage: "house")
}
.badge(5)
}
func calculateBadgeXPos(width: CGFloat) -> CGFloat {
let t = (2*CGFloat(self.selectedTab))+1
return CGFloat(t * width/(2*CGFloat(TABS_COUNT)))
}
then use it here:
GeometryReader { geometry in
ZStack(alignment: .bottomLeading) {
// make sure to update TABS_COUNT
TabView(selection: self.$selectedTab) {
...
}
NotificationBadge(...)
.offset(x: self.calculateBadgeXPos(width: geometry.size.width), y: -28)
}
}
Looks sth like this on Preview
The above mentioned .introspectTabBarController modifier worked for me.
Gave a nice native badge on tabItem. Looks great on both orientations.
.introspectTabBarController { (UITabBarController) in
self.tabBarControl = UITabBarController
if let items = UITabBarController.tabBar.items {
let tabItem = items[2] // in my case it was 3rd item
tabItem.badgeValue = "5" // hardcoded
}
}
Though, when changing tabs, the badge disappears so I saved UITabBarController in #State and when tab changes I set tabItem.badgeValue again.
P.S: iOS 15+ supports .badge modifier. Use it if you're targeting above or iOS 15.
iOS 15 added support for .badge modifier, but as I need to support iOS 14, I've created UITabBarController wrapper:
struct TabBarController<TabContent: View, Tab: Hashable>: UIViewControllerRepresentable {
let tabs: [Tab]
#Binding
var selection: Tab
let tabBarItem: (Tab) -> UITabBarItem
let badgeValue: (Tab) -> String?
#ViewBuilder
let contentView: (Tab) -> TabContent
func makeUIViewController(context: Context) -> UITabBarController {
let controller = UITabBarController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {
context.coordinator.viewControllers
.keys
.filterNot(tabs.contains(_:))
.forEach { removedKey in
context.coordinator.viewControllers.removeValue(forKey: removedKey)
}
uiViewController.viewControllers = tabs.map { tab in
let rootView = contentView(tab)
let viewController = context.coordinator.viewControllers[tab] ?? {
let viewController = UIHostingController(rootView: rootView)
viewController.tabBarItem = tabBarItem(tab)
context.coordinator.viewControllers[tab] = viewController
return viewController
}()
viewController.rootView = rootView
viewController.tabBarItem.badgeValue = badgeValue(tab)
return viewController
}
uiViewController.selectedIndex = tabs.firstIndex(of: selection) ?? 0
}
func makeCoordinator() -> Coordinator {
Coordinator(selection: $selection)
}
final class Coordinator: NSObject, UITabBarControllerDelegate {
#Binding
private var selection: Tab
var viewControllers = [Tab: UIHostingController<TabContent>]()
init(selection: Binding<Tab>) {
_selection = selection
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let newTab = viewControllers.first(where: { $0.value == viewController })?.key else {
print("tabBarController:didSelect: unexpected")
return
}
selection = newTab
}
}
}
filterNot:
extension Collection {
#inlinable public func filterNot(_ isNotIncluded: (Element) throws -> Bool) rethrows -> [Element] {
try filter { try !isNotIncluded($0) }
}
}
Usage:
TabBarController(
tabs: [1,2,3],
selection: $selection,
tabBarItem: { tab in
UITabBarItem(title: "tab \(tab)", image: UIImage(systemName: "1.square.fill"), tag: 0)
},
badgeValue: { tab in
"\(tab)"
},
contentView: { tab in
Text("\(tab) screen")
}
).ignoresSafeArea()
struct ContentView: View {
var body: some View {
TabView {
Text("Home")
.tabItem {
Text("Home")
}
Text("Home")
.tabItem {
Text("Home")
}
Text("Home")
.tabItem {
Text("Home")
}
}
.introspectTabBarController { (tabbarController) in
if let items = tabbarController.tabBar.items {
let tabItem = items[2]
tabItem.badgeValue = "1"
}
}
}
}

SwiftUI - Hiding a ScrollView's indicators makes it stop scrolling

I'm trying to hide the indicators of a ScrollView but when I try doing so, the ScrollView just doesn't scroll anymore. I'm using macOS if that matters.
ScrollView(showsIndicators: false) {
// Everything is in here
}
On request of #SoOverIt
Demo:
Nothing special, just launched some other test example. Xcode 11.2 / macOS 10.15
var body : some View {
VStack {
ScrollView([.vertical], showsIndicators: false) {
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
}
.frame(height: 100)
.border(Color.blue)
}
.border(Color.red)
}
I fixed the issue.
extension View {
func hideIndicators() -> some View {
return PanelScrollView{ self }
}
}
struct PanelScrollView<Content> : View where Content : View {
let content: () -> Content
var body: some View {
PanelScrollViewControllerRepresentable(content: self.content())
}
}
struct PanelScrollViewControllerRepresentable<Content>: NSViewControllerRepresentable where Content: View{
func makeNSViewController(context: Context) -> PanelScrollViewHostingController<Content> {
return PanelScrollViewHostingController(rootView: self.content)
}
func updateNSViewController(_ nsViewController: PanelScrollViewHostingController<Content>, context: Context) {
}
typealias NSViewControllerType = PanelScrollViewHostingController<Content>
let content: Content
}
class PanelScrollViewHostingController<Content>: NSHostingController<Content> where Content : View {
var scrollView: NSScrollView?
override func viewDidAppear() {
self.scrollView = findNSScrollView(view: self.view)
self.scrollView?.scrollerStyle = .overlay
self.scrollView?.hasVerticalScroller = false
self.scrollView?.hasHorizontalScroller = false
super.viewDidAppear()
}
func findNSScrollView(view: NSView?) -> NSScrollView? {
if view?.isKind(of: NSScrollView.self) ?? false {
return (view as? NSScrollView)
}
for v in view?.subviews ?? [] {
if let vc = findNSScrollView(view: v) {
return vc
}
}
return nil
}
}
Preview:
struct MyScrollView_Previews: PreviewProvider {
static var previews: some View {
ScrollView{
VStack{
Text("hello")
Text("hello")
Text("hello")
Text("hello")
Text("hello")
}
}.hideIndicators()
}
}
So... I think that's the only way for now.
You basically just put a View over your ScrollView indicator with the same backgroundColor as your background View
Note: This obviously only works if your background is static with no content at the trailing edge.
Idea
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme: ColorScheme
let yourBackgroundColorLight: Color = .white
let yourBackgroundColorDark: Color = .black
var yourBackgroundColor: Color { colorScheme == .light ? yourBackgroundColorLight : yourBackgroundColorDark }
var body: some View {
ScrollView {
VStack {
ForEach(0..<1000) { i in
Text(String(i)).frame(width: 280).foregroundColor(.green)
}
}
}
.background(yourBackgroundColor) //<-- Same
.overlay(
HStack {
Spacer()
Rectangle()
.frame(width: 10)
.foregroundColor(yourBackgroundColor) //<-- Same
}
)
}
}
Compact version
You could improve this like that, I suppose you have your color dynamically set up inside assets.
Usage:
ScrollView {
...
}
.hideIndicators(with: <#Your Color#>)
Implementation:
extension View {
func hideIndicators(with color: Color) -> some View {
return modifier(HideIndicators(color: color))
}
}
struct HideIndicators: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.overlay(
HStack {
Spacer()
Rectangle()
.frame(width: 10)
.foregroundColor(color)
}
)
}
}

SwiftUI hide TabBar in subview

I am working with SwiftUI, and I have some issues with the TabBar.
I want to hide the TabBar on a specific subview.
Have tried with
UITabBar.appearance().isHidden = true
It only works on the direct views in the TabView. But when I place it in a subview it doesn't work.
Have anyone a solution for this?
Thanks.
iOS 16 native way
.toolbar(.hidden, for: .tabBar)
iOS 14
Install the Introspect SwiftPM: https://github.com/siteline/SwiftUI-Introspect
struct SomeView: View{
#State var uiTabarController: UITabBarController?
var body: some View {
List {
-your code here-
}
.navigationBarTitle("Title", displayMode: .inline)
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = true
uiTabarController = UITabBarController
}.onDisappear{
uiTabarController?.tabBar.isHidden = false
}
}
}
In this code in uiTabarController, we are taking the reference of UITabarController. When we go back then we enabled the Tabar again. So, that's why this is needed.
iOS 14 Simple Solution
Install the Introspect SwiftPM: https://github.com/siteline/SwiftUI-Introspect
var body: some View {
List {
-your code here-
}
.navigationBarTitle("Title", displayMode: .inline)
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = true
}
}
NOTE: You have to re-enable the TabBar it in the parent view or it will still be hidden.
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false
}
It's working, only changes are need to be called on the main queue
struct ShowTabBar: ViewModifier {
func body(content: Content) -> some View {
return content.padding(.zero).onAppear {
DispatchQueue.main.async {
Tool.showTabBar()
}
}
}
}
struct HiddenTabBar: ViewModifier {
func body(content: Content) -> some View {
return content.padding(.zero).onAppear {
DispatchQueue.main.async {
Tool.hiddenTabBar()
}
}
}
}
Traverse the allsubview of the window to hide the UITabBar. You can write it as ViewModifier and use it in SwiftUI or use tools to hide it. This method works for me.
extension UIView {
func allSubviews() -> [UIView] {
var res = self.subviews
for subview in self.subviews {
let riz = subview.allSubviews()
res.append(contentsOf: riz)
}
return res
}
}
struct Tool {
static func showTabBar() {
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
if let view = v as? UITabBar {
view.isHidden = false
}
})
}
static func hiddenTabBar() {
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
if let view = v as? UITabBar {
view.isHidden = true
}
})
}
}
struct ShowTabBar: ViewModifier {
func body(content: Content) -> some View {
return content.padding(.zero).onAppear {
Tool.showTabBar()
}
}
}
struct HiddenTabBar: ViewModifier {
func body(content: Content) -> some View {
return content.padding(.zero).onAppear {
Tool.hiddenTabBar()
}
}
}
extension View {
func showTabBar() -> some View {
return self.modifier(ShowTabBar())
}
func hiddenTabBar() -> some View {
return self.modifier(HiddenTabBar())
}
}
iOS 15 solution
This solution works well except with view modifier in the SwiftUI.TabView.
Since my TabView is in the struct that conforms App, it looks like there still is not any UITabBar subview in the connected scenes.
With the code below, you only need to use showTabBar() or hiddenTabBar() in your SwiftUI.View.
extension UIApplication {
var key: UIWindow? {
self.connectedScenes
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?
.windows
.filter({$0.isKeyWindow})
.first
}
}
extension UIView {
func allSubviews() -> [UIView] {
var subs = self.subviews
for subview in self.subviews {
let rec = subview.allSubviews()
subs.append(contentsOf: rec)
}
return subs
}
}
struct TabBarModifier {
static func showTabBar() {
UIApplication.shared.key?.allSubviews().forEach({ subView in
if let view = subView as? UITabBar {
view.isHidden = false
}
})
}
static func hideTabBar() {
UIApplication.shared.key?.allSubviews().forEach({ subView in
if let view = subView as? UITabBar {
view.isHidden = true
}
})
}
}
struct ShowTabBar: ViewModifier {
func body(content: Content) -> some View {
return content.padding(.zero).onAppear {
TabBarModifier.showTabBar()
}
}
}
struct HiddenTabBar: ViewModifier {
func body(content: Content) -> some View {
return content.padding(.zero).onAppear {
TabBarModifier.hideTabBar()
}
}
}
extension View {
func showTabBar() -> some View {
return self.modifier(ShowTabBar())
}
func hiddenTabBar() -> some View {
return self.modifier(HiddenTabBar())
}
}
here's no way to hide TabView so I had to add TabView inside ZStack as this:
var body: some View {
ZStack {
TabView {
TabBar1().environmentObject(self.userData)
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
TabBar2()
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}
}
if self.userData.showFullScreen {
FullScreen().environmentObject(self.userData)
}
}
}
UserData:
final class UserData: ObservableObject {
#Published var showFullScreen = false
}
TabBar1:
struct TabBar1: View {
#EnvironmentObject var userData: UserData
var body: some View {
Text("TabBar 1")
.edgesIgnoringSafeArea(.all)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.green)
.onTapGesture {
self.userData.showFullScreen.toggle()
}
}
}
FullScreen:
struct FullScreen: View {
#EnvironmentObject var userData: UserData
var body: some View {
Text("FullScreen")
.edgesIgnoringSafeArea(.all)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.background(Color.red)
.onTapGesture {
self.userData.showFullScreen.toggle()
}
}
}
check full code on Github
there's also some other ways but it depends on the structure of the views
The basic idea I'm using is to combine ObservableObject and ZStack. I have placed TabView into ZStack with conditional subview presentation.
It's look like.
Look through github repo
To hide TabBar when we jumps towards next screen we just have to place NavigationView to the right place. Makesure Embed TabView inside NavigationView so creating unique Navigationview for both tabs.
As explained here https://janeshswift.com/ios/swiftui/how-to-hide-tabbar-on-push-with-swiftui/
import SwiftUI
struct TabBarView: View {
#State var tabSelection: Int = 0
#State var tabArray = ["Profile", "Settings"]
var body: some View {
NavigationView {
TabView(selection: $tabSelection){
ForEach(0 ..< tabArray.count, id: \.self) { indexValue in
NavigationLink(destination: DetailView()){
VStack{
Text("\(tabArray[indexValue]) tab -- Click to jump next view")
}
}
.tabItem {
Image(systemName: "\(indexValue).circle.fill")
Text(tabArray[indexValue])
}
.tag(indexValue)
}
}
.navigationBarTitle(tabArray[tabSelection])
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail View")
.navigationBarTitle("NavigatedView")
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("helllo")
}
}
Install the Introspect SwiftPM: https://github.com/siteline/SwiftUI-Introspect
in order to use this you need to create a variable of type UITabBar in the view you want the tabbar to be hidden...
enter code here
#State private var tabBar: UITabBar?
then below the navigationView in the same view you have to add this line:
.introspectTabBarController { UITabBarController in tabBar = UITabBarController.tabBar
self.tabBar?.isHidden = true } .onDisappear() { self.tabBar?.isHidden = false }
It is actually possible to get the underlying UITabbarController for TabView by using this handy little framework :
https://github.com/siteline/SwiftUI-Introspect
This solution uses the MVVM pattern as an example to have programmatic control over the Tabbar visibility, and be able to show, hide, enable, disable form anywhere in the code using NSNotifications
SwiftUI View : Setup the tabview like this
struct MainTabView: View {
var viewModel: MainTabViewModel
var body: some View {
TabView() {
Text("View1")
.tabItem {
Text("View1")
}
Text("View2")
.tabItem {
Text("View2")
}
}
.introspectTabBarController { tabBarController in
// customize here the UITabBarViewController if you like
self.viewModel.tabBarController = tabBarController
}
}
}
Then for the ViewModel
final class MainTabViewModel: ObservableObject {
var tabBarController: UITabBarController?
init() {
startListeningNotifications()
}
func startListeningNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(showTabbarView), name: "showBottomTabbar", object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(hideTabbarView), name: "hideBottomTabbar", object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(enableTabbarTouch), name: "enableTouchTabbar", object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(disableTabbarTouch), name: "disableTouchTabbar", object: nil)
}
#objc func showTabbarView() {
self.tabBarController?.tabBar.isHidden = false
}
#objc func hideTabbarView() {
self.tabBarController?.tabBar.isHidden = true
}
#objc func disableTabbarTouch() {
self.tabBarController?.tabBar.isUserInteractionEnabled = false
}
#objc func enableTabbarTouch() {
self.tabBarController?.tabBar.isUserInteractionEnabled = true
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
and finally to control the tabbar, just use these fonctions from wherever you feel like (will be in the viewmodels in the pattern of this example)
public func showTabbar() {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .showBottomTabbar, object: nil)
}
}
public func hideTabbar() {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .hideBottomTabbar, object: nil)
}
}
public func enableTouchTabbar() {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .enableTouchTabbar, object: nil)
}
}
public func disableTouchTabbar() {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .disableTouchTabbar, object: nil)
}
}
Increase the frame size of TabView like this:
.frame(width: UIScreen.main.bounds.width, height: showTabbar ? UIScreen.main.bounds.height : UIScreen.main.bounds.height + 100.00)
Not ideal and hacky but the simplest thing to do for me that is working very well was to hide the navigationBar of the outer navigationView and then add another navigationView in each of the TabView's views. Works well so far:
struct LaunchView: View {
var body: some View {
NavigationView {
TabView {
ViewA()
.tabItem {
Label("TabA", systemImage: "some.image")
}
ViewB()
.tabItem {
Label("TabB", systemImage: "some.image")
}
ViewC()
.tabItem {
Label("TabC", systemImage: "some.image")
}
}
.navigationBarHidden(true)
}
}
}
struct ViewA: View {
var body: some View {
NavigationView {
// Content
.navigationTitle("Settings")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
This way you can set the title but also the .toolBarItem's in each separate view.
in general, it's nice to be able to create pages with an w/o tabbar
it looks smooth and your page content doesn't change it's size while hiding tabbar on the page
solution is
hide tabbar from the root container
add custom tabbar modifier
use this modifier on navViews to show the tabbar for all nav view hierarchy OR use it on the specific pages in the view hierarchy
here is a small sample project how your app could look like with this approach
https://github.com/alexis-ag/swiftui_classic-tabview_show-hide
I have tried to use https://stackoverflow.com/a/62963499/11844048 solution but the TabBar hide in all views once I landed this view. I have modified it a bit to achieve to hide TabBar in single view.
struct AppInfoView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
ZStack{
}
.frame(maxWidth: .infinity)
.background(Color("homepage_bg")).ignoresSafeArea(.all)
.onAppear{
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
if let view = v as? UITabBar {
view.isHidden = true
}
})
}
.onDisAppear(...) //it works too. But seeing TabBar shown bit delay when naviagting back. So below the customizable back button.
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action : {
self.mode.wrappedValue.dismiss()
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.allSubviews().forEach({ (v) in
if let view = v as? UITabBar {
view.isHidden = false
}
})
}){
Image(systemName: "chevron.left")
})
}
extension UIView {
func allSubviews() -> [UIView] {
var res = self.subviews
for subview in self.subviews {
let riz = subview.allSubviews()
res.append(contentsOf: riz)
}
return res
}
}
Most answers here deal with this requirement in one of two ways:
import a framework to locate the UITabBarController
modify the view hierarchy (ZStack, NavigationView, ...)
The first one is a clean approach: it locates the underlying element that enables the desired action. However, it may be overkill for a single use case.
The second approach involves some tradeoffs and could be generally considered a smell, since it introduces hierarchy changes for the sake of working around the lack of access to the required element.
Instead, we could follow a clean, simple approach by creating a protocol extension like so:
import UIKit
protocol TabBarAppearanceDelegate {
func toggleVisibility()
func hideTabBar()
func showTabBar()
// add more methods to control appearance as needed
}
extension TabBarAppearanceDelegate {
private var tabBarController: UITabBarController? {
// this is where we access the underlying element, no need to import a framework for a one-liner
UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController?.children.first as? UITabBarController
}
func toggleVisibility() {
tabBarController?.tabBar.isHidden.toggle()
}
func hideTabBar() {
tabBarController?.tabBar.isHidden = true
}
func showTabBar() {
tabBarController?.tabBar.isHidden = false
}
}
Then we can make any object conform to this protocol, and inject it as dependency in the views as needed. This will depend on your architecture but it could go like follows.
This is where you'd keep app-wide state, an ObservableObject (you could designate a different one, if preferred):
import Foundation
class StateController: ObservableObject {
// you would typically manage app-wide state here
}
// this is where we adopt the needed behaviour
extension StateController: TabBarAppearanceDelegate {}
We can now inject the object as a view dependency:
#main
struct TabBarVisibilityApp: App {
private let stateController = StateController()
var body: some Scene {
WindowGroup {
TabView {
NavigationView {
SampleView(tabBarAppearanceDelegate: stateController)
}
.tabItem {
Label("Home", systemImage: "house")
}
}
}
}
}
This is how you would use it (valid for in any view that requires the behaviour):
import SwiftUI
struct SampleView: View {
let tabBarAppearanceDelegate: TabBarAppearanceDelegate
var body: some View {
VStack {
Spacer()
Button(action: {
tabBarAppearanceDelegate.toggleVisibility()
} ) {
Text("Toggle tab bar visibility")
}
Spacer()
}
}
}
This approach is simple, testable, and requires no extra dependencies... until Apple provides a direct way to control tab bar visibility with a SwiftUI API.
iOS 16
Usage of .toolbar modifier. Create state property of type Visibility and manage its value from the pushed view
First view
struct FirstTab: View {
#State var tabBarVisibility: Visibility = .visible
var body: some View {
NavigationView {
NavigationLink(destination: WidgetDetailView(tab: self)) {
Text("test")
}
}
.toolbar(tabBarVisibility, for: .tabBar)
}
}
Second view
struct WidgetDetailView: View {
var tab: FirstTab
var body: some View {
Rectangle()
.foregroundColor(Color.red)
.onAppear {
tab.tabBarVisibility = .hidden
}
.onDisappear {
tab.tabBarVisibility = .visible
}
}
}
On iOS 16
Use .toolbar(.hidden, for: .tabBar).
For example:
var body: some View {
TabView {
FirstView()
.tabItem {
Text("First tab")
}
.toolbar(.hidden, for: .tabBar)
SecondView()
.tabItem {
Text("Second tab")
}
.toolbar(.hidden, for: .tabBar)
}
}
Note that .toolbar(.hidden, for: .tabBar) is applied to each tabItem, not to the parent view. YMMV if you have a different structure (like a parent NavigationView etc.)
Add NavigationView as root instead of TabView
NavigationView{
TabView(selection:$selectedIndex) {
}
}
If you don't want NavigationView in TabBar page just hide it.
.navigationBarHidden(true)
Refer sample project - https://github.com/TreatTrick/Hide-TabBar-In-SwiftUI
just use UIKitโ€˜s UINavigationController, like this:
let host = UINavigationController(rootViewController:
UIHostingController(rootView: HLHome()))
It is possible!
Basically your task is to extract UITabBar somehow and then hide it programatically.
Below is the code which emulates tab bar hiding on push behaviour.
struct ContentView: View {
var body: some View {
TabView {
ForEach(titles, id: \.self) { title in
NavigationView {
view(fromTitle: title)
}
.tabItem {
Text(title)
Image(systemName: "photo")
}
.tag(title)
}
}
}
private let titles = ["one", "two"]
#ViewBuilder
private func view(fromTitle title: String) -> some View {
if title == "one" {
RegularView(title: title)
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
} else {
HideOnPushView(title: title)
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct RegularView: View {
let title: String
var body: some View {
VStack(spacing: 20) {
Text(title)
NavigationLink("Regular push") {
Text("1111111")
}
}
}
}
struct HideOnPushView: View {
let title: String
var body: some View {
VStack(spacing: 20) {
Text(title)
NavigationLink("Hide on push") {
Text("222222")
.onAppear {
tabBar?.hide()
}
}
}
.background(
TabBarExtractor(tabBar: $tabBar)
)
.onAppear {
tabBar?.show()
}
}
#State private var tabBar: UITabBar?
}
TabBar extractor code:
import SwiftUI
struct TabBarExtractor: UIViewControllerRepresentable {
#Binding var tabBar: UITabBar?
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
func makeUIViewController(context: Context) -> some UIViewController {
let controller = ViewController()
controller.onTabBarAppearance = {
tabBar = $0
}
return controller
}
}
private extension TabBarExtractor {
class ViewController: UIViewController {
var onTabBarAppearance: ((UITabBar) -> Void)?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let tabBar = self.tabBarController?.tabBar {
onTabBarAppearance?(tabBar)
} else {
print("Could not locate TabBar! Try change extractor place in views hierarchy.")
}
}
}
}
TabBar category:
import UIKit
extension UITabBar {
func toggleVisibility() {
if isHidden {
show()
} else {
hide()
}
}
func show() {
guard isHidden else { return }
let visibleY = frame.origin.y
let hiddenY = visibleY + frame.height
frame.origin.y = hiddenY
isHidden = false
UIView.animate(withDuration: 0.3) { [weak self] in
self?.frame.origin.y = visibleY
}
}
func hide() {
guard !isHidden else { return }
let visibleY = frame.origin.y
let hiddenY = visibleY + frame.height
UIView.animate(withDuration: 0.3) { [weak self] in
self?.frame.origin.y = hiddenY
} completion: { [weak self] completed in
guard completed else { return }
self?.isHidden = true
self?.frame.origin.y = visibleY
}
}
}
I had the same problem, and ended up using:
.opacity(hideTabBar == true ? 0 : 1)
where hideTabBar is a Bool that I pass around to the views that need to hide the TabBar().
So basically you should try something like this:
TabView(selection: your desired tab).opacity(hideTabBar == true ? 0 : 1)

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