Related
I use Firebase to push notifications to the users at a certain time. They receive the notification but no alert sound is played. In the settings, the allow sound/notifications are turned on and other IOS13 and other apps play sound.
Version Number of FirebasePushNotificationPlugin Plugin: 3.3.10
Device Tested On: iphone X, OS: 13.4.1
Simulator Tested On: N/A (simulators don't receive notifications)
Version of VS: VS for Mac Community, 8.6.6 (build 11)
Version of Xamarin: Xamarin.IOS 13.18.2.1, Xamarin.Forms v4.6.0.847
AppDelegate.cs:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
bool fbaseStarted = false;
try
{
// This method does all the UNUserNotificationCenter.Current.RequestAuthorization() code so we don't have to.
FirebasePushNotificationManager.Initialize(options, true);
fbaseStarted = true;
}
catch
{ }
LoadApplication(new App());
if (!fbaseStarted)
{
try
{
FirebasePushNotificationManager.Initialize(options, true);
}
catch { }
}
FirebasePushNotificationManager.CurrentNotificationPresentationOption = UNNotificationPresentationOptions.Badge | UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Sound;
}
Within one of the pages of my code, I subscribe a list of tags (please note that I unsubscribe because the first time the code runs it fails silently if the notifications aren't approved - resulting in the model thinking the notifications was subscribed when it wasn't):
CrossFirebasePushNotification.Current.UnsubscribeAll();
CrossFirebasePushNotification.Current.Subscribe(Constants.NotificationTagsArray);
I keep coming across payload json solutions but unless I am wrong, I don't think that applies to me as I am using Xamarin and the FirebasePushNotificationPlugin. Is there any additional permissions that were added in ios 13 for playing notifications with sound that I have missed?
I have also posted here: https://github.com/CrossGeeks/FirebasePushNotificationPlugin/issues/348 but nobody has been able to assist me yet.
Thanks
The issue actually lies with the sending of the notifications nothing to do with the Xamarin App. The issue resided in the services that sends the notifications to firebase (to then be sent out to the phones).
In the service we were sending a FirebaseNet.Messaging.Message() to the phones:
Message FireBasemessage = new Message()
{
To = "/topics/" + PushNote.Tag,
TimeToLive = 86400,
Priority = MessagePriority.high,
ContentAvailable = true,
Notification = new AndroidNotification()
{
Tag = "/topics/" + PushNote.Tag,
Body = enhancedMessage,
Title = xtitle,
}
,
Data = new Dictionary<string, string>
{
{ "param", PushNote.Tag },
{ "text", enhancedMessage}
}
};
In the AndroidNotification() object required Sound = "default" to be added for it to work. Please note that this works for both Android and IOS notifications despite the fact it is an AndroidNotification object.
I'm currently developing an iOS app that enables users to log in to the app using TouchID, but firstly they must set up a password inside the app first. Problem is, to show the setup password option to enable the TouchID login, I need to detect if the iOS device supports TouchID.
Using the LAContext and canEvaluatePolicy (like the answers in here If Device Supports Touch ID), I am able to determine whether the current device supports TouchID if the user has set up passcode on their iOS device. Here is a my code snippet (I'm using Xamarin, so it's in C#):
static bool DeviceSupportsTouchID ()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
var context = new LAContext();
NSError authError;
bool touchIDSetOnDevice = context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out authError);
return (touchIDSetOnDevice || (LAStatus) Convert.ToInt16(authError.Code) != LAStatus.TouchIDNotAvailable);
}
return false;
}
If the user has not set up the device passcode, the authError will just return "PasscodeNotSet" error regardless of whether the device actually supports TouchID or not.
If the user's device supports TouchID, I want to always show the TouchID option in my app regardless of whether the user has set up passcode on their device (I will just warn the user to setup passcode on their device first). Vice versa, if the user's device doesn't support TouchID, I obviously don't want to show the TouchID option in my app.
So my question is, is there a nice way to consistently determine whether an iOS device supports TouchID regardless of whether the user has set up passcode on their device?
The only workaround I can think of is to determine the architecture of the device (which is answered in Determine if iOS device is 32- or 64-bit), as TouchID is only supported on devices with 64-bit architecture. However, I'm looking if there's any nicer way to do this.
In conclusion of the discussion below, for the time being it is not possible to determine whether a device actually supports TouchID or not when the user hasn't set up passcode on their device.
I have reported this TouchID flaw on the Apple bug reporter. Those who want to follow the issue can see it on Open Radar here: http://www.openradar.me/20342024
Thanks #rckoenes for the input :)
EDIT
Turns out that someone has reported a similar issue already (#18364575). Here is Apple's reply regarding the issue:
"Engineering has determined that this issue behaves as intended based on the following information:
If passcode is not set, you will not be able to detect Touch ID presence. Once the passcode is set, canEvaluatePolicy will eventually return LAErrorTouchIDNotAvailable or LAErrorTouchIdNotEnrolled and you will be able to detect Touch ID presence/state.
If users have disabled passcode on phone with Touch ID, they knew that they will not be able to use Touch ID, so the apps don't need to detect Touch ID presence or promote Touch ID based features. "
So..... the final answer from Apple is No. :(
Note: similar StackOverflow question from the person who reported this -> iOS8 check if device has Touch ID
(wonder why I didn't find this question before despite my extensive searching...)
The correct way to detect if TouchID is available:
BOOL hasTouchID = NO;
// if the LAContext class is available
if ([LAContext class]) {
LAContext *context = [LAContext new];
NSError *error = nil;
hasTouchId = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
}
Sorry it is in Objective-C, you might have to translate it to C#.
You should refrain from checking the system version and just check whether or not the class or methods are available.
I know this is a question from last year, but this solution does not make what you need? (Swift code)
if #available(iOS 8.0, *) {
var error: NSError?
let hasTouchID = LAContext().canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error)
//Show the touch id option if the device has touch id hardware feature (even if the passcode is not set or touch id is not enrolled)
if(hasTouchID || (error?.code != LAError.TouchIDNotAvailable.rawValue)) {
touchIDContentView.hidden = false
}
}
Then, when the user presses the button to log in with touch id:
#IBAction func loginWithTouchId() {
let context = LAContext()
var error: NSError?
let reasonString = "Log in with Touch ID"
if (context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error)) {
[context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success: Bool, evalPolicyError: NSError?) -> Void in
//Has touch id. Treat the success boolean
})]
} else {
//Then, if the user has touch id but is not enrolled or the passcode is not set, show a alert message
switch error!.code{
case LAError.TouchIDNotEnrolled.rawValue:
//Show alert message to inform that touch id is not enrolled
break
case LAError.PasscodeNotSet.rawValue:
//Show alert message to inform that passcode is not set
break
default:
// The LAError.TouchIDNotAvailable case.
// Will not catch here, because if not available, the option will not visible
}
}
}
Hope it helps!
For Objective C
It works great on all devices without checking device version.
- (void)canAuthenticatedByTouchID{
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = touchIDRequestReason;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
}else{
switch (authError.code) {
case kLAErrorTouchIDNotAvailable:
[labelNotSupportTouchID setHidden:NO];
[switchBtn setHidden:YES];
[labelEnableTouchid setHidden:YES];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self showAlertMessage:#"EyeCheck Pro" message:#"Device does not support Touch ID Service."];
});
break;
}
}
}
Here is a bit tedious way to figure out if device has physical touch id sensor.
+ (BOOL)isTouchIDExist {
if(![LAContext class]) //Since this mandotory class is not there, that means there is no physical touch id.
return false;
//Get the current device model name
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *model = malloc(size);
sysctlbyname("hw.machine", model, &size, NULL, 0);
NSString *deviceModel = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
//Devices that does not support touch id
NSArray *deviceModelsWithoutTouchID = [[NSArray alloc]
initWithObjects:
#"iPhone1,1", //iPhone
#"iPhone1,2", //iPhone 3G
#"iPhone2,1", //iPhone 3GS
#"iPhone3,1", //iPhone 4
#"iPhone3,2",
#"iPhone3,3",
#"iPhone4,1", //iPhone 4S
#"iPhone5,1", //iPhone 5
#"iPhone5,2",
#"iPhone5,3", //iPhone 5C
#"iPhone5,4",
#"iPod1,1", //iPod
#"iPod2,1",
#"iPod3,1",
#"iPod4,1",
#"iPod5,1",
#"iPod7,1",
#"iPad1,1", //iPad
#"iPad2,1", //iPad 2
#"iPad2,2",
#"iPad2,3",
#"iPad2,4",// iPad mini 1G
#"iPad2,5",
#"iPad2,5",
#"iPad2,7",
#"iPad3,1", //iPad 3
#"iPad3,2",
#"iPad3,3",
#"iPad3,4", //iPad 4
#"iPad3,5",
#"iPad3,6",
#"iPad4,1", //iPad Air
#"iPad4,2",
#"iPad4,3",
#"iPad4,4", //iPad mini 2
#"iPad4,5",
#"iPad4,6",
#"iPad4,7",
nil];
return ![deviceModelsWithoutTouchID containsObject:deviceModel];
}
Reference:
https://www.theiphonewiki.com/wiki/Models
https://en.wikipedia.org/wiki/IOS
Following is the way by which you can identify whether Touch Id or Face ID is supported on the device
open class LocalAuth: NSObject {
public static let shared = LocalAuth()
private override init() {}
var laContext = LAContext()
func canAuthenticate() -> Bool {
var error: NSError?
let hasTouchId = laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
return hasTouchId
}
func hasTouchId() -> Bool {
if canAuthenticate() && laContext.biometryType == .touchID {
return true
}
return false
}
func hasFaceId() -> Bool {
if canAuthenticate() && laContext.biometryType == .faceID {
return true
}
return false
}
}
And following is the Usage of the above-shared code
if LocalAuth.shared.hasTouchId() {
print("Has Touch Id")
} else if LocalAuth.shared.hasFaceId() {
print("Has Face Id")
} else {
print("Device does not have Biometric Authentication Method")
}
For iOS 11+ you can use biometryType: LABiometryType of LAContext. More from Apple documentation:
/// Indicates the type of the biometry supported by the device.
///
/// #discussion This property is set only when canEvaluatePolicy succeeds for a biometric policy.
/// The default value is LABiometryTypeNone.
#available(iOS 11.0, *)
open var biometryType: LABiometryType { get }
#available(iOS 11.0, *)
public enum LABiometryType : Int {
/// The device does not support biometry.
#available(iOS 11.2, *)
case none
/// The device does not support biometry.
#available(iOS, introduced: 11.0, deprecated: 11.2, renamed: "LABiometryType.none")
public static var LABiometryNone: LABiometryType { get }
/// The device supports Touch ID.
case touchID
/// The device supports Face ID.
case faceID
}
For iOS 11+, for context error, you can check for kLAErrorBiometryNotAvailable
I have an iOS app that makes some small network requests on app launch (resource updates, etc). If the user turns off cellular access for the app in iOS Settings, they get a prompt from iOS about network usage every time they launch. Is there a way to know programmatically that cellular data for this app has been disabled, so that I can disable the requests at startup?
So I found this on the apple dev forums from an Apple engineer (https://devforums.apple.com/message/1059332#1059332).
Another developer wrote in to DTS and thus I had a chance to
investigate this in depth. Alas, the news is much as I expected:
there is no supported way to detect that your app is in this state.
Nor is there a way to make a "no user interaction" network connection,
that is, request that the connection fail rather than present UI like
this. If these limitations are causing problems for your app, I
encourage you to file a bug describing your specific requirements.
https://developer.apple.com/bug-reporting/
So it looks like it is not possible to detect if cellular data for your app has been turned off.
Edit
I filed a radar for this requesting that it be added. I just got this notification in my radar
We believe this issue has been addressed in the latest iOS 9 beta.
I looked through the API diffs, but so far I can't find the new API.
As of iOS9, the capability to check the setting to enable/disable use of cellular data for your app (Settings/Cellular/AppName) is available using Apple's CTCellularData class. The following code will set cellularDataRestrictedState when it is run initially and then set it and log whenever it changes:
import CoreTelephony
var cellularDataRestrictedState = CTCellularDataRestrictedState.restrictedStateUnknown
let cellState = CTCellularData.init()
cellState.cellularDataRestrictionDidUpdateNotifier = { (dataRestrictedState) in
if cellularDataRestrictedState != .restrictedStateUnknown { // State has changed - log to console
print("cellularDataRestrictedState: " + "\(dataRestrictedState == .restrictedStateUnknown ? "unknown" : dataRestrictedState == .restricted ? "restricted" : "not restricted")")
}
cellularDataRestrictedState = dataRestrictedState
}
Unfortunately (as of iOS11) this seems to check only the state of the app's switch - if your app's switch is set to enabled and the user switches the Cellular Data master switch to disabled, this API will return the app's state as being "not restricted".
Just wanted to add an Objective C version of the above Swift code for future travellers.
- (void)monitorCanUseCellularData {
if (GCIsiOS9) {
CTCellularData *cellularData = [[CTCellularData alloc] init];
NSLog(#"%ld", cellularData.restrictedState);
// 0, kCTCellularDataRestrictedStateUnknown
[cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
NSLog(#"%ld", state);
self.canUseCellularData = cellularData.restrictedState ==2?true:false;
}];
}
}
I have found that the CTCellularData class needs some time to get to the correct value. In my implementation I call the didUpdateNotifier very early after appDidFinishLaunching. By the time my networking call are returning with errors I definitely have a correct value for the restricted state.
class CellularRestriction: NSObject {
private static var cellularData = CTCellularData()
private static var currentState = CTCellularDataRestrictedState.restrictedStateUnknown
static var isRestricted: Bool {
currentState = cellularData.restrictedState
return currentState == .restricted
}
static func prepare() {
if currentState == .restrictedStateUnknown {
cellularData.cellularDataRestrictionDidUpdateNotifier = { state in
currentState = cellularData.restrictedState // This value may be inconsistent, however the next read of isRestricted should be correct.
}
}
}
}
You can detect if cellular data disabled using NWPathMonitor class. (https://developer.apple.com/documentation/network/nwpathmonitor)
let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
cellMonitor.pathUpdateHandler = { path in
self.isCellConnected = path.status == .satisfied
}
Adding to dirkgroten's answer, you can use the Apple Reachability class, found here:
https://developer.apple.com/Library/ios/samplecode/Reachability/Introduction/Intro.html
It uses SCNetworkReachability, and is very straight forward to use, it will detect connectivity via Cell and WiFi as you will need to check both at start up.
There are lots of frameworks out there that will give you the status of your network connectivity, and of course you can roll your own. I've found AFNetworking to be one of the best. It has a singleton class called AFNetworkReachabilityManager that abstracts some of the complexities for you. Specifically you'll want to look at the two boolean properties:
reachableViaWWAN
reachableViaWiFi
There is also a reachability changed status block that you can set:
– setReachabilityStatusChangeBlock:
AFNetworking Github
AFNetworkReachabilityManager
I was wondering if there is a method that would allow me to detect from the keyboard container app whether the associated keyboard has been activated in the the device's Settings app.
For example, I am interested in adding a simple "steps" feature inside the container app where step 1 would be "activate the keyboard", and step 2 would be contingent on step 1's completion. As such, I am interested in figuring out whether there is a way to detect whether the keyboard extension is activated?
Thanks!
Here is a method I have used in one of my projects. I think it is what you asked for, hope it helps you.
- (BOOL)isCustomKeyboardEnabled {
NSString *bundleID = #"com.company.app.customkeyboard"; // Replace this string with your custom keyboard's bundle ID
NSArray *keyboards = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] objectForKey:#"AppleKeyboards"]; // Array of all active keyboards
for (NSString *keyboard in keyboards) {
if ([keyboard isEqualToString:bundleID])
return YES;
}
return NO;
}
Just in case here is Swift version of Kurt's brilliant and awesome answer:
func isKeyboardExtensionEnabled() -> Bool {
guard let appBundleIdentifier = Bundle.main.bundleIdentifier else {
fatalError("isKeyboardExtensionEnabled(): Cannot retrieve bundle identifier.")
}
guard let keyboards = UserDefaults.standard.dictionaryRepresentation()["AppleKeyboards"] as? [String] else {
// There is no key `AppleKeyboards` in NSUserDefaults. That happens sometimes.
return false
}
let keyboardExtensionBundleIdentifierPrefix = appBundleIdentifier + "."
for keyboard in keyboards {
if keyboard.hasPrefix(keyboardExtensionBundleIdentifierPrefix) {
return true
}
}
return false
}
The current documentation states By default, your extension and its containing app have no direct access to each other’s containers.
It is also stating that the container app can share data with the keyboard in the following fashion:
// Create and share access to an NSUserDefaults object.
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc]
initWithSuiteName:#"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account.
[mySharedDefaults setObject:theAccountName forKey:#"lastAccountName"];
Read more on this: Communicating and persisting data between apps with App Groups
Obstacle no 1: According to the documentation, for this to work, the RequestsOpenAccess in the plist needs to be set to YES as it would gain the following capability:
Option to use a shared container with the keyboard’s containing app,
which enables features such as providing a custom lexicon management
UI in the containing app
Requesting full access for a simple case like this is definitely not preferred on my side.
Obstacle no 2: Using this knowledge of setting a NSUserDefault, leaves me to think of a method where this can be set in place. But there's no public method indicating an extension is installed. So this is a dead end for now.
--
[Update 1]
Not super relevant but still worth stating: the shouldAllowExtensionPointIdentifier app delegate method in combination with the constant UIApplicationKeyboardExtensionPointIdentifier can deal with disallowing custom keyboards. The extension point identifiers are not unique identifiers of the extension but of their type.
Read more on this: Can I disable custom keyboards (iOS8) for my app?
--
[Update 2]
Another question with same issue, but w/o solution: How to detect an app extension is enabled in containing app on iOS 8?
--
This is a work-in-progress answer stating my findings so far which I hope to be updating coming days should I find a solution.
You can use this function (Swift 3 and 4) to check your custom keyboard extension have open access or not:
func isOpenAccessGranted() -> Bool{
if #available(iOS 10.0, *) {
let originalString = UIPasteboard.general.string
UIPasteboard.general.string = "Sour LeangChhean"
if UIPasteboard.general.hasStrings {
UIPasteboard.general.string = originalString ?? ""
return true
}else{
UIPasteboard.general.string = ""
return false
}
} else {
// Fallback on earlier versions
if UIPasteboard.general.isKind(of: UIPasteboard.self) {
return true
}else{
return false
}
}
}
I have an iOS app that makes some small network requests on app launch (resource updates, etc). If the user turns off cellular access for the app in iOS Settings, they get a prompt from iOS about network usage every time they launch. Is there a way to know programmatically that cellular data for this app has been disabled, so that I can disable the requests at startup?
So I found this on the apple dev forums from an Apple engineer (https://devforums.apple.com/message/1059332#1059332).
Another developer wrote in to DTS and thus I had a chance to
investigate this in depth. Alas, the news is much as I expected:
there is no supported way to detect that your app is in this state.
Nor is there a way to make a "no user interaction" network connection,
that is, request that the connection fail rather than present UI like
this. If these limitations are causing problems for your app, I
encourage you to file a bug describing your specific requirements.
https://developer.apple.com/bug-reporting/
So it looks like it is not possible to detect if cellular data for your app has been turned off.
Edit
I filed a radar for this requesting that it be added. I just got this notification in my radar
We believe this issue has been addressed in the latest iOS 9 beta.
I looked through the API diffs, but so far I can't find the new API.
As of iOS9, the capability to check the setting to enable/disable use of cellular data for your app (Settings/Cellular/AppName) is available using Apple's CTCellularData class. The following code will set cellularDataRestrictedState when it is run initially and then set it and log whenever it changes:
import CoreTelephony
var cellularDataRestrictedState = CTCellularDataRestrictedState.restrictedStateUnknown
let cellState = CTCellularData.init()
cellState.cellularDataRestrictionDidUpdateNotifier = { (dataRestrictedState) in
if cellularDataRestrictedState != .restrictedStateUnknown { // State has changed - log to console
print("cellularDataRestrictedState: " + "\(dataRestrictedState == .restrictedStateUnknown ? "unknown" : dataRestrictedState == .restricted ? "restricted" : "not restricted")")
}
cellularDataRestrictedState = dataRestrictedState
}
Unfortunately (as of iOS11) this seems to check only the state of the app's switch - if your app's switch is set to enabled and the user switches the Cellular Data master switch to disabled, this API will return the app's state as being "not restricted".
Just wanted to add an Objective C version of the above Swift code for future travellers.
- (void)monitorCanUseCellularData {
if (GCIsiOS9) {
CTCellularData *cellularData = [[CTCellularData alloc] init];
NSLog(#"%ld", cellularData.restrictedState);
// 0, kCTCellularDataRestrictedStateUnknown
[cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
NSLog(#"%ld", state);
self.canUseCellularData = cellularData.restrictedState ==2?true:false;
}];
}
}
I have found that the CTCellularData class needs some time to get to the correct value. In my implementation I call the didUpdateNotifier very early after appDidFinishLaunching. By the time my networking call are returning with errors I definitely have a correct value for the restricted state.
class CellularRestriction: NSObject {
private static var cellularData = CTCellularData()
private static var currentState = CTCellularDataRestrictedState.restrictedStateUnknown
static var isRestricted: Bool {
currentState = cellularData.restrictedState
return currentState == .restricted
}
static func prepare() {
if currentState == .restrictedStateUnknown {
cellularData.cellularDataRestrictionDidUpdateNotifier = { state in
currentState = cellularData.restrictedState // This value may be inconsistent, however the next read of isRestricted should be correct.
}
}
}
}
You can detect if cellular data disabled using NWPathMonitor class. (https://developer.apple.com/documentation/network/nwpathmonitor)
let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
cellMonitor.pathUpdateHandler = { path in
self.isCellConnected = path.status == .satisfied
}
Adding to dirkgroten's answer, you can use the Apple Reachability class, found here:
https://developer.apple.com/Library/ios/samplecode/Reachability/Introduction/Intro.html
It uses SCNetworkReachability, and is very straight forward to use, it will detect connectivity via Cell and WiFi as you will need to check both at start up.
There are lots of frameworks out there that will give you the status of your network connectivity, and of course you can roll your own. I've found AFNetworking to be one of the best. It has a singleton class called AFNetworkReachabilityManager that abstracts some of the complexities for you. Specifically you'll want to look at the two boolean properties:
reachableViaWWAN
reachableViaWiFi
There is also a reachability changed status block that you can set:
– setReachabilityStatusChangeBlock:
AFNetworking Github
AFNetworkReachabilityManager