Cần thiết cho: auto-logout security, ẩn UI controls khi xem video, screensaver, power saving...
Giải pháp: Timer + UITapGestureRecognizer
Bước 1: Tạo dự án
Xcode → Single View App → Tên: "DetectingInactivity".
Bước 2: Code ViewController.swift
import UIKit
class ViewController: UIViewController {
var inactivityTimer: Timer?
let inactivityTimeout: TimeInterval = 5.0 // 5 giây
override func viewDidLoad() {
super.viewDidLoad()
startInactivityTimer()
setupTapGesture()
}
// MARK: - Timer Management
func startInactivityTimer() {
inactivityTimer?.invalidate()
inactivityTimer = Timer.scheduledTimer(timeInterval: inactivityTimeout,
target: self,
selector: #selector(userInactive),
userInfo: nil,
repeats: false) // Chỉ chạy 1 lần
}
func resetInactivityTimer() {
startInactivityTimer() // Reset bằng cách tạo timer mới
}
@objc func userInactive() {
print("👤 User không tương tác > \(inactivityTimeout) giây")
// Thực hiện action: logout, ẩn UI, pause video...
showInactivityAlert()
}
// MARK: - Gesture Recognizer
func setupTapGesture() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(screenTapped))
tapGesture.cancelsTouchesInView = false // Quan trọng: không chặn touch các view con
view.addGestureRecognizer(tapGesture)
}
@objc func screenTapped(_ sender: UITapGestureRecognizer) {
// Chỉ reset khi tap ra ngoài interactive elements
// (button, textfield, slider... tự handle touch của mình)
resetInactivityTimer()
print("🔄 Timer reset do user interaction")
}
// MARK: - UI Feedback
func showInactivityAlert() {
let alert = UIAlertController(title: "Không hoạt động",
message: "Bạn đã không tương tác hơn 5 giây",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
self.resetInactivityTimer() // Reset sau khi userDismiss alert
})
present(alert, animated: true)
}
// Cleanup
deinit {
inactivityTimer?.invalidate()
}
}
Cải tiến: Detect mọi loại interaction (không chỉ tap)
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
resetInactivityTimer()
}
// Hoặc override sendEvent (AppDelegate) cho toàn app
// AppDelegate.swift
var inactivityTimer: Timer?
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) -> Bool {
// ...
setupGlobalInactivityDetection()
return true
}
func setupGlobalInactivityDetection() {
inactivityTimer = Timer.scheduledTimer(timeInterval: 300, // 5 phút
target: self,
selector: #selector(globalInactivity),
userInfo: nil,
repeats: false)
}
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
// Chỉ reset cho touch events
if event.type == .touches {
inactivityTimer?.invalidate()
inactivityTimer = Timer.scheduledTimer(timeInterval: 300,
target: self,
selector: #selector(globalInactivity),
userInfo: nil,
repeats: false)
}
}
@objc func globalInactivity() {
print("Global inactivity 5 phút - Auto logout")
// Post notification để VC handle logout
NotificationCenter.default.post(name: .userInactivityTimeout, object: nil)
}
extension Notification.Name {
static let userInactivityTimeout = Notification.Name("userInactivityTimeout")
}
Lưu ý quan trọng
cancelsTouchesInView = falsecho phép touch truyền xuống button/textfield- Timer
repeats: false(one-shot) hiệu quả hơn repeating timer - Luôn
invalidate()timer cũ trước khi tạo mới - Cleanup trong
deinittránh leak - Đối với app phức tạp: dùng AppDelegate.sendEvent hoặc NotificationCenter cho global detection
Kịch bản ứng dụng thực tế
| Use Case | Timeout | Action |
|---|---|---|
| Banking/Security app | 2-5 phút | Auto logout, clear sensitive data |
| Video player | 3-5 giây | Ẩn controls, hide status bar |
| Kiosk/Public display | 30-60 giây | Reset về màn hình home |
| Reading app | 1-2 phút | Auto-save position, dim screen |