๐ฐSlider label position
โฑ๐ง under construction
Last updated
โฑ๐ง under construction
Last updated
SwiftUI โฉ controls โฉ Slider โฉ label position
่จญๅฎ Slider
ๆธๅผๆจ็ฑคๅฐๆปๆกฟ่ชฟ้็ไธๆนใ
ๆนๆณ ๏ผ๏ผไฝฟ็จๅบๅฎๅฏฌๅบฆ็ Sliderใ
ๅช้ป๏ผๅฎนๆ่จ็ฎๆธๅผๆจ็ฑค็ๅ็งป้ใ
็ผบ้ป๏ผๅฏฌๅบฆๅบๅฎ๏ผ้ฉๆๆงๅทฎใ
.background
่ฒ ่ฒฌๅฐๅญ่ฆๅ (Text
) ็ฝฎไธญๅฐ้ฝ (Text
็ไธญๅฟ้ปๆพๅจ่ๆฏๆญฃไธญๅคฎ)ใ
่จ็ฎ Text
็ๅ็งป้ๆ๏ผ้ ไปฅไธญๅคฎ็บๅบๆบใ
็จ .offset()
ๅ็งป Text
ใ
// view properties
@State var t: CGFloat = 0.5
let sliderWidth: CGFloat = 300 // โญ๏ธ ๆปๆกฟๅฏฌๅบฆ
// view body
// slider(fixed width) > background > text
Slider(value: $t)
// โญ๏ธ 1. `.background` ่ฒ ่ฒฌๅฐๅญ่ฆๅใ็ฝฎไธญๅฐ้ฝใ
.background {
let knobWidth: CGFloat = 28 // ๆปๆกฟ่ชฟ้ๅฏฌๅบฆ
let knobRange = sliderWidth - knobWidth // ่ชฟ้ๆดปๅ็ฏๅๅฏฌๅบฆ
// โญ๏ธ 2. ๅ ็บ Text ๆ่ขซ .background ็ฝฎไธญๅฐ้ฝ
// ๆไปฅ่จ็ฎๅ็งป้ๆ๏ผ้ ไปฅไธญๅคฎ็บๅบๆบใ
let xOffset = (t-0.5) * knobRange // ๆธๅผๆจ็ฑคๅ็งป้
// โญ๏ธ 3. ็จ .offset() ๅ็งป Textใ
Text("\(t, specifier: "%.2f")")
.offset(x: xOffset, y: -30)
}// background
.frame(width: sliderWidth) // โญ๏ธ ๅฏฌๅบฆๅบๅฎ
ๆนๆณ๏ผ-๏ผ๏ผ็จ .background
๏ผ GeometryReader
๏ผ Slider
ๅฐบๅฏธ
ๅช้ป๏ผๅฏ้ฉๆๆฏ่ฆๅ็็ฐๅขใๅคงๅฐๅฏ่ชๅ่ชฟๆดใ
็ผบ้ป๏ผๅฎนๆๅฟฝ็ฅ GeometryReader
ไปฅๅทฆไธ่ง็บๅฐ้ฝๆนๅผ๏ผๅฐ่ดๅฎไฝ้ฏ่ชคใ
้็ถ .background
่ฒ ่ฒฌๅฐๅญ่ฆๅ็ฝฎไธญๅฐ้ฝ๏ผไฝ GeometryReader
ๆฏๅฎๅฏไธ็ๅญ่ฆๅ๏ผ่ไธ GeometryReader
ๆ็จๆๆฏ่ฆๅ็ๆๆ็ฉบ้๏ผๅ ๆญค .background
็ๅฐ้ฝ็ญๆผๆฒๆๆ็จโ๏ธ๏ผๅฐ้ฝไธป่ฆๆฏ็ฑ GeometryReader
ไพๆฅๆๆๆง๏ผ
ๆณจๆ๏ผGeometryReader
็ๅฐ้ฝๆนๅผ่ .background
ไธๅ๏ผๅฎ่ฒ ่ฒฌๅฐๅญ่ฆๅๅฐ้ฝใๅทฆไธ่งใ๏ผๅ ๆญคๅจ GeometryReader
ๅ
ง้จๆพ็ฝฎๅญ่ฆๅ็้่ผฏ่ๅจ .background
ๅ
งไธๅโ๏ธ
่จ็ฎๆจ็ฑคๅ็งป้ๆ๏ผ่ฆไปฅ GeometryReader
ๅทฆไธ่ง่้๏ผๅๆ้่ฆ่้ๅฐ่ชฟ้ๆฌ่บซๆไธๅฎๅฏฌๅบฆ (28)๏ผๅ ๆญค่ชฟ้็ไธญๅฟ้ป็กๆณๆฅ่งธๅฐๆปๆกฟๆๅทฆๅด๏ผ่ๆฏ่ชฟ้็ๅทฆๅดๆฅ่งธๅฐๆปๆกฟ็ๆๅทฆๅด๏ผๆไปฅ่จ็ฎๆจ็ฑคๅ็งป้ๆ๏ผ้่ฆๅ
ๅ ไธ่ชฟ้ๅฏฌๅบฆ็ไธๅใ
็จ .position()
็ดๆฅ่จญๅฎ Text ไธญๅฟ้ปไฝ็ฝฎใ
ๅช้ป๏ผไธๆๅ ็บๅญ้ซๅฏฌๅบฆ่ฎๅ่ๅฐ่ดๆจ็ฑคไฝ็ฝฎ่่ชฟ้็กๆณๅฐ้ฝใ
// view's properties
@State var s: CGFloat = 100 // ๆปๆกฟๆธๅผ
let range: ClosedRange<CGFloat> = 100...300 // ๆธๅผ็ฏๅ
let min = range.lowerBound // ๆๅฐๅผ
let max = range.upperBound // ๆๅคงๅผ
// slider > background > GeometryReader > Text
Slider(value: $s, in: range)
// โญ๏ธ 1. ็จ .background ๆญ้
GeometryReader ไพๅๅพ Slider ็ๅฐบๅฏธ
// - ้็ถ background ่ฒ ่ฒฌๅฐๅญ่ฆๅใ็ฝฎไธญๅฐ้ฝใ๏ผไฝ GeometryReader
// ๆฏๅฎๅฏไธ็ๅญ่ฆๅ๏ผ่ไธ GeometryReader ๆ็จๆ่ๆฏ็ๆๆ็ฉบ้๏ผ
// ๅ ๆญค background ็ๅฐ้ฝ็ญๆผๆฒๆๆ็จโ๏ธ
.background {
// โญ๏ธ 2. ๆณจๆ๏ผGeometryReader ็ๅฐ้ฝๆนๅผ่ background ไธๅ๏ผ
// ๅฎ่ฒ ่ฒฌๅฐๅญ่ฆๅๅฐ้ฝใๅทฆไธ่งใ๏ผๅ ๆญคๅจ GeometryReader ๅ
ง้จ
// ๆพ็ฝฎๅญ่ฆๅ็้่ผฏ่ๅจ background ๅ
งไธๅโ๏ธ
GeometryReader { geo in
let s2 = (s - min)/(max - min) // s2: 0...1
let knobWidth: CGFloat = 28 // ๆปๆกฟ่ชฟ้ๅฏฌๅบฆ
let sliderWidth = geo.size.width // ๆปๆกฟๅฏฌๅบฆ
let knobRange = sliderWidth - knobWidth // ่ชฟ้็ๆดปๅๅฏฌๅบฆ
// โญ๏ธ 3. ่จ็ฎๆจ็ฑคๅ็งป้ๆ๏ผ่ฆไปฅ GeometryReaderใๅทฆไธ่งใ่้๏ผ
// ๅๆ้่ฆ่้ๅฐ่ชฟ้ๅฏฌๅบฆใๅ ็บ่ชฟ้ๆฌ่บซๆไธๅฎๅฏฌๅบฆ (28)๏ผ
// ๅ ๆญค่ชฟ้็ไธญๅฟ้ป็กๆณๆฅ่งธๅฐๆปๆกฟ็ๆๅทฆๅด๏ผ่ๆฏ่ชฟ้็ใๅทฆๅดใ
// ๆฅ่งธๅฐๆปๆกฟ็ๆๅทฆๅด๏ผๆไปฅ่จ็ฎๆจ็ฑคๅ็งป้ๆ๏ผ้่ฆๅ
ๅ ไธ่ชฟ้ๅฏฌๅบฆ็ไธๅใ
let xOffset = (s2) * knobRange + knobWidth/2
// โญ๏ธ 4. ็จ .position() ็ดๆฅ่จญๅฎ Text ไธญๅฟ้ปไฝ็ฝฎ
// ๅช้ป๏ผไธๆๅ ็บๅญ้ซๅฏฌๅบฆ่ฎๅ่ๅฐ่ดๆจ็ฑคไฝ็ฝฎ่่ชฟ้็กๆณๅฐ้ฝใ
Text("\(s, specifier: "%.0f")")
.position(x: xOffset, y: -16)
// โ๏ธ ็จ .offset() ็็ผบ้ป๏ผ
// ่จ็ฎ Text ๅ็งป้ๆ้่ฆ่ๆ
ฎๅฐๅญ้ซๅฏฌๅบฆ่ฎๅ๏ผๅพๅข่จ็ฎๅฐ้ฃใ
// .offset(x: xOffset, y: -30)
}// GeometryReader
}// background
}// slider2-1
ๆนๆณ๏ผ-๏ผ๏ผ็จ .background
๏ผ GeometryReader
๏ผ ZStack
ๅนซๅฟ็ฝฎไธญ
ๅช้ป๏ผ
ๅฏ้ฉๆๆฏ่ฆๅใๅคงๅฐๅฏ่ชๅ่ชฟๆดใ
ZStack
ๅฏๅนซๅฟ็ฝฎไธญๅญ่ฆๅใ
็ผบ้ป๏ผ
ๅฎนๆๅฟ่จๅฐ ZStack
็ๅฐบๅฏธๆพๅฐ่ GeometryReader
ไธๆจฃๅคง๏ผๅฐ่ด GeometryReader
ไพ็ถๅ
ๅฐๆดๅ ZStack
ๆพๅฐๅทฆไธ่ง๏ผ็ถๅพ ZStack
ๆๅจๅฎ็ๅ
ง้จๅ็ฝฎไธญๅฐ้ฝ๏ผ้ๆ็็ฝฎไธญๅฐ้ฝๅฐฑๆฏ็กๆ็๏ผๅ ็บๆดๅ ZStack
้ฝๅจๅทฆไธ่งโ๏ธ ๏ผ็ไธๅ slider 2-2๏ผ
ๅฟ่จๅฐ ZStack
็ๅฐบๅฏธๆพๅฐ่ GeometryReader
ไธๆจฃๅคงโ๏ธ
GeometryReader
ไพ็ถๅ
ๅฐๆดๅ ZStack
ๆพๅฐๅทฆไธ่ง๏ผ็ถๅพ ZStack
ๆๅจๅฎ็ๅ
ง้จๅ็ฝฎไธญๅฐ้ฝโ๏ธ
่จ็ฎๅ็งป้ๆ๏ผไปไปฅใไธญๅฟ้ปใ็บ่้๏ผๅฐ่ดๆจ็ฑคไฝ็ฝฎ้ฏ่ชคโ๏ธ
// view's properties
@State var s: CGFloat = 100 // ๆปๆกฟๆธๅผ
// view body
// 2-2. slider > overlay > GeometryReader > ZStack > Text
Slider(value: $s, in: 100...300)
.background {
// โญ๏ธ 2. GeometryReader ไพ็ถๅ
ๅฐๆดๅ ZStack ๆพๅฐๅทฆไธ่ง๏ผ
// ็ถๅพ ZStack ๆๅจๅฎ็ๅ
ง้จๅ็ฝฎไธญๅฐ้ฝโ๏ธ
GeometryReader { geo in
let s2 = (s - 100)/(300 - 100) // s2: 0...1
let width = geo.size.width - 28 // ่ชฟ้็ๆดปๅๅฏฌๅบฆ
// โญ๏ธ 3. ่จ็ฎๅ็งป้ๆ๏ผไปไปฅใไธญๅฟ้ปใ็บ่้๏ผๅฐ่ดๆจ็ฑคไฝ็ฝฎ้ฏ่ชคโ๏ธ
let xOffset: CGFloat = (s2 - 0.5) * width
// โญ๏ธ 1. ๅฟ่จๅฐ ZStack ๅฐบๅฏธๆพๅฐ่ GeometryReader ไธๆจฃๅคง
ZStack {
Text("\(s, specifier: "%.0f")")
.offset(x: xOffset, y: -30)
}// ZStack
}// GeometryReader
}// background
ๆนๆณ๏ผ-๏ผ๏ผๅๆนๆณ๏ผ-๏ผ๏ผไฝไฟฎๆญฃไบๅฟ่จๅฐ ZStack
็ๅฐบๅฏธๆพๅฐ่ GeometryReader
ไธๆจฃๅคง็ๅ้ก๏ผๅฆๆญค ZStack
ๆ่ฝ็ๆญฃๆฅๆๅนซๅฟ็ฝฎไธญๅฐ้ฝๅญ่ฆๅใ
ไฝฟ็จ .overlay
+ GeometryReader
ๅๅพ Slider
ๅฐบๅฏธ (geo.size
)
ๅ
ๆฃ้ค GeometryReader
ๅทฆๅณๅไธๅๆปๆกฟ่ชฟ้็ๅฏฌๅบฆใ
่จป๏ผ็ถไธๅ view ็จๅฐ view modifier ๆ๏ผๆฏๅญ่ฆๅ้ไฟ่ฆๅ้ไพ็๏ผไพๅฆ๏ผchild.parent().grandParent()
๏ผๅ้ข็ๆฏๅญ่ฆๅใๅพ้ข็ๆฏๆฏ่ฆๅโ๏ธ
ๅฉ็จ .frame(maxWidth: .infinity, maxHeight: .infinity)
ๅฐ ZStack
็ๅฐบๅฏธๆพๅฐๆๅคง (GeometryReader
ๆฃๆ่ชฟ้ๅฏฌๅบฆ)
ๆญคๆ ZStack
ๅฐฑ่ฝๆญฃๅธธๅนซๅฟ็ฝฎไธญๅฐ้ฝ Text
ใ
ๅฉ็จ GeometryReader
็่ณ่จ (geo.size
) ่จ็ฎ ZStack
็ๅฐบๅฏธใ
(่จป๏ผ้่ฃกๆ็จๅฐ GeometryKit ้ๅ custom package)
็จ .position()
็ดๆฅ่จญๅฎๆจ็ฑคไธญๅฟ้ปไฝ็ฝฎ (ๆญคๆๆจ็ฑคๆ็ๅจ่ชฟ้ๆญฃไธๆน)ใ
็จ .offset()
ๅฐๆจ็ฑคๅพไธ่ชฟใ
// view's properties
let bounds: ClosedRange<CGFloat> = 100...300
let min = bounds.lowerBound
let max = bounds.upperBound
// view body
// 2-3. slider > overlay > GeometryReader > ZStack(maximized) > Text
Slider(value: $s, in: bounds)
.overlay {
// โญ๏ธ 1. overlay + GeometryReader ๅๅพ Slider ๅฐบๅฏธ (geo.size)
GeometryReader { geo in
let s2 = (s - min)/(max - min) // s2: 0...1
let knobWidth: CGFloat = 28
let yOffset: CGFloat = -16
// โญ๏ธ 5. ่จ็ฎ ZStack ็ๅคงๅฐ
let zstackSize = geo.size - CGSize(knobWidth, 0) // GeometryKit
// โญ๏ธ 4. ZStack ๅนซๅฟ็ฝฎไธญๅฐ้ฝ Text
ZStack(alignment: .center) {
Text("\(s, specifier: "%.0f")")
// โญ๏ธ 6. ็จ .position() ็ดๆฅ่จญๅฎๆจ็ฑคไธญๅฟ้ปไฝ็ฝฎ๏ผ
// ๆญคๆๆจ็ฑคๆ็ๅจ่ชฟ้ๆญฃไธๆนใ
.position(zstackSize[s2, 0]) // GeometryKit
// โญ๏ธ 7. ๅฐๆจ็ฑคๅพไธ่ชฟใ
.offset(y: yOffset)
}// ZStack
// โญ๏ธ 3. ๅฐ ZStack ็ๅฐบๅฏธๆพๅฐๆๅคง
.frame(maxWidth: .infinity, maxHeight: .infinity)
// โญ๏ธ 2. ๅ
ๆฃ้ค GeometryReader ๅทฆๅณๅไธๅๆปๆกฟ่ชฟ้็ๅฏฌๅบฆ
.padding(.horizontal, knobWidth/2) // knob half width
}//GeometryReader
.allowsHitTesting(false) // ้ฒๆญข overlay ๆไฝ slider
}// overlay
ๆนๆณ๏ผ-๏ผ๏ผๅๆนๆณ๏ผ-๏ผใ็ผๅฐ็ไบบๆ่ฉฒๆๅฏ่ฆบๅฐ๏ผ้็ถๆนๆณ๏ผ-๏ผไธญๆๅๆ็จ ZStack
ไพๅนซๅฟ็ฝฎไธญๅญ่ฆๅ๏ผๅฎๅฏไธ็ๅญ่ฆๅ Text
ๅป้ธๆไบ .position()
ไพ็ดๆฅ่จญๅฎ่ชๅทฑ็ไธญๅฟ้ปไฝ็ฝฎใๆๅฅ่ฉฑ่ชช๏ผZStack
ๆ นๆฌๆฒ่ตทไปปไฝไฝ็จ๏ผๅ ๆญคๅฏไปฅ็ดๆฅ็งป้ค ZStack
โ๏ธ
๐ง
import SwiftUI
import GeometryKit // CGSize as Vector2D
struct SliderValuePosition: View {
@State var t: CGFloat = 0.5
@State var s: CGFloat = 100
// slider range
let range: ClosedRange<CGFloat> = 100...300
var min: CGFloat { range.lowerBound }
var max: CGFloat { range.upperBound }
var s2: CGFloat { (s - min)/(max - min) } // s2: 0...1
let knobWidth: CGFloat = 28 // slider knob width
let yOffset: CGFloat = -16 // label y-offset
var body: some View {
ScrollView {
VStack(spacing: 100) {
Spacer()
slider1
HStack {
slider2_1
slider2_2
}
slider2_3
slider2_4
Spacer()
}// VSTack
.padding()
.frame(maxWidth: 900)
}// ScrollView
}// body
// 1. slider(fixed width) > background > text
var slider1: some View {
let sliderWidth: CGFloat = 300 // โญ๏ธ fixed slider width
let knobRange = sliderWidth - knobWidth
let xOffset = (t-0.5) * knobRange // -0.5...0.5 * knobRange
return Slider(value: $t).background {
Text("\(t, specifier: "%.2f")")
.offset(x: xOffset, y: -30)
}// slider.background
.bgColor(.green)
.frame(width: sliderWidth)
.borderAndTitle("slider 1 (โญ๏ธ fixed width โญ๏ธ)")
}// slider1
// 2-1. slider > background > GeometryReader > Text
var slider2_1: some View {
Slider(value: $s, in: range)
.background {
GeometryReader { geo in
let sliderWidth = geo.size.width // slider width
let knobRange = sliderWidth - knobWidth // knob ็ๆดปๅๅฏฌๅบฆ
let xOffset = s2 * knobRange + knobWidth/2 // label x-offset
// โญ๏ธ ็จ .position() ็ๅช้ป๏ผ
// ็ดๆฅ่จญๅฎไธญๅฟ้ปไฝ็ฝฎ๏ผไธๆๅ ็บๅญ้ซๅฏฌๅบฆ่ฎๅ่ๅฐ่ดไฝ็ฝฎๅ็งป
Text("\(s, specifier: "%.0f")")
.position(x: xOffset, y: -16)
// โ๏ธ ็จ .offset() ็็ผบ้ป๏ผ
// ๅจ GeometryReader ๅ
ง้จ๏ผๅญ่ฆๅๆๅฐ้ฝๅทฆไธ่ง๏ผ
// ๅ ๆญค่จ็ฎ Text ็ๅ็งป้ๆ้่ฆ่ๆ
ฎๅฐๅญ้ซๅฏฌๅบฆ่ฎๅ๏ผ
// ๅฐ่ด่จ็ฎไธ็ๅฐ้ฃใ
// .offset(x: xOffset, y: -30)
}// GeometryReader
.bgColor(.yellow).allowsHitTesting(false)
}// background
.borderAndTitle("slider 2-1")
}// slider2-1
// 2-2. slider > background > GeometryReader > ZStack > Text
var slider2_2: some View {
Slider(value: $s, in: range)
.background {
GeometryReader { geo in
let sliderWidth = geo.size.width
let knobRangeWidth = sliderWidth - knobWidth
let xOffset = (s2 - 0.5) * knobRangeWidth
ZStack {
Text("\(s, specifier: "%.0f")")
.offset(x: xOffset, y: -30) // move upward
}// ZStack
.bgColor(.red, opacity: 0.9)
}// GeometryReader
.bgColor(.yellow)
.allowsHitTesting(false)
}// slider.background
.borderAndTitle("slider 2-2")
}// slider2_2
// 2-3. slider > overlay > GeometryReader > ZStack(maximized) > Text
var slider2_3: some View {
Slider(value: $s, in: range)
.overlay {
GeometryReader { geo in
let zstackSize = geo.size - CGSize(knobWidth, 0) // GeometryKit
ZStack {
Text("\(s, specifier: "%.0f")")
// โญ๏ธ position label directly
.position(zstackSize[s2, 0]) // GeometryKit
.offset(y: yOffset) // move upward
}// ZStack
// โญ๏ธ maximize ZStack
.frame(maxWidth: .infinity, maxHeight: .infinity)
.bgColor(.red)
.padding(.horizontal, knobWidth/2) // knob half width
}//GeometryReader
.allowsHitTesting(false)
}// slider.overlay
.borderAndTitle("slider 2-3")
}// slider2_3
// 2-4. slider > background > GeometryReader > Text
var slider2_4: some View {
Slider(value: $s, in: range)
.background {
GeometryReader { geo in
Text("\(s, specifier: "%.0f")")
// โญ๏ธ position label directly
.position(geo.size[s2, 0]) // GeometryKit
.offset(y: yOffset) // move upward
}//GeometryReader
.padding(.horizontal, knobWidth/2) // โญ๏ธ knob half width
.bgColor(.yellow)
.allowsHitTesting(false)
}// background
.borderAndTitle("slider 2-4")
}// slider2_4
}// SliderValuePosition
// view extension
private extension View {
/// `view.borderAndTitle("name")`
func borderAndTitle(_ title: String) -> some View {
self
.border(.secondary)
.padding()
.border(.secondary)
.overlay {
Text(title)
.offset(y: 50)
.foregroundStyle(Color.secondary)
.multilineTextAlignment(.center)
}
}
/// `view.bgColor(.red)`
func bgColor(_ color: Color, opacity: Double = 0.5) -> some View {
self.background(color.opacity(opacity))
}
}
GeometryReader๏ผ็จๆผๅๅพ Slider ็ๅฏฌๅบฆใ
GeometryKit๏ผcustom package