How to create custom slider by using SwiftUI?
Solution 1
As it turned out for me accent color is depending on the context, as well as the frame, so we don't need to handle that.
As far as the control goes I made a really dummy and simple example. Please do not consider this as a solution, rather a starter.
struct CustomView: View {
@Binding var percentage: Float // or some value binded
var body: some View {
GeometryReader { geometry in
// TODO: - there might be a need for horizontal and vertical alignments
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(.gray)
Rectangle()
.foregroundColor(.accentColor)
.frame(width: geometry.size.width * CGFloat(self.percentage / 100))
}
.cornerRadius(12)
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ value in
// TODO: - maybe use other logic here
self.percentage = min(max(0, Float(value.location.x / geometry.size.width * 100)), 100)
}))
}
}
}
You can use it like
@State var percentage: Float = 50
...
var body: some View {
...
CustomView(percentage: $percentage)
.accentColor(.red)
.frame(width: 200, height: 44)
...
Solution 2
In my case, I had to customize the thumb. (ex. screen locker)
I leave an answer for a problem similar one.
LockerSlider.swift
import SwiftUI
struct LockerSlider<V>: View where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint {
// MARK: - Value
// MARK: Private
@Binding private var value: V
private let bounds: ClosedRange<V>
private let step: V.Stride
private let length: CGFloat = 50
private let lineWidth: CGFloat = 2
@State private var ratio: CGFloat = 0
@State private var startX: CGFloat? = nil
// MARK: - Initializer
init(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride = 1) {
_value = value
self.bounds = bounds
self.step = step
}
// MARK: - View
// MARK: Public
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .leading) {
// Track
RoundedRectangle(cornerRadius: length / 2)
.foregroundColor(Color(#colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)))
// Thumb
Circle()
.foregroundColor(.white)
.frame(width: length, height: length)
.offset(x: (proxy.size.width - length) * ratio)
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ updateStatus(value: $0, proxy: proxy) })
.onEnded { _ in startX = nil })
}
.frame(height: length)
.overlay(overlay)
.simultaneousGesture(DragGesture(minimumDistance: 0)
.onChanged({ update(value: $0, proxy: proxy) }))
.onAppear {
ratio = min(1, max(0,CGFloat(value / bounds.upperBound)))
}
}
}
// MARK: Private
private var overlay: some View {
RoundedRectangle(cornerRadius: (length + lineWidth) / 2)
.stroke(Color.gray, lineWidth: lineWidth)
.frame(height: length + lineWidth)
}
// MARK: - Function
// MARK: Private
private func updateStatus(value: DragGesture.Value, proxy: GeometryProxy) {
guard startX == nil else { return }
let delta = value.startLocation.x - (proxy.size.width - length) * ratio
startX = (length < value.startLocation.x && 0 < delta) ? delta : value.startLocation.x
}
private func update(value: DragGesture.Value, proxy: GeometryProxy) {
guard let x = startX else { return }
startX = min(length, max(0, x))
var point = value.location.x - x
let delta = proxy.size.width - length
// Check the boundary
if point < 0 {
startX = value.location.x
point = 0
} else if delta < point {
startX = value.location.x - delta
point = delta
}
// Ratio
var ratio = point / delta
// Step
if step != 1 {
let unit = CGFloat(step) / CGFloat(bounds.upperBound)
let remainder = ratio.remainder(dividingBy: unit)
if remainder != 0 {
ratio = ratio - CGFloat(remainder)
}
}
self.ratio = ratio
self.value = V(bounds.upperBound) * V(ratio)
}
}
Demo.swift
import SwiftUI
struct Demo: View {
// MARK: - Value
// MARK: Private
@State private var number = 150000.0
// MARK - View
// MARK: Public
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Number\n\(number)")
.bold()
.padding(.bottom, 20)
Text("OS Slider")
Slider(value: $number, in: 0...1050000, step: 0.02)
.padding(.bottom, 20)
Text("Custom Slider")
LockerSlider(value: $number, in: 0...1050000, step: 0.02)
.padding(.bottom, 20)
}
.padding(20)
}
}
Mohammad Mugish
Updated on June 30, 2022Comments
-
Mohammad Mugish almost 2 years
I am able to create a Slider by using SwiftUI but I am not able to change the style of the slider as shown in the image(below).
Problem: I am not able to find any option in SwiftUI to change the slider style.
Note: I want to create this by using SwiftUI only. I already created this slider in Swift by using "https://github.com/daprice/iOS-Tactile-Slider"
I have tried following but it's not the solution :
1. Slider(value: .constant(0.3)).accentColor(Color.white) 2. Slider(value: $age, in: 18...20, step: 1, minimumValueLabel: Text("18"), maximumValueLabel: Text("20")) { Text("") } 3. Slider(value: $age, in: 18...20, step: 1, minimumValueLabel: Image(systemName: "18.circle"), maximumValueLabel: Image(systemName: "20.circle")) { Text("") }
How can I create a slider with the style as shown in the image using SwiftUI only?
-
gujci over 4 yearsSorry, first, I have not read it correctly. I started to look into "how to pass parameters to
UIViewRepresentable
". But later realised, you wanted the full swiftUI stuff. So I guess the question in how can you extract the modifier information from the original view. -
Mohammad Mugish over 4 yearsYes. There are two things. First is, Create Slider from scratch by using SwiftUI or Second is to Create Slider by extracting something from the UIKit library. Both are good options because my focus is to design this slider in SwiftUI.
-
gujci over 4 yearsFor the second thing, wrapping it is the solution, if I understand you correctly. This snippet might be something for starter. gist.github.com/Gujci/7a7c37ce6a4bc29c498ca3c593bf2b69
-
gujci over 4 yearsAlso,
accentColor
depends on the context, so, if you make a new swiftUI view and use.foregroundColor(.accentColor)
it will be the one, you set on the outer level. -
gujci over 4 yearsAlso I don't think, that SwiftUI color can be translated back to UIKit with a public API.
-
-
Mohammad Mugish over 4 yearsThanks for this solution. I got it.
-
Mohammad Mugish over 4 yearsI found one problem with this. If I want to change the value of the slider by just tapping anywhere on the slider, but the value is not getting changed. I need to drag it, then the only value is getting changed. How can I change the value by just tapping anywhere on the slider?
-
gujci over 4 yearsLOL, It's much more simple, use
DragGesture(minimumDistance: 0)
. Found here -
eResourcesInc over 4 yearsThis got me on the right path. Combine this with an @EnvironmentObject holding an audio player, and you have got the start of a simple, dynamic SwiftUI audio player tracker
-
JohnKubik over 2 yearsHey Den! This code is amazing. I was wondering if you had some time where I could pay you to tutor me on swift. I am trying to understand how this works, and getting hung up on how the heck the number gets updated. Thanks!
-
Den over 2 years@JohnKubik I think WWDC is the best tutor. Swift and SwiftUI are still babies, so you can learn quickly and easily through WWDC and keep up with the latest technology. I also recommend the free iOS courses from Stanford University. You can learn about SwiftUI. youtube.com/watch?v=bqu6BquVi2M