我想在用户 30 秒没有触摸屏幕后显示提示。为此,每当触摸屏幕时都需要调用
restartInactivityTimer()
函数。
有没有办法发现任何屏幕触摸来重新启动计时器,但不“消耗”它的点击手势?我真的需要从
每个
restartInactivityTimer()
(子)视图闭包调用
onTapGesture
吗,就像下面的代码片段一样?
struct MyView: View {
@State private var inactivityTimer: Timer?
var body: some View {
VStack {
subView1
subView2
subView3
subView4
}
.onAppear {
restartInactivityTimer()
}
.onTapGesture {
// not reaching here when any subView is touched
restartInactivityTimer()
}
}
func restartInactivityTimer() {
inactivityTimer?.invalidate()
inactivityTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false) { _ in
showHint()
}
}
var subView1: some View {
// ...
.onTapGesture {
// ... other actions for tapping that subview
restartInactivityTimer()
}
}
// ... other subviews
}
我看到的大多数解决方案都是通过覆盖
UIApplication.sendEvent
(示例)来检测用户活动。我们无法在 SwiftUI 中轻松做到这一点,但如果您进行方法调配,则是可能的。
您可以在应用程序启动之前通过编写自己的入口点来混合方法。
@main
struct EntryPoint {
// swizzle here
static func main() {
let original = class_getInstanceMethod(UIApplication.self, #selector(UIApplication.sendEvent))!
let new = class_getInstanceMethod(ActivityDetector.self, #selector(ActivityDetector.mySendEvent))!
method_exchangeImplementations(original, new)
// launch your app
YourApp.main()
}
}
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
调配的方法是:
class ActivityDetector: NSObject, ObservableObject {
private typealias SendEventFunc = @convention(c) (AnyObject, Selector, UIEvent) -> Void
@objc func mySendEvent(_ event: UIEvent) {
// call the original sendEvent
// from: https://stackoverflow.com/a/61523711/5133585
unsafeBitCast(
class_getMethodImplementation(ActivityDetector.self, #selector(mySendEvent)),
to: SendEventFunc.self
)(self, #selector(UIApplication.sendEvent), event)
// send a notification, just like in the non-SwiftUI solutions
NotificationCenter.default.post(name: .init("UserActivity"), object: nil)
}
}
现在在您的视图中,您可以收听通知:
@State var presentAlert = false
@State var timer = Timer.publish(every: 60, on: .current, in: .common).autoconnect()
var body: some View {
Text("Foo")
// time is up!
.onReceive(timer) { _ in
presentAlert = true
timer.upstream.connect().cancel()
}
// user did something!
.onReceive(NotificationCenter.default.publisher(for: .init("UserActivity")), perform: { _ in
timer.upstream.connect().cancel()
timer = Timer.publish (every: 5, on: .current, in: .common).autoconnect()
})
.alert("Foo", isPresented: $presentAlert) {
Button("OK") {}
}
}
在这里使用
Timer.publish
有点浪费,因为您只需要发布者的一个输出。可能有更好的方法来完成这部分......