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

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().

Related

How to implement screen lock in swift?

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

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.

Pop up ad works, but not once I implement in-app purchase to remove it?

The test ad works fine when I don't include the in app purchase. I click on the cell, it takes me to the next view controller, and the ad pops up. However, when I include in-app purchases the ad doesn't show up even if the user didn't pay to remove ads.
The ad shows up with this function:
func showAd() {
self.interstitial = createInterstitialAd()
}
But when I add this, the ad doesn't show even if the user hasn't paid to remove ads.
func showAd() {
if let purchased = UserDefaults.standard.value(forKey: "payment") as? Bool{
if purchased == true{
interstitial = nil
print("there is no ad!!!!")
}else{
self.interstitial = createInterstitialAd()
print("there is an ad!!!")
}
}
Your problem is that initially there will be no value in UserDefaults for the payment key. This will cause the outer if statement to fall through, resulting in no ad.
You can make your code simpler by using bool(forKey:) - This will return false where the key is not present in UserDefaults rather than nil:
func showAd() {
if UserDefaults.standard.bool(forKey: "payment") {
interstitial = nil
print("there is no ad!!!!")
} else {
self.interstitial = createInterstitialAd()
print("there is an ad!!!")
}
}

Continue handling receipts after log in

I implemented auto renew subscriptions in my app. By default app tries to use production URL for handling receipts, if code returns 27001, app will use sandbox URL. So if I run app from XCode and sandbox account is specified in the iPhone Setting (iTunes/AppStore), app tries at second attempt to load product details for sandbox and does it successfully. Because product array is not empty, I am able to buy a subscription after confirmation prompt. It looks like code working as expected but not in all conditions.
App Review Team does the following: they log off from real and sandbox account in the device settings. In this case if they run app, app shows prompt to log in, because in the ViewController app checks for status of subscriptions. They press Cancel. Next they go to BuySubscriptionViewController and press Cancel again. So at this moment product array is empty and it is not possible to buy a product.
I added the following condition:
#IBAction func buySub(_ sender: Any) {
if (product_mysub != nil) {
StoreManager.shared.buy(product: product_mysub)
} else {
requestData()
}
}
So if a product wasn't found I am asking user to log in again if he tries to buy it.
func requestData() {
let receiptManager: ReceiptManager = ReceiptManager()
receiptManager.startValidatingReceipts()
}
from ReceiptManager.swift:
func startValidatingReceipts() {
do {
_ = try self.getReceiptURL()?.checkResourceIsReachable()
do {
let receiptData = try Data(contentsOf: self.getReceiptURL()!)
self.validateData(data: receiptData)
print("Receipt exists")
} catch {
print("Not able to get data from URL")
}
} catch {
guard UserDefaults.standard.bool(forKey: "didRefreshReceipt") == false else {
print("Stopping after second attempt")
return
}
UserDefaults.standard.set(true, forKey: "didRefreshReceipt")
let receiptRequest = SKReceiptRefreshRequest()
receiptRequest.delegate = self
receiptRequest.start()
print("Receipt URL Doesn't exist", error.localizedDescription)
}
}
If users taps on a button again, app doesn't ask him to log in again and gets data about products. If user taps third time on the button, app shows confirmation prompt to buy a product. How can I continue purchase flow after entering email and password without asking user to press the button 2 times yet?
extension ReceiptManager: SKRequestDelegate {
func requestDidFinish(_ request: SKRequest) {
// this func is not executed after entering account details in the prompt and pressing OK
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error refreshing receipt", error.localizedDescription)
}
}

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.

Resources