AppsFlyer with iOS 14 - ios

I'm working on an existing project and haven't used AppsFlyer before.
in older version of AppsFlyer we have initialized it using these lines in AppDelegate
AppsFlyerTracker.shared().appsFlyerDevKey = appsflyerKey
AppsFlyerTracker.shared().appleAppID = appId
AppsFlyerTracker.shared().trackAppLaunch()
And Track events using
AppsFlyerTracker.shared().trackEvent("Started", withValues: prop)
But in the latest version of AppsFlyer the initial class name has changed from
AppsFlyerTracker -> AppsFlyerLib
// now event is logged by
AppsFlyerLib.shared().logEvent("Started", withValues: prop)
So I have two questions
As according to guide lines in iOS 14, we need to add permission for user to accept it before any tracking. Is it applies to these AppsFlyers logEvent event too ?
if we need to add permission then adding these lines will fill the purpose ?
AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60)
ATTrackingManager.requestTrackingAuthorization { (status) in
}
I didn't find alternate for AppsFlyerTracker.shared().trackAppLaunch() in the latest AppsFlyerLib

if user in above iOS 14 you must need to add these condition
based on your question :
As according to guide lines in iOS 14, we need to add permission for user to accept it before any tracking. Is it applies to these AppsFlyers logEvent event too ?
Ans : YES
if we need to add permission then adding these lines will fill the purpose ?
Ans : YES
initially you need to add the framework App Tracking Transparency
// The following block is optional for applications wishing to give users the option to block IDFA collection.
// for iOS 14 and above - The user may be prompted to block IDFA collection.
// If user opts-out, the IDFA will not be collected by the SDK.
// for iOS 13 and below - The IDFA will be collected by the SDK. The user will NOT be prompted to block collection.
if #available(iOS 14, *) {
// Set a timeout for the SDK to wait for the IDFA collection before handling app launch
// If timeout expires before user asks to block IDFA collection, the IDFA will be collected.
AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60)
// Show the user the Apple IDFA consent dialog (AppTrackingTransparency)
// MUST be called here before start() in order to prevent IDFA collection by the SDK
ATTrackingManager.requestTrackingAuthorization { (status) in
}
}
the above completion handler prompts the following two points
The completion handler will be called with the result of the user's decision for granting or denying permission to use application tracking.
The completion handler will be called immediately if access to request authorization is restricted.
and your final question
I didn't find alternate for AppsFlyerTracker.shared().trackAppLaunch() in the latest AppsFlyerLib
Ans :
func applicationDidBecomeActive(_ application: UIApplication) {
// Start the SDK (start the IDFA timeout set above, for iOS 14 or later)
if #available(iOS 14, *) {
AppsFlyerLib.shared().start()
}else{
AppsFlyerTracker.shared().trackAppLaunch()
}
}
you can get the sample project provided by AppsFlyer's team.

Related

App Clip - Support Multiple Businesses Locations

When I designed my App Clip Launch experience, I had in mind that the App can only be triggered via QR code, NFC or App Clip Code. That why I linked the App Launch to a specific location with specific Id.
When my App went live last week, and when I try to scan a NFC tag the App is launching as expected every time.
Now, if I tap the App Clip icon on the home screen, the App is launching with the last URL scanned I dig some googling and I found that the App Clip is caching the last URL scanned and simulating a universal link launch when icon tapped!
This is not working for me! So I am looking for a way to check if the App was launched via scan or tap? I tried to log the App launch but it's always running in the order either via Scan (NFC) or icon tap:
AppDelegate.didFinishLaunchingWithOptions()
SceneDelegate.willConnectTo() // It's here where I am handling the Universal Link
How can I check if the user launched the App via Tap or Scan? Knowing that the App is always simulating Universal launch Link when icon tapped!
Or how I can look for the saved URL? I tried to fetch all UserDefaults and Some Keychain data, but I found nothing!
I faced the same issue! And unfortunately there’s no way to:
Check how the App was launched, icon tap or NFC/QR scan
To retrieve cached data from either UserDefaults or Keychain
Apple says clearly on their Human Interface Guidelines that if you want support multiple businesses you should add the location services factor!
Consider multiple businesses. An App Clip may power many different
businesses or a business that has multiple locations. In both
scenarios, people may end up using the App Clip for more than one
business or location at a time. The App Clip must handle this use case
and update its user interface accordingly. For example, consider a way
to switch between recent businesses or locations within your App Clip,
and verify the user’s location when they launch it.
So, now your tags for specific location should be mapped to a coordinates [Longitude, Latitude]. Apple has introduced a new location verification API just for App Clips that allows you to do a one-time check to see if the App Clip code, NFC tag or QR code that the user scanned is where it says it is.
Enable Your App Clip to Verify the User’s Location
To enable your App Clip to verify the user’s location, modify your App Clip’s Info.plist file:
Open your App Clip’s Info.plist, add the NSAppClip key, and set its
type to Dictionary.
Add an entry to the dictionary with NSAppClipRequestLocationConfirmation as the key, select Boolean as
its type, and set its value to true.
But using App Clip Location services is different:
Parse the information on the URL that launches the App CLip
Send a request to your Database to fetch the location information for this business
Use activity.appClipActivationPayload to confirm if the location (in Step 2) is in region where the user is right now.
The Code bellow (Copied from Apple) explains how to do it.
import UIKit
import AppClip
import CoreLocation
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Call the verifyUserLocation(_:) function in all applicable life-cycle callbacks.
func verifyUserLocation(_ activity: NSUserActivity?) {
// Guard against faulty data.
guard activity != nil else { return }
guard activity!.activityType == NSUserActivityTypeBrowsingWeb else { return }
guard let payload = activity!.appClipActivationPayload else { return }
guard let incomingURL = activity?.webpageURL else { return }
// Create a CLRegion object.
guard let region = location(from: incomingURL) else {
// Respond to parsing errors here.
return
}
// Verify that the invocation happened at the expected location.
payload.confirmAcquired(in: region) { (inRegion, error) in
guard let confirmationError = error as? APActivationPayloadError else {
if inRegion {
// The location of the NFC tag matches the user's location.
} else {
// The location of the NFC tag doesn't match the records;
// for example, if someone moved the NFC tag.
}
return
}
if confirmationError.code == .doesNotMatch {
// The scanned URL wasn't registered for the App Clip.
} else {
// The user denied location access, or the source of the
// App Clip’s invocation wasn’t an NFC tag or visual code.
}
}
}
func location(from url:URL) -> CLRegion? {
// You should retrieve the coordinates from your Database
let coordinates = CLLocationCoordinate2D(latitude: 37.334722,
longitude: 122.008889)
return CLCircularRegion(center: coordinates,
radius: 100,
identifier: "Apple Park")
}
}
And that’s it, this his how your support multiple businesses with App Clip

User Notification Center get Authorization Options Swift 3/4 ios10/11

For iOS lower than 10 we used to have the property to get the types (.alert, .badge etc) of notifications User allowed. We used this code:
UIApplication.shared.currentUserNotificationSettings?.types
But it's now deprecated.
Question:
How can we do the same but using UNUserNotificationCenter for ios10/11?
Is there an equivalent method?
Consider: the deprecated way still works but we never know if one day Apple will take it down.
Thanks in advance!
You can still get the notification settings by:
UNUserNotificationCenter.current().getNotificationSettings { settings in
if settings.alertSetting == .enabled {
//alert is enabled
}
}
As it's mentioned in apple doc
When the value of this property is UNNotificationSetting.enabled, the
app is authorized to display alerts.

Disabling Callkit from China Store Best Approach?

We are using CallKit framework to benefit native usage for Voip features. Users can make Voice and Video Calls in our Messenger App.
But Apple removing CallKit apps from China, because of Chinese government.
What is the best approach for CallKit apps like us for now?
We do not want to remove our app from China and we do not remove all CallKit functionality from our app because of China..
I agree with txulu that it seems that CallKit just needs to be disabled/not used for users in China - see this helpful response on the Apple Developer forums.
The general consensus seems to be that as long as you can explain to App Review how you’re disabling CallKit features for users in China, that should probably be acceptable unless/until Apple publishes specific guidelines.
For your particular problem Ahmet, it sounds like CallKit may provide some of the the core functionality of your app. If this is the case and you really need to support users in China, you might want to look at rebuilding your app using another VOIP framework to make calls (VOIP is still allowed in China...just not using CallKit). Or perhaps you could disable and hide the calling features in your app if the user is in China.
My app was only using CallKit to observe when a call initiated from my app ends, so I was able to devise a work around. For users in China I now observe for the UIApplicationDidBecomeActiveNotification and make my best guess about whether a phone call initiated from the app has ended based on how much time has elapsed since the call began. It's not as good as using CallKit's CXCallObserver, but it seems to work well enough for my purpose.
Update! My app passed App Store review with the fix described.
Submitted a new version yesterday.
Included a short message in the reviewer info section saying "In this version and onwards, we do not use CallKit features for users in China. We detect the user's region using NSLocale."
App was approved around 12hr later without any questions or comments from the App Review team.
Detecting users in China
To determine if a user is in China, I am using NSLocale to get the users' currentLocale and countryCode. If the countryCode contains one of the ISO codes for China (CN, CHN), I set a flag to note I cannot use CallKit and not initialize or use CallKit features in my app.
- (void)viewDidLoad {
[super viewDidLoad];
NSLocale *userLocale = [NSLocale currentLocale];
if ([userLocale.countryCode containsString: #"CN"] || [userLocale.countryCode containsString: #"CHN"]) {
NSLog(#"currentLocale is China so we cannot use CallKit.");
self.cannotUseCallKit = YES;
} else {
self.cannotUseCallKit = NO;
// setup CallKit observer
self.callObserver = [[CXCallObserver alloc] init];
[self.callObserver setDelegate:self queue:nil];
}
}
To test this, you can change the region in Settings > General > Language and Region > Region. When I set Region to 'China' but left language set as English, [NSLocale currentLocale] returned "en_CN".
Swift 5
Utility Functions
func isCallKitSupported() -> Bool {
let userLocale = NSLocale.current
guard let regionCode = userLocale.regionCode else { return false }
if regionCode.contains("CN") ||
regionCode.contains("CHN") {
return false
} else {
return true
}
}
MainViewController
class MainViewController: UIViewController {
...
var callObserver = CXCallObserver()
...
override func viewDidLoad() {
super.viewDidLoad()
if isCallKitSupported() {
callObserver.setDelegate(self, queue: nil)
}
...
}
...
}
Note: countryCode is now regionCode and only returns 'US', 'CN', etc. No language before country code like 'en_CN'.
Swift 5
func isCallKitSupport() -> Bool {
let userLocale = NSLocale.current
if userLocale.regionCode?.contains("CN") != nil ||
userLocale.regionCode?.contains("CHN") != nil {
return false
} else {
return true
}
}
One thing you could try, even though it may not work: disable callkit functionality based on the locale region. This may be enough "proof" that Callkit is disabled for China from the legal perspective in order to be approved for the Appstore. Then your Chinese customers could just switch the region in the settings to get Callkit. This would be already "their" problem so to speak.
Disclaimer: I'm by no means a lawyer or anything, follow this advice at your own risk.
Edit:
CXProvider.isSupported is no longer available: I keep the answer here hoping that it will be restored back on an upcoming iOS 13 release.
From iOS 13 onwards, the correct way to do this is to check the new CXProvider.isSupported property.
Here's the documentation (from Xcode, as the online documentation has not been updated yet):
Go to “Pricing and Availability” in iTunes Connect.
Availability” (Click blue button Edit).
Deselect China in the list “Deselect” button.
Click “Done”.

What makes a user discoverable to CKDiscoverAllUserIdentitiesOperation?

I am trying to discover contacts for a user with the code below (the code is in an implementation of a UITableViewController. I put breakpoints in both code blocks, and I determined that the the userIdentityDiscoveredBlock is not called while the completionBlock is called. This indicates that the operation is being run as expected, it just isn't finding any contacts.
I am running on the simulator, but I verified that the simulator has synced all my iCloud contacts (opening the Contacts app on the simulator shows all my contacts).
override func viewDidLoad() {
super.viewDidLoad()
let op = CKDiscoverAllUserIdentitiesOperation()
op.discoverAllUserIdentitiesCompletionBlock = { error -> Void in
// reload my data table
}
op.userIdentityDiscoveredBlock = { user -> Void in
if user.hasiCloudAccount {
self.iCloudUsers.append(user)
} else {
self.nonICloudUsers.append(user)
}
}
CKContainer.default().add(op)
}
So my question is this - Is there something else that has to be done in order to discover contacts? Is this a simulator issue?
I searched the documentation and other questions but I can't seem to find information on this given the operation is new to iOS 10.
There's quite a bit that must be done before CKDiscoverAllUserIdentitiesOperation will return any results.
First, each user of your app must grant permission to be looked up by email. Your app makes this request using CKContainer requestApplicationPermission.
Each user of your app must also be logged into an iCloud account. iCloud Drive must also be enabled by the user.
And lastly, for CKDiscoverAllUserIdentitiesOperation to return any users, the person must have contacts with email addresses that match other users that completed all of the previous steps.

How to test if "Allow Full Access" permission is granted from containing app?

I'm working on a keyboard extension project. At some points of the application code I need to test if the user have granted the "Allow Full Access" permission for the keyboard extension. The deal is that I need to do those tests from the application side, and based on this let the user to access keyboard settings or alert him in case the permission wasn't granted.
The problem is that the methods that provided here like:
func isOpenAccessGranted() -> Bool {
return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}
or:
func isOpenAccessGranted() -> Bool {
let fm = NSFileManager.defaultManager()
let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
"group.com.example")?.path
var error: NSError?
fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
if (error != nil) {
NSLog("Full Access: Off")
return false
}
NSLog("Full Access: On");
return true
}
Working only from the keyboard side, as the keyboard is the only one that is affected from this permission. From the containing app side both of those methods always return true.
Does someone knows a reliable way to test this from the application side?
Consider using NSUSerdefaults in your app and keyboard. In order for an extension and app to be able to share the same NSUserdefaults, you can simply turn on App Groups. Select your main app target under your project in the project navigator and go to capabilities and enable App Groups by toggling it to on, adding your Developer profile and fixing the possibly arising issues.
Now create a new container. According to the help, it must start with “group.”, so give it a name like “group.com.mycompany.myapp”. Select your Today Extension target and repeat this process of switching on app groups. Don’t create a new one, rather select this newly created group to signify that the Today Extension is a part of the group.
This will now allow you to share the same NSUserdefaults as your container app.
All you have to do now is use the code you provided and add the saving method to the NSUserdefaults like so:
func isOpenAccessGranted() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
let fm = NSFileManager.defaultManager()
let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
"group.com.example")?.path
var error: NSError?
fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
if (error != nil) {
NSLog("Full Access: Off")
defaults.setBool(false, forKey: "hasFullAccess")
return false
}
NSLog("Full Access: On");
defaults.setBool(true, forKey: "hasFullAccess")
return true
}
Do this in your keyboard extension. And then in your parent app, simply retrieve them and act upon their value.
let hasFullAccess : Bool = NSUserDefaults.standardUserDefaults().boolForKey("hasFullAccess")
if hasFullAccess{
//User granted full access
}
else{
//User didn't grant full access
}
If the user didn't grant full access the show an alert, else code away!
EDIT:
After contacting Apple's Technical Developer support they told me that this is not possible to achieve in any supported way as of right now. Their response is below:
Our engineers have reviewed your request and have concluded that there
is no supported way to achieve the desired functionality given the
currently shipping system configurations.
If you would like for Apple to consider adding support for such
features in the future, please submit an enhancement request via the
Bug Reporter tool at https://developer.apple.com/bug-reporting/.
Hope that helps, Julian
You can easily test whether the "Allow Full Access" permission is granted on iOS 11 and later.
To get the "Allow Full Access" permission, first subclass the
UIInputViewController class.
Add the code. The code returns a bool value.
Objective-C
[self hasFullAccess];
Swift
self.hasFullAccess
https://developer.apple.com/documentation/uikit/uiinputviewcontroller/2875763-hasfullaccess?changes=_2
Objective-C
[UIInputViewController new].hasFullAccess;

Resources