SwiftUI 不活动计时器

问题描述 投票:0回答:1

我想在用户 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
}
swiftui timer touch-event user-inactivity
1个回答
0
投票

我看到的大多数解决方案都是通过覆盖

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
有点浪费,因为您只需要发布者的一个输出。可能有更好的方法来完成这部分......

© www.soinside.com 2019 - 2024. All rights reserved.