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

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.

Related

AppsFlyer with iOS 14

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.

Game Center Matchmaking Custom Invites

I'm sure theres a 'simple' answer to this, but I've been having a difficult time finding it.
I've created an iOS card game that works in game center. At the moment, it determines the online players automatically and works fine.
What I want to do is have the user invite their game center friends to play. The documentation isn't really helping me.
I'm doing my project in swift, and I feel like I'm dealing with deprecated items and old documentation here?
I've managed to get the users list of friends and then I assume I need to instantiate a matchmaker with that list of friends.
What I'm stuck at, is trying to implement the "RecipientResponseHandler" code as per apple's guidelines:
request.recipientResponseHandler = ^(GKPlayer *player, GKInviteeResponse response)
{
[self updateUIForPlayer: player accepted: (response == GKInviteeResponseAccepted)];
};
Question 1: First of all, What does the ^ symbol represent in the swift language?
Question 2: XCode doesn't seem to like the * symbol in the arguments...I've tried doing things like...
request.recipientResponseHandler = (player: GKPlayer, response: GKInviteeResponse){
print("Do codeStuffHere")
};
But, I can't find a way of wording this that XCode will allow...
Question 3: How can I write this response handler to get it to work?
Question 4: Also,to listen for invites, I need to implement the GKLocalPlayerListener protocol...Anything else?
Question 5: I feel like I'm missing something glaring?
I've been learning on my own, and sometimes it takes a while to understand concepts without any direction, so please go easy on me internet. I'm just starting to understand completion handlers and closures...
Thanks in advance for any help.
What does the ^ symbol represent in the swift language?
XCode doesn't seem to like the * symbol in the arguments.
That's because that code is written in Objective-C, not Swift.
I'm assuming you're looking at the documentation for GKMatchRequest (update as of Jan 2023: It seems Apple has removed the code listing from this page at some point).
If so, then yeah, Apple just hasn't gotten around to updating all their documentation to use Swift instead of Objective-C (even on pages where the language selected is Swift, like this one).
Here's the Swift equivalent of that entire code listing:
func invite(friends: [GKPlayer]) {
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
request.recipients = friends
request.inviteMessage = "Your Custom Invitation Message Here"
request.recipientResponseHandler = { player, response in
self.updateUI(for: player, accepted: response == .accepted)
}
}
func updateUI(for player: GKPlayer, accepted: Bool) {
// update your UI here
}
For question 4, be sure to register the GKLocalPlayerListener and only register once, for example:
GKLocalPlayer.local.register(self)

iOS 11.4 not asking Privacy Usage ( Privacy - Motion Usage Description has been set )

I'm stumped, iOS 11.4 ( 15F79 ), iPhone 6. Cannot get the App to Ask for Motion Data. info.plist has been set via the editor and double checked via the info.plist open in textWrangler, Also deleted key and saved via textWrangler.
<key>NSMotionUsageDescription</key>
<string>This app needs your Phones motion manager to update when the phone is tilted. Please allow this App to use your phones tilt devices</string>
I have deleted then reinstalled the app about 10 times. I have restared the phone 5 times. I have checked through settings and my app does NOT show up in Privacy-Motion and Fitness or anywhere else in settings. I am using a free developer account, maybe that has something to do with it?
I created a new Xcode game template and changed nothing apart from importing CoreMotion and this code
**** Edited, sorry I forgot to say I had started the instance, just forgot to put it here, just in case someone thinks that's the problem ************
let motionManager = CMMotionManager()
override func didMove(to view: SKView) {
motionManager.startDeviceMotionUpdates()
if motionManager.isDeviceMotionActive == true {
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates(to: OperationQueue.current!, withHandler: {
(accelerometerData: CMAccelerometerData!, error: NSError!) in
let acceleration = accelerometerData.acceleration
print(accelerometerData)
} as! CMAccelerometerHandler)
}else{
print(CMMotionActivityManager.authorizationStatus().rawValue)
}
which prints a 0 ( an Enum - case not determined ) to the console.
In my actual app it was a 3 ( same Enum - case Denied ).
As I've said, I have uninstalled, reinstalled, edited plist via Xcode and text wrangler ( a code editor ) , tried different versions of the code above, tried the code in different places ( in did move to view, in class )tried code off apple docs. etc.... I haven't been asked the NSUsage question and the App keeps crashing.
I have looked for ways to get the Alert fired up, As in CLLocationManager.requestWhenInUseAuthorization() but I cannot find a comparable CMMotion version ( I don't think there is one. ) I have created a new swift file , imported Foundation and CMMotion and just put that code there, But still no Alert asking for Motion Data.
I tried a single view app template instead of a game template thinking that might be the issue, Nope.
What do I do?
Any help Appreciated. Thanks
You are confusing two related but different classes.
CMMotionManager gives access to accelerometer, magnetometer and gyroscope data. It does not require any user permission as this information is not considered privacy related.
In your else clause you are checking the authorisation status of CMMotionActivityManager. This object reports the device motion type (walking, running, driving). This information is considered privacy related and when you create an instance of this class and request data from it, the permissions alert is displayed.
The reason your else is being triggered is because you are checking isDeviceMotionActive; this will be false until you call startDeviceMotionUpdates, which you never do. Even if you used isAccelerometerActive you would have a problem because you call startAccelerometerUpdates in the if clause which will never be reached.
You probably meant to check isAccelerometerAvailable. If this returns false then there isn't much you can do; the device doesn't have an accelerometer.
Update
It doesn't make sense to check isDeviceMotionActive immediately after calling startDeviceMotion:
You know it's active; you just started it
I imagine the start up takes some time, so you could expect to get false if you check immediately.
Apple recommends that you do not have more than one observer in place for each motion device type, so the purpose of check the is...Active to ensure you don't call start... again if you have already done so.
If you only want gyroscope data then you don't need to call startDeviceMotionUpdates at all.

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”.

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