🦋
SwiftUI: タップで1度、長押し中は繰り返し発火するボタン
キーボードのキーのように単に押した時は一度処理が走り、長押しした時はその最中繰り返し処理が走るようなボタンを実装したかったのですが、iOS 16までのSwiftUIにはそんな便利なAPIが生えていないので自作しました。(iOS 17にはbuttonRepeatBehavior()
が導入されるっぽい)
要点
-
DragGesture
を使う -
ObservableObject
でTimer
と処理発火の管理をする -
threshold
で長押し判定は調節できる(この例だと0.1×5=0.5秒)
class RepeatWhilePressingButtonModel: ObservableObject {
@Published var isTouching: Bool = false
private var timer: Timer?
private let threshold: Int = 5
private var counter: Int = 0
func setup(onEvent handler: @escaping () -> Void) {
guard timer == nil else { return }
isTouching = true
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
guard let self else { return }
self.counter += 1
if self.threshold < self.counter {
handler()
}
}
}
func reset() {
isTouching = false
timer?.invalidate()
timer = nil
counter = 0
}
}
struct RepeatWhilePressingButton: View {
let systemName: String
let onEventHandler: () -> Void
@StateObject var model = RepeatWhilePressingButtonModel()
var body: some View {
Image(systemName: systemName)
.frame(width: 32, height: 32)
.padding(4)
.background(Color.gray)
.cornerRadius(8)
.opacity(model.isTouching ? 0.5 : 1.0)
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0.0, coordinateSpace: .global)
.onChanged { drag in
onEventHandler()
model.setup {
onEventHandler()
}
}
.onEnded { drag in
model.reset()
}
)
}
}
Discussion