This is an update to this outdates question: Alternate Icon in iOS 10.3: avoid notification dialog for icon change
func setAppIcon(Type: String) {
if #available(iOS 10.3, *) {
UIApplication.shared.setAlternateIconName(Type)
}
}
With the few lines above it is possible to change the Appicon dynamically, the feature was added with iOS 10.3.
The code above is working fine but every time the app icon changed iOS triggers an alert like this:
So is there a way to get rid of this alert? (I know that apple could reject application for disabling user-information but I'd like to use it just for test purposes)
Any help would be SUPER appreciated, thanks! :-)
Try the following code, however, it is written with Objective-c. It makes use of private method, I guess that you will never mind.
- (void)lc_setAlternateIconName:(NSString*)iconName
{
//anti apple private method call analyse
if ([[UIApplication sharedApplication] respondsToSelector:#selector(supportsAlternateIcons)] &&
[[UIApplication sharedApplication] supportsAlternateIcons])
{
NSMutableString *selectorString = [[NSMutableString alloc] initWithCapacity:40];
[selectorString appendString:#"_setAlternate"];
[selectorString appendString:#"IconName:"];
[selectorString appendString:#"completionHandler:"];
SEL selector = NSSelectorFromString(selectorString);
IMP imp = [[UIApplication sharedApplication] methodForSelector:selector];
void (*func)(id, SEL, id, id) = (void *)imp;
if (func)
{
func([UIApplication sharedApplication], selector, iconName, ^(NSError * _Nullable error) {});
}
}
}
Related
I'm working on a iOS SDK that other apps will integrate. If the application where my code is running is linked against the AdSupport.framework I would like to use the IDFA for install attribution. Otherwise not.
When I use the following code ,will Apple reject the application?
+ (NSString *)appleIDFA {
NSString *idfa = nil;
Class ASIdentifierManagerClass = NSClassFromString(#"ASIdentifierManager");
if (ASIdentifierManagerClass) {
SEL sharedManagerSelector = NSSelectorFromString(#"sharedManager");
id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector);
SEL advertisingIdentifierSelector = NSSelectorFromString(#"advertisingIdentifier");
NSUUID *advertisingIdentifier = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
idfa = [advertisingIdentifier UUIDString];
}
return idfa;
}
I ended up using those solution and did not have problems with it.
My problem is that I can't find out if a certain URL can be opened from an iOS widget.
The method canOpenURL: is not available on today's widget because there is no UIApplication class.
Moreover the method openURL: of NSExtensionContext returns YES for the boolean "success", even with an invalid URL.
The code below enters the else condition (success BOOL is always YES) but in the same time the simulator shows a popup error, as you can seen in the attached image.
NSURL* invalidURL = [NSURL URLWithString:#"fake://blablabla"];
[self.extensionContext openURL:invalidURL completionHandler:^(BOOL success) {
if (success == NO) {
DDLogWarn(#"Can't open URL: %#", invalidURL);
}
else{
DDLogInfo(#"Successfully opened URL: %#",invalidURL);
}
}];
It's a known bug. I filed this issue with Apple last year (rdar://18107612) when iOS 8.0b5 was current, and it's still an open issue.
File your own bug with Apple at http://bugreport.apple.com and hope for the best.
You can get to the shared UIApplication instance by using performSelector:, something like
UIApplication *sharedApplication = [[UIApplication class] performSelector:NSSelectorFromString(#"sharedApplication")];
I Added ios-8's new touchID API to my app.
It usually works as expected, BUT when entering app while my finger is already on home-button - API's success callback is called but pop-up still appears on screen. after pressing CANCEL UI becomes non-responsive.
I also encountered the same issue, and the solution was to invoke the call to the Touch ID API using a high priority queue, as well as a delay:
// Touch ID must be called with a high priority queue, otherwise it might fail.
// Also, a dispatch_after is required, otherwise we might receive "Pending UI mechanism already set."
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.75 * NSEC_PER_SEC), highPriorityQueue, ^{
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
// Check if device supports TouchID
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// TouchID supported, show it to user
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Unlock Using Touch ID"
reply:^(BOOL success, NSError *error) {
if (success) {
// This action has to be on main thread and must be synchronous
dispatch_async(dispatch_get_main_queue(), ^{
...
});
}
else if (error) {
...
}
}];
}
});
When testing our app, we found a delay of 750ms to be optimal, but your mileage may vary.
Update (03/10/2015): Several iOS developers, like 1Password for example, are reporting that iOS 8.2 have finally fixed this issue.
Whilst using a delay can potentially address the issue, it masks the root cause. You need to ensure you only show the Touch ID dialog when the Application State is Active. If you display it immediately during the launch process (meaning the Application is still technically in an inactive state), then these sorts of display issues can occur. This isn't documented, and I found this out the hard way. Providing a delay seems to fix it because you're application is in an active state by then, but this isn't guarenteed.
To ensure it runs when the application is active, you can check the current application state, and either run it immediately, or when we receive the applicationDidBecomeActive notification. See below for an example:
- (void)setup
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// We need to be in an active state for Touch ID to play nice
// If we're not, defer the presentation until we are
if([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
{
[self presentTouchID];
}
else
{
__weak __typeof(self) wSelf = self;
_onActiveBlock = ^{
[wSelf presentTouchID];
};
}
}
-(void)applicationDidBecomeActive:(NSNotification *)notif
{
if(_onActiveBlock)
{
_onActiveBlock();
_onActiveBlock = nil;
}
}
- (void)presentTouchID
{
_context = [[LAContext alloc] init];
_context.localizedFallbackTitle = _fallbackTitle;
[_context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:_reason
reply: ^(BOOL success, NSError *authenticationError)
{
// Handle response here
}];
}
This accepted answer does not address the underlying cause of the problem: invoking evaluatePolicy() twice, the second time while the first invocation is in progress. So the current solution only works sometimes by luck, as everything is timing dependent.
The brute-force, straightforward way to work around the problem is a simple boolean flag to prevent subsequent calls from happening until the first completes.
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
if ( NSClassFromString(#"LAContext") && ! delegate.touchIDInProgress ) {
delegate.touchIDInProgress = YES;
LAContext *localAuthenticationContext = [[LAContext alloc] init];
__autoreleasing NSError *authenticationError;
if ([localAuthenticationContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authenticationError]) {
[localAuthenticationContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:kTouchIDReason reply:^(BOOL success, NSError *error) {
delegate.touchIDInProgress = NO;
if (success) {
...
} else {
...
}
}];
}
I started getting the "Pending UI mechanism already set." error mentioned as well, so I decided to see if other apps were affected. I have both Dropbox and Mint set up for Touch ID. Sure enough Touch ID wasn't working for them either and they were falling back to passcodes.
I rebooted my phone and it started working again, so it would seem the Touch ID can bug out and stop working. I'm on iOS 8.2 btw.
I guess the proper way to handle this condition is like those apps do and fallback to password / passcode.
I am creating a update method for in house enterprise apps. What I am trying to create is a class that I can quickly put in a app and it will check with the server if it needs to be updated. This is what I have so far, it checks correctly but its returning NO before it finishes the method.
In my checkUpdate.h
#interface checkForUpdate : NSObject
+ (BOOL)updateCheck;
#end
In my checkUpdate.m
#import "checkForUpdate.h"
#implementation checkForUpdate
BOOL needsUpdate
NSDictionary *versionDict;
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
+ (BOOL)updateCheck {
NSString *urlStringVersion = [[NSString alloc] initWithFormat:#"http://URL/app_info?app=app"];
NSURL *urlVersion = [NSURL URLWithString:urlStringVersion];
dispatch_async(kBgQueue, ^{
NSData* data =[NSData dataWithContentsOfURL:urlVersion];
if (data){
NSError* error;
NSArray* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (json != nil && [json count] != 0) {
versionDict = [json objectAtIndex:0];
CGFloat serverVersion = [[versionDict valueForKey:#"version"]floatValue];
CGFloat appVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleVersionKey] floatValue];
NSLog(#"Server Version %f",serverVersion);
NSLog(#"App Version %f",appVersion);
if ([versionDict count] != 0){
if (serverVersion > appVersion){
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:1];
needsUpdate = YES;
}else{
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
needsUpdate = NO;
}
}
}
}
});
return needsUpdate;
}
#end
I call it like this
NSLog(#"Needs Update %#",[checkForUpdate checkForUpdateWithResponse] ? #"Yes":#"No");
This is my log
Needs Update No
Server Version 2.000000
App Version 1.000000
I'm not sure why it's returning NO before it even checks. I need it to be a asynchronous because the server that the app will check with is behind our firewall. So if the person is outside the firewall the app needs to continue when is can't reach the server. I'm I headed in the right direction, or is there a better way?
You are asynchronously checking for an update but expecting an immediate response by virtue of your method's design. You can re-engineer your method to something like the example below to notify a handler whenever the operation is complete:
Note: Unchecked and untested for errors; however, the lesson to be gleaned from the example is to use a callback of sorts:
UpdateChecker Class
typedef void (^onComplete)(BOOL requiresUpdate);
#interface UpdateChecker : NSObject
-(void)checkForUpdates:(onComplete)completionHandler;
#end
#implementation UpdateChecker
-(void)checkForUpdates:(onComplete)completionHandler
{
NSString *urlStringVersion = [[NSString alloc] initWithFormat:#"http://URL/app_info?app=app"];
NSURL *urlVersion = [NSURL URLWithString:urlStringVersion];
dispatch_block_t executionBlock =
^{
/*
Your update checking script here
(Use the same logic you are currently using to retrieve the data using the url)
*/
NSData* data = [NSData dataWithContentsOfURL:urlVersion];
BOOL requiresUpdate = NO;
if (data)
{
...
...
...
requiresUpdate = ...; //<-whatever your outcome
}
//Then when completed, notify the handler (this is our callback)
//Note: I typically call the handler on the main thread, but is not required.
//Suit to taste.
dispatch_async(dispatch_get_main_queue(),
^{
if (completionHandler!=NULL)
completionHandler(requiresUpdate);
});
};
dispatch_async(kBgQueue, executionBlock);
}
#end
This is what it would look when you use UpdateChecker to check for updates throughout your app
UpdateChecker *checker = [UpdateChecker alloc] init];
[checker checkForUpdates:^(BOOL requiresUpdate)
{
if (requiresUpdate)
{
//Do something if your app requires update
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:1];
}
else
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}];
Since dispatch_async is non-blocking, your method returns before your update information has returned (does the dispatch and continues). As needsUpdate defaults to NO, that's what you'll see. You can see this in your log timing - the "Needs Update No" shows up before the server and app versions.
You need some sort of callback (a delegate method or second dispatch_async for example) to ensure you get the correct result, or you need to block. I recommend looking into NSURLConnection, and sendAsynchronousRequest:queue:completionHandler: - it will execute the completion handler on completion, where you can have whatever code you need for handling the update.
I have two scenarios here.
One is, my App is active in the background on my iPad. If I go to safari and click a link with my URL scheme, the App opens and displays an alert with the URL.
This is what I want!
Second scenario is when the App is inactive, and not in the background. Here the App launches, but the alert is never displayed. I can alert the URL out from "didFinishLaunchingWithOptions", but I need it in my JavaScript function: handleOpenURL(url).
It seems to me that handleOpenURL in my AppDelegate.m is only fired when the App is in the background. Is there any way to make it do the same while not running in the background?
Here is my obj-c handleOpenUrl:
if (!url) { return NO; }
// calls into javascript global function 'handleOpenURL'
NSString* jsString = [NSString stringWithFormat:#"window.setTimeout(function(){ handleOpenURL(\"%#\"); }, 1)", url];
[self.viewController.webView stringByEvaluatingJavaScriptFromString:jsString];
// all plugins will get the notification, and their handlers will be called
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
return YES;
It should output to this javascript function:
function handleOpenURL(url) {
alert('invoke: ' + url);
}
Now when the App starts initially, it runs didFinishLaunchingWithOptions:
NSURL* url = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey];
NSString* invokeString = nil;
if (url) {
invokeString = [url absoluteString];
NSLog(#"iPaperReeder launchOptions = %#", url);
}
self.viewController.invokeString = invokeString;
Should I modify the didFinishLaunchingWithOptions method, to make it run handleOpenURL?
You can easily sort out this problem.
In "CDVHandleOpenURL.m" file you need to change code as below
NSString* jsString = [NSString stringWithFormat:#"document.addEventListener('deviceready',function(){if (typeof handleOpenURL === 'function') { handleOpenURL(\"%#\");}});", url];
To
NSString* jsString = [NSString stringWithFormat:#"if (typeof handleOpenURL === 'function') { handleOpenURL(\"%#\");} else { window._savedOpenURL = \"%#\"; }", url, url];
this will work perfectly.
Best of luck
I figured it out. I was missing a call in the onDeviceReady function:
if (invokeString) handleOpenURL(invokeString);