模仿iOS聲音控制元件
xcode 13.4.1, swift 5.5, iOS 15.4
2022-08-14
蘋果手機的控制面板大家天天都在用,只要向上一滑,就可以設置屏幕亮度、聲音大小,開啓小電筒等,這次我們就來看看如果用SwiftUI很簡單就可以實現聲音大小的控制元件。
整個元件共分三個層次,最底層是灰底,中間層就白色,用來顯示現在的音量,最上層就是一個圖示。
1. 為了更好的顯示的對比,把底色設為teal,先放最底層灰底的圓角長方形。
import SwiftUI
struct LightComponent : View {
var body: some View {
ZStack {
Color.teal.ignoresSafeArea()
ZStack {
Rectangle()
.fill(.gray)
.cornerRadius(10)
}.frame(width: 50, height: 200)
}
}
}
2. 為了更好的顯示的對比,把底色設為teal,先放最底層灰底的圓角長方形。
import SwiftUI
struct LightComponent : View {
var body: some View {
ZStack {
Color.teal.ignoresSafeArea()
ZStack {
Rectangle()
.fill(.gray)
.cornerRadius(10)
.overlay(
Rectangle()
.fill(.white)
.cornerRadius(10)
)
}.frame(width: 50, height: 200)
}
}
}
3. 最大聲就是全部都顯示白色,一半聲就只把下半部份顯示為白色,上半部份為灰底色,若是靜音就全部只顯示灰底色。為了達到以上的效果,可以使用clipShape和trim來裁切顯示部份,如下只顯示一半的白色,所以把trim設為0.0到0.5,:
struct ClipLine : Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.width/2, y: rect.height))
path.addLine(to: CGPoint(x: rect.width/2, y: 0))
}
}
}
struct LightComponent : View {
var body: some View {
ZStack {
Color.teal.ignoresSafeArea()
ZStack {
Rectangle()
.fill(.gray)
.cornerRadius(10)
.overlay(
Rectangle()
.fill(.white)
.cornerRadius(10)
.clipShape(
ClipLine()
.trim(from: 0.0, to: 0.5)
.stroke(lineWidth: 100)
)
)
}.frame(width: 50, height: 200)
}
}
}
4. 以下實現drag去控制白色條的增加或減少:
struct LightComponent : View {
@State var current = 0.5
@State var tempCurrent = 0.5
@State var delta = 0.0
var body: some View {
ZStack {
Color.teal.ignoresSafeArea()
GeometryReader { proxy in
let size = proxy.size
let dragGesture = DragGesture()
.onChanged({ value in
delta = value.translation.height/size.height
tempCurrent = max(0, min(1, current - delta))
})
.onEnded({ value in
current = max(0, min(1, current - delta))
delta = 0.0
})
ZStack {
Rectangle()
.fill(.gray)
.cornerRadius(10)
.overlay(
Rectangle()
.fill(.white)
.cornerRadius(10)
.clipShape(
ClipLine()
.trim(from: 0.0, to: current - delta)
.stroke(lineWidth: 100)
)
)
}
.gesture(dragGesture)
}.frame(width: 50, height: 200)
}
}
}
5. 最後在底部放上聲音的圖示,為了把圖示放在整個元件的底部,所以把ZStack的alignment設為bottom:
struct LightComponent : View {
@State var current = 0.5
@State var tempCurrent = 0.5
@State var delta = 0.0
var body: some View {
ZStack {
Color.teal.ignoresSafeArea()
GeometryReader { proxy in
let size = proxy.size
let dragGesture = DragGesture()
.onChanged({ value in
delta = value.translation.height/size.height
tempCurrent = max(0, min(1, current - delta))
})
.onEnded({ value in
current = max(0, min(1, current - delta))
delta = 0.0
})
ZStack(alignment: .bottom) {
Rectangle()
.fill(.gray)
.cornerRadius(10)
.overlay(
Rectangle()
.fill(.white)
.cornerRadius(10)
.clipShape(
ClipLine()
.trim(from: 0.0, to: current - delta)
.stroke(lineWidth: 100)
)
)
ZStack {
if tempCurrent == 0.0 {
Image(systemName: "speaker.slash.fill")
}
if tempCurrent > 0 && tempCurrent <= 0.25 {
Image(systemName: "speaker.wave.1.fill")
}
if tempCurrent > 0.25 && tempCurrent < 0.75 {
Image(systemName: "speaker.wave.2.fill")
}
if tempCurrent >= 0.75 {
Image(systemName: "speaker.wave.3.fill")
}
}
.foregroundColor(.black)
.animation(.linear(duration: 0.2), value: tempCurrent)
.padding(.bottom, 10)
}
.gesture(dragGesture)
}.frame(width: 50, height: 200)
}
}
}