How to implement screen lock in swift? - ios

I am trying to implement app lock on my iOS app written with swift 5 and using storyboard to create UI. I need to implement feature such that when user enables screen lock in settings of my app , the app lock activates and without biometrics success user cannot view app content , else app should work normally.
I have already implemented the authentication code using the following tutorial.
And I have implemented the same in SceneDelegate as follows :
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
debugPrint("Did become active")
self.biometricIDAuth.canEvaluate { (canEvaluate, _, canEvaluateError) in
guard canEvaluate else {
// Face ID/Touch ID may not be available or configured
return
}
self.biometricIDAuth.evaluate { [weak self] (success, error) in
guard success else {
// Face ID/Touch ID may not be configured
return
}
// You are successfully verified
debugPrint("Success biometric : \(success)")
}
}
}
where biometricIDAuth is class name. Currently the code only shows the authentication UI when entering foreground after several minutes in background . how to implement it such that every time the user comes into foreground the UI for the same shows and if possible how to blur the content when default authentication ui shows.

This is used for faceid or touch id
import LocalAuthentication
func imageTapped() {
// Your action
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error){
let reason = "Identify Yourself!."
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { [weak self] (success,authError) in
DispatchQueue.main.async {
if success {
// This is success faceId succeed to identify user.
// do your work here
} else {
// biometric is avaliable but could not be verified.
// try to use custom password. or whatever you like.
}
}
}
} else {
// there is neither touch id or face id.
}
}
when user is going to background use notification center to pop to authenticationVC.
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(
self, selector: #selector(popToAuthVC),
name: UIApplication.willResignActiveNotification, object: nil
)
make sure your info.plist has this
source

Related

How to detect if In-App purchases process is in progress?

Is it possible in Swift to detect if user is purchasing something? The process usually takes about 15-20 seconds and shows 3-4 different alerts(alert typing password for Apple ID, for confirming purchase, for information if it is successfully purchased or not etc.).
My problem is showing ads(OpenAd) whenever the app is about to become active, so it is really bad user experience to see ads when he tries to buy premium account to remove ads...
This is the part of code where I present them(AppDelegate):
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
tryToPresentAd()
}
And here are the methods that do the presenting(Also written in AppDelegate):
func tryToPresentAd() {
let ad: GADAppOpenAd? = self.appOpenAd
self.appOpenAd = nil
if ad != nil {
guard let rootController = self.window?.rootViewController else { return }
ad?.present(fromRootViewController: rootController)
} else {
requestAppOpenAd()
}
}
func requestAppOpenAd() {
self.appOpenAd = nil
GADAppOpenAd.load(withAdUnitID: Bundle.getValue(forKey: "xyzID"), request: GADRequest(), orientation: .portrait) { (ad, error) in
if error != nil {
debugPrint("Failed to load app open ad", error as Any)
} else {
self.appOpenAd = ad
}
}
}
Is there any way to detect it? Maybe like putting some kind of flags or something, or maybe Apple have some built-in way to detect it? Thanks.
Perhaps you could try to create a bool which would turn to true whenever an user decides to purchase something, and check if that bool is false in order to tryToPresentAd().

LocalAuthentication issue

I have an app where the user can authenticate either with TouchID / FaceID (if Available, enrolled and enabled) or with passcode. All those options can be set in the settings of the app and are stored in UserDefaults. Once the app loads, it checks if those Bool keys have true value in UserDefaults and act accordingly.
My problem comes when a user has a device with TouchID/FaceID but they haven't enabled & enrolled it. In this case, the app should show passcode screen only. But instead, I'm presented by TouchID on my iPhone, when I have disabled the option (for testing purposes). According to Apple's documentation, it says:
If Touch ID or Face ID is available, enrolled, and not disabled, the user is asked for that first. Otherwise, they are asked to enter the device passcode.
On a simulator, I see the Passcode screen, but on my iPhone, I see the TouchID pop up when it's disabled and UserDefaults returns false for that key. Why is that happening? What am I doing wrong?
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
func setUI() {
let faceTouchIdState = UserDefaults.standard.bool(forKey: DefaultsKeys.faceTouchIdState)
let passcodeState = UserDefaults.standard.bool(forKey: DefaultsKeys.passcodeState)
if faceTouchIdState {
print("Authenticate")
authenticate()
}
else {
print("Passscode")
showEnterPasscode()
}
}
func showEnterPasscode() {
let context = LAContext()
var errMess: NSError?
let policy = LAPolicy.deviceOwnerAuthentication
if context.canEvaluatePolicy(policy, error: &errMess) {
context.evaluatePolicy(policy, localizedReason: "Please authenticate to unlock the app.") { [unowned self] (success, err) in
DispatchQueue.main.async {
if success && err == nil {
self.performSegue(withIdentifier: "GoToTabbar", sender: nil)
}
else {
print(err?.localizedDescription)
}
}
}
}
else {
print("cannot evaluate")
}
}
There is nothing wrong with your code. I believe the issue is on how you did this "I have disabled the option (for testing purposes)".
Since you were prompted with the "Touch ID" pop up, then it proves that your biometric wasn't really disabled. I surmise that you toggled one of the "USE TOUCH ID FOR" switches and thought doing so will disable biometric in your app, which it won't.
If you want to test the fallback to passcode in your device:
try unenrolling all fingerprints
OR
disable your fingerprint via inputting unenrolled fingerprints on
your app multiple times.

UI won't display when entering my app from a VOIP call received in the background?

I'm having a little confusion in regards to showing my UI when opening the app from an incoming background video call. I am successfully causing iOS to summon the default "incoming video call" interface when the app is in the background, but after the call is answered, my app isn't being woken up properly?
When I receive the call push, I setup a CXProvider and notify CallKit of an incoming call:
func handleIncomingCallFromBackground() {
//These properties are parsed from the push payload before this method is triggered
guard let callingUser = callingUser, roomName != nil else {
print("Unexpected nil caller and roomname (background)")
return
}
let callHandleTitle = "\(callingUser.first_name) \(callingUser.surname)"
let configuration = CXProviderConfiguration.default
let callKitProvider = CXProvider(configuration: configuration)
callKitProvider.setDelegate(self, queue: nil)
let callHandle = CXHandle(type: .generic, value: callHandleTitle)
self.callHandle = callHandle
let callUpdate = CXCallUpdate.default
callUpdate.remoteHandle = callHandle
let callUUID = UUID()
self.callUUID = callUUID
callKitProvider.reportNewIncomingCall(with: callUUID, update: callUpdate) { error in
if error != nil {
self.resetTwilioObjects()
}
}
}
I respond to the answer call delegate method for the CXProvider, in which I get an access token for the video call from the server, send a response to the server to alert the caller that we've accepted the call, and perform a segue to our own video call controller (that's all jobsVC.showVideoCallVC() does) which handles connecting the call through a Twilio room etc. Code below.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let customer = callingUser, let callHandle = self.callHandle, let uuid = callUUID, let roomName = roomName else {
resetTwilioObjects()
return
}
self.twilioAPI = TwilioAPI()
self.twilioAPI!.accessToken(room: roomName) { (success, accessToken) in
switch success{
case true:
guard let token = accessToken else {
return
}
let customerID = customer.userID
DispatchQueue.global().asyncAfter(deadline: .now() , execute: {
self.twilioAPI!.postResponse(customerID: customerID, status: 1) { (success) in
if let success = success, !success {
self.resetTwilioObjects()
}
}
})
guard let jobsVC = P_ChildHomeJobsVC.instance else {
print("Jobs VC unexpectedly nil")
return
}
//All this method does is perform a segue, the parameters are stored for later.
jobsVC.showVideoCallVC(callingUser: customer, callHandle: callHandle, callKitProvider: provider, callUUID: uuid, twilioAccessToken: token, roomName: roomName)
action.fulfill()
default:
action.fail()
self.resetTwilioObjects()
}
}
}
Depending on whether or not the device is locked, I get differing behaviour:
If the device is locked, upon hitting my app icon to open the app, I get the latest app screenshot show up with a green bar at the top instead of the UI.
If the device is unlocked, upon hitting my app icon to open the app nothing happens at all - it stays on the iOS interface.
According to my logs, the segue is actually being performed correctly and the inner workings of the video call controller are even being fired, but shortly after I get the applicationWillResignActive: delegate call and everything stops.
What's odd (or maybe not) is that if the device is locked whilst the app is still in the foreground, everything works as expected: the app is correctly woken up and the updated UI is shown. I noticed that I still get the applicationWillResignActive: call, but immediately get applicationDidBecomeActive: after that.
Does anyone have any suggestions or hints as to what I might be doing wrong?

Swift Firebase Delete An user when app will terminate

Im trying to Delete user when app will terminate but this does not work.
If I have an anonymous user signed in and the user close the app I don't want firebase to save the anonymous user. I want to delete it. This is my current code. Thank you in advance
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
if let users = Auth.auth().currentUser {
if !users.isAnonymous {
return
}else {
users.delete(completion: { (error) in
if error != nil {
print(error!)
return
}
try! Auth.auth().signOut()
print("Anonymous user deleted")
})
}
}
}
I added listerners in the ViewDidLoad to the screen where I want the user the be deleted from. Like this:
NotificationCenter.default.addObserver(self, selector: #selector(suspending), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(suspending), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
And then I have this function to delete the user:
#objc func suspending () {
if Auth.auth().currentUser != nil {
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
print("An error happened delting the user.")
} else {
print("Account succesfully deleted.")
}
}
}
}
The only thing that could happen is, that you need to re-authenticate. If that's the case, than follow this answer to implement it as well
Hei #pprevalon, I've been trying to achieve the same thing as you, just by updating a value on appWillTerminate, but here's what I found out from other members
Your implementation of this method has approximately five seconds to perform any tasks and return. If the method does not return before time expires, the system may kill the process altogether.
Still trying to figure out a way, since it happens 1/10 times for the func appWillTerminate to be called, but I cannot count on that.
P.S. Besides that, think about this : If the user switches from active -> background at first, followed by background -> kill app, the method will never get called.

AVPlayer audioSessionGotInterrupted notification when waking up from background

I use AVAudioPlayer to play audio. I have background audio enabled and the audio sessions are configured correctly.
I implemented the audioSessionGotInterrupted method to be informed if the audio session gets interrupted. This is my current code:
#objc private func audioSessionGotInterrupted(note: NSNotification) {
guard let userInfo = note.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
print("interrupted")
// Interruption began, take appropriate actions
player.pause()
saveCurrentPlayerPosition()
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options == .shouldResume {
print("restored")
// Interruption Ended - playback should resume
setupPlayer()
player.play()
} else {
// Interruption Ended - playback should NOT resume
// just keep the player paused
}
}
}
}
Now I do the following:
Play some audio
Lock the phone
Pause the audio
Wait for some seconds until I see in the XCode debugger that the app has been stopped in background
I hit play in the lockscreen
My commandCenter play() methods gets called as expected. However also the audioSessionGotInterrupted method gets called with type == .began.
How is that possible? I expect to see no notification of that kind or at least .ended
I use iOS 10 beta 8.
Check this
https://developer.apple.com/documentation/avfoundation/avaudiosession/1616596-interruptionnotification
Starting in iOS 10, the system will deactivate the audio session of most apps in response to the app process being suspended. When the app starts running again, it will receive an interruption notification that its audio session has been deactivated by the system. This notification is necessarily delayed in time because it can only be delivered once the app is running again. If your app's audio session was suspended for this reason, the userInfo dictionary will contain the AVAudioSessionInterruptionWasSuspendedKey key with a value of true.
If your audio session is configured to be non-mixable (the default behavior for the playback, playAndRecord, soloAmbient, and multiRoute categories), it's recommended that you deactivate your audio session if you're not actively using audio when you go into the background. Doing so will avoid having your audio session deactivated by the system (and receiving this somewhat confusing notification).
if let reason = AVAudioSessionInterruptionType(rawValue: reasonType as! UInt) {
switch reason {
case .began:
var shouldPause = true
if #available(iOS 10.3, *) {
if let _ = notification.userInfo?[AVAudioSessionInterruptionWasSuspendedKey] {
shouldPause = false
}
}
if shouldPause {
self.pause()
}
break
case .ended:
break
}
}
While the answer above is not wrong it still caused a lot of trouble in my app and a lot of boilerplate code for checking multiple cases.
If you read the description of AVAudioSessionInterruptionWasSuspendedKey it says that the notification is thrown if you didn't deactivate your audio session before your app was sent to the background (which happens every time you lock the screen)
To solve this issue you simply have to deactivate your session if there is no sound playing when app is sent to background and activate it back if the sound is playing. After that you will not receive the AVAudioSessionInterruptionWasSuspendedKey notification.
NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: .main) { sender in
guard self.player.isPlaying == false else { return }
self.setSession(active: false)
}
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { sender in
guard self.player.isPlaying else { return }
self.setSession(active: true)
}
func setSession(active: Bool) -> Bool {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, mode: .default)
try session.setActive(active)
return true
} catch let error {
print("*** Failed to activate audio session: \(error.localizedDescription)")
return false
}
}
Note: Activating session is probably not necessary because it is handled by Apple's internal playback classes (like AVPlayer for example) but it is a good practice to do it manually.

Resources