Xcode 11.4
Swift 5.0
CheckPhoneLockState Project on GitHub
I know it is an edge case but sometimes it is really necessary to know the device lock state especially when we are working with background services like Bluetooth or networking. Actually, iOS API does not let developers check if the device is locked or unlocked programmatically. But there is a workaround that lets us find it out with some seconds of delay after locking.
isProtectedDataAvailable is a property in UIApplication that says if the files are protected after locking the device. It is a good clue to check the phone state but there are two points.
Firstly, when the device is locked, the property’s value changes from false to true with a delay of around 10 seconds.
Secondly, as its a property of UIApplication, it must be called on the main thread. Almost whenever we need to check the state of the phone, the code execution has been started from a background thread. So we should care about threading. It can be solved easily, for example, by a closure.
To begin with, let’s define an enumeration for phone state:
enum DeviceLockState {
case locked
case unlocked
}
Then creating a Utility class with a class function encapsulating the state checking code that accepts a closure to run in the main thread. Be careful that running heavy codes on the main thread can have negative impacts on UI responsiveness.
import UIKit
class Utility {
class func checkDeviceLockState(completion: @escaping (DeviceLockState) -> Void) {
DispatchQueue.main.async {
if UIApplication.shared.isProtectedDataAvailable {
completion(.unlocked)
} else {
completion(.locked)
}
}
}
}
Now we are going to test that function but testing this function needs more code than the function itself. To do so, I add a button that starts a dispatchQueue and asks it to check the state of the phone after 15 seconds. The exciting point here is that, this code starts by tapping the button and it has not fired by a background service, so it will not execute after locking the phone. Indeed wherever the phone is locked, the app execution is suspended. Thus, the dispatchQueue code will not run after locking the phone and we can not test our function simply.
To solve that, Before running the DispatchQueue I asks the iOS to let me run in the background a little longer by calling the beginBackgroundTask(). Just note that this duration should not take longer than 180 seconds and when my work is finished, I should tell the OS that my job is over and I do not need more time in the background by calling endBackgroundTask() method. I also need to to pass the identifier that has been retrieved from beginBackgroundTask() function. That is very easy. Just look at the code. It explains everything by far, better than me.
var backgroundTaskID: UIBackgroundTaskIdentifier!
@IBAction func actionButtonTapped(_ sender: Any) {
backgroundTaskID = UIApplication.shared.beginBackgroundTask()
DispatchQueue.global().asyncAfter(deadline: .now() + 15) {[weak self] in
Utility.checkDeviceLockState() {
lockState in
if let self = self {
self.showPhoneState(lockState)
UIApplication.shared.endBackgroundTask(self.backgroundTaskID)
}
}
}
}
func showPhoneState(_ deviceLockState: DeviceLockState) {
switch deviceLockState {
case .locked:
print("locked")
case .unlocked:
print("Unlocked")
}
}
Now click the button and lock the device. After 15 seconds, it checks the lock state and prints it in the Xcode output window for you. You should replace the print commands with any codes that should be executed regarding the phone lock state.
Xcode 11.4
Swift 5.0
CheckPhoneLockState Project on GitHub