SwiftUI Button interact with Map - swift

I'm totally new with Swift and SwiftUI and for a project group, I need to develop my first IOS app.
I can display a map with Mapbox but I don't know how to follow my user when I click on a button.
I don't know how to interact my button with my struct MapView
This is my code:
MapView.swift:
import Mapbox
struct MapView: UIViewRepresentable {
let mapView: MGLMapView = MGLMapView(frame: .zero)
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapView>) {
}
func styleURL(_ styleURL: URL) -> MapView {
mapView.styleURL = styleURL
return self
}
func centerCoordinate(_ centerCoordinate: CLLocationCoordinate2D) -> MapView {
mapView.centerCoordinate = centerCoordinate
return self
}
func zoomLevel(_ zoomLevel: Double) -> MapView {
mapView.zoomLevel = zoomLevel
return self
}
func userTrackingMode(_ userTrackingMode: MGLUserTrackingMode) -> MapView {
mapView.userTrackingMode = userTrackingMode
return self
}
}
class Coordinator: NSObject, MGLMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
}
ContentView.swift:
import Mapbox
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
ZStack {
MapView()
.userTrackingMode(.follow)
.edgesIgnoringSafeArea(.all)
HStack(alignment: .top) {
Spacer()
VStack() {
Button(action: {
//ACTION TO CHANGE FOLLOW MODE
}) {
Image(systemName: "location.fill")
.frame(width: 40.0, height: 40.0)
}
.padding(.top, 60.0)
.padding(.trailing, 10.0)
.frame(width: 45.0, height: 80.0)
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.colorScheme, .dark)
}
}

Actually I think in your case, as you have a reference to map, you can try to interact with it directly (aka imperatively, because it is such by nature, so no need to make simple thing complex)
Like
...
let myMapHolder = MapView()
var body: some View {
ZStack {
myMapHolder
.userTrackingMode(.follow)
.edgesIgnoringSafeArea(.all)
...
VStack() {
Button(action: {
self.myMapHolder.mapView.userTrackingMode = _your_mode_
}) {
Image(systemName: "location.fill")

Related

`ColorPicker` with active label?

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.

SwiftUI why a variable is passed on other views automatically?

How is my MapView "annotations" variable get updated every time I change my "locations" variable in my ContentView? I googled that swift array is a value type, and I don't even need to use binding on the "annotations" and bind to the "locations" for "annotations" get to know when "locations" changes, why is that?
import SwiftUI
import MapKit
struct ContentView: View {
#State var centerCoordinate = CLLocationCoordinate2D()
#State var locations = [MKPointAnnotation]()
var body: some View {
ZStack{
MapView(centerCoordinate: $centerCoordinate, annotations: locations)
Circle()
.fill(Color.blue)
.opacity(0.5)
.frame(width: 32, height: 32, alignment: .center)
VStack{
Spacer()
HStack{
Spacer()
Button(action: {
let newLocation = MKPointAnnotation()
newLocation.coordinate = centerCoordinate
locations.append(newLocation)
}){
Image(systemName: "plus")
}
.padding()
.background(Color.black.opacity(0.75))
.foregroundColor(.white)
.font(.title)
.clipShape(Circle())
.padding([.trailing, .bottom])
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
struct MapView: UIViewRepresentable{
#Binding var centerCoordinate: CLLocationCoordinate2D
var annotations: [MKPointAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
// Callback function - when anything being sent to UIViewRepresentabel struct is changed
func updateUIView(_ uiView: UIViewType, context: Context) {
print("Updating UIView")
if annotations.count != uiView.annotations.count{
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotations(annotations)
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
let parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.centerCoordinate = mapView.centerCoordinate
}
}
}
extension MKPointAnnotation{
static var example: MKPointAnnotation{
let point = MKPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: 51.5, longitude: -0.13)
point.title = "London"
point.subtitle = "The home of 2012 Summer Olympics"
return point
}
}
Not sure if I understood your question but let me try.
First you have this state in your View:
#State var locations = [MKPointAnnotation]()
Next, you assign its value to:
MapView(centerCoordinate: $centerCoordinate, annotations: locations)
This means, every time you change locations, SwiftUI will re-render the MapView with new values.
Any UIViewRepresentable have to implement the func updateUIView in order to receive these changes when SwiftUI re-render the respective component.
So, every time your MapView being rendered, this function is going to be trigged:
func updateUIView(_ uiView: UIViewType, context: Context) {
print("Updating UIView")
if annotations.count != uiView.annotations.count{
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotations(annotations)
}
}
The uiView.annotations and annotations are note related each other. At the moment uiView.annotations changes, it just make side effects in the view itself and don't update the given #State because it could create an infinity rendering loop.

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

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

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