如何在一段时间不活动后自动更新视图?

问题描述

我正在尝试向我的 SwiftUI 应用程序添加超时功能。达到超时时应更新视图。我在另一个 thread 上找到了代码,它适用于超时部分,但我无法更新视图。

我在 UIApplication 扩展中使用静态属性来切换超时标志。当此静态属性更改时,似乎不会通知视图。这样做的正确方法是什么?

补充说明:

@workingdog 在下面提出了一个答案。这并不完全有效,因为在实际的应用程序中,不仅有一个视图,而且用户可以在多个视图之间导航。所以,我正在寻找一个全局计时器,无论当前视图是什么,它都可以通过任何触摸操作重置。

在示例代码中,全局计时器工作,但是当静态变量 UIApplication.timeout 更改为 true 时,视图不会注意到。

我怎样才能更新视图?有什么比静态变量更适合这个目的吗?或者计时器不应该在 UIApplication 扩展中开始?

这是我的代码:

import SwiftUI

@main
struct TimeoutApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
              .onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
        }
    }
}

extension UIApplication {
  
  private static var timerToDetectInactivity: Timer?
  static var timeout = false
    
  func addTapGestureRecognizer() {
      guard let window = windows.first else { return }
      let tapGesture = UITapGestureRecognizer(target: self,action: #selector(tapped))
      tapGesture.requiresExclusiveTouchType = false
      tapGesture.cancelsTouchesInView = false
      tapGesture.delegate = self
      window.addGestureRecognizer(tapGesture)
  }

  private func resetTimer() {
    let showScreenSaverInSeconds: TimeInterval = 5
    if let timerToDetectInactivity = UIApplication.timerToDetectInactivity {
        timerToDetectInactivity.invalidate()
    }
    UIApplication.timerToDetectInactivity = Timer.scheduledTimer(
      timeInterval: showScreenSaverInSeconds,target: self,selector: #selector(timeout),userInfo: nil,repeats: false
    )
  }
  
  @objc func timeout() {
    print("Timeout")
    Self.timeout = true
  }
    
  @objc func tapped(_ sender: UITapGestureRecognizer) {
    if !Self.timeout {
      print("Tapped")
      self.resetTimer()
    }
  }
}

extension UIApplication: UIGestureRecognizerDelegate {
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      Text(UIApplication.timeout ? "TimeOut" : "Hello World!")
          .padding()
      Button("Print to Console") {
        print(UIApplication.timeout ? "Timeout reached,why is view not updated?" : "Hello World!")
      }
    }
  }
}

解决方法

你可以这样做 - >

  1. 创建“UIApplication”的子类(使用单独的文件,如 MYApplication.swift)。

  2. 在文件中使用以下代码。

  3. 现在一旦用户停止,方法“idle_timer_exceeded”将被调用 触摸屏幕。

  4. 使用通知更新 UI

import UIKit
import Foundation

private let g_secs = 5.0 // Set desired time

class MYApplication: UIApplication
{
    var idle_timer : dispatch_cancelable_closure?
    
    override init()
    {
        super.init()
        reset_idle_timer()
    }
    
    override func sendEvent( event: UIEvent )
    {
        super.sendEvent( event )
        
        if let all_touches = event.allTouches() {
            if ( all_touches.count > 0 ) {
                let phase = (all_touches.anyObject() as UITouch).phase
                if phase == UITouchPhase.Began {
                    reset_idle_timer()
                }
            }
        }
    }
    
    private func reset_idle_timer()
    {
        cancel_delay( idle_timer )
        idle_timer = delay( g_secs ) { self.idle_timer_exceeded() }
    }
    
    func idle_timer_exceeded()
    {
        
        // You can broadcast notification here and use an observer to trigger the UI update function
        reset_idle_timer()
    }
}

您可以查看此帖子的来源 here

,

要在达到超时时更新视图,您可以执行以下操作:

import SwiftUI

@main
struct TimeoutApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
    struct ContentView: View {
    @State var thingToUpdate = "tap the screen to rest the timer"
    @State var timeRemaining = 5
    let timer = Timer.publish(every: 1,on: .main,in: .common).autoconnect()
    
    var body: some View {
        VStack (spacing: 30) {
            Text("\(thingToUpdate)")
            Text("\(timeRemaining)")
                .onReceive(timer) { _ in
                    if timeRemaining > 0 {
                        timeRemaining -= 1
                    } else {
                        thingToUpdate = "refreshed,tap the screen to rest the timer"
                    }
                }
        }
        .frame(minWidth: 0,idealWidth: .infinity,maxWidth: .infinity,minHeight: 0,idealHeight: .infinity,maxHeight: .infinity,alignment: .center)
        .contentShape(Rectangle())
        .onTapGesture {
            thingToUpdate = "tap the screen to rest the timer"
            timeRemaining = 5
        }
    }
}
,

这是我使用“环境”的“全局”计时器代码。虽然它对我有用,但似乎很多 工作只是为了做一些简单的事情。这让我相信一定有 一个更好的方法来做到这一点。不管怎样,也许你可以在这里回收一些想法。

import SwiftUI

@main
struct TestApp: App {
    @StateObject var globalTimer = MyGlobalTimer()
    var body: some Scene {
        WindowGroup {
            ContentView().environment(\.globalTimerKey,globalTimer)
        }
    }
}

struct MyGlobalTimerKey: EnvironmentKey {
    static let defaultValue = MyGlobalTimer()
}

extension EnvironmentValues {
    var globalTimerKey: MyGlobalTimer {
        get { return self[MyGlobalTimerKey] }
        set { self[MyGlobalTimerKey] = newValue }
    }
}

class MyGlobalTimer: ObservableObject {
    @Published var timeRemaining: Int = 5
    var timer = Timer.publish(every: 1,in: .common).autoconnect()
    
    func reset(_ newValue: Int = 5) {
        timeRemaining = newValue
    }
}

struct ContentView: View {
    @Environment(\.globalTimerKey) var globalTimer
    @State var refresh = true
    
    var body: some View {
        GlobalTimerView {  // <-- this puts the content into a tapable view
            // the content
            VStack {
                Text(String(globalTimer.timeRemaining))
                    .accentColor(refresh ? .black :.black) // <-- trick to refresh the view
                    .onReceive(globalTimer.timer) { _ in
                        if globalTimer.timeRemaining > 0 {
                            globalTimer.timeRemaining -= 1
                            refresh.toggle() // <-- trick to refresh the view
                            print("----> time remaining: \(globalTimer.timeRemaining)")
                        } else {
                            // do something at every time step after the countdown
                            print("----> do something ")
                        }
                    }
            }
        }
    }
}

struct GlobalTimerView<Content: View>: View {
    @Environment(\.globalTimerKey) var globalTimer
    let content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        ZStack {
            content
            Color(white: 1.0,opacity: 0.001)
                .frame(minWidth: 0,alignment: .center)
                .contentShape(Rectangle())
                .onTapGesture {
                    globalTimer.reset()
                }
        }.onAppear {
            globalTimer.reset()
        }
    }
}

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...