問題
iOS 7 では、自身のカスタムスキームを呼び出した時、
UIApplicationWillResignActiveNotification / UIApplicationDidBecomeActiveNotification が呼び出されない・通知されない。
ルーティングを実装して遷移するために呼び出したり、WebView で認証させてる場合に実装によっては影響することがあるのではなかろうかと思ってます。
自身を呼び出す (iOS 7)
-[AppDelegate application:openURL:sourceApplication:annotation:] [Line 28] url: openurl://, app: (null), state: 0, annotation: (null)
自身を呼び出す (iOS 8 later)
-[AppDelegate applicationWillResignActive:] [Line 36]
-[AppDelegate application:openURL:sourceApplication:annotation:] [Line 28] url: openurl://, app: example.OpenURLTest, state: 1, annotation: (null)
-[AppDelegate applicationDidBecomeActive:] [Line 52]
他アプリケーションが呼び出す (iOS 7)
-[AppDelegate applicationWillEnterForeground:] [Line 47]
-[AppDelegate application:openURL:sourceApplication:annotation:] [Line 28] url: openurl://, app: com.apple.mobilesafari, state: 1, annotation: (null)
-[AppDelegate applicationDidBecomeActive:] [Line 52]
他アプリケーションが呼び出す (iOS 8 later)
-[AppDelegate applicationWillEnterForeground:] [Line 47]
-[AppDelegate application:openURL:sourceApplication:annotation:] [Line 28] url: openurl://, app: com.apple.mobilesafari, state: 1, annotation: (null)
-[AppDelegate applicationDidBecomeActive:] [Line 52]
対応
Method Swizzling で、iOS 8 以降と同じ処理になるよう UIApplicationWillResignActiveNotification / UIApplicationDidBecomeActiveNotification を補完してみました。
AppDelegate.m
+ (void)initialize
{
[super initialize];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (NSOrderedAscending == [[UIDevice currentDevice].systemVersion compare:@"8.0"
options:NSNumericSearch]) {
Method fromMethod = class_getInstanceMethod([self class],
@selector(application:openURL:sourceApplication:annotation:));
Method toMethod = class_getInstanceMethod([self class],
@selector(iOS7_application:openURL:sourceApplication:annotation:));
method_exchangeImplementations(fromMethod, toMethod);
}
});
}
/**
iOS 7 では自身のスキームを呼び出した際に UIApplicationWillResignActiveNotification / UIApplicationDidBecomeActiveNotification が呼び出されないため補完する
*/
- (BOOL)iOS7_application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
BOOL result = NO;
BOOL oneself = NO;
NSString *bundleIdentifier = sourceApplication;
// 自身のスキーム呼び出し時、sourceApplication が nil になっている
if (!sourceApplication || 0 == [sourceApplication length]) {
oneself = YES;
bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
}
// UIApplicationWillResignActiveNotification
if(oneself) {
// perform delegate
if([self respondsToSelector:@selector(applicationWillResignActive:)]) {
[self applicationWillResignActive:application];
}
// post notification
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillResignActiveNotification
object:nil];
}
// perform original
result = [self iOS7_application:application
openURL:url
sourceApplication:bundleIdentifier
annotation:annotation];
// UIApplicationDidBecomeActiveNotification
if (oneself) {
// perform delegate
if([self respondsToSelector:@selector(applicationDidBecomeActive:)]) {
[self applicationDidBecomeActive:application];
}
// post notification
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification
object:nil];
}
return result;
}
課題
application.applicationState が iOS 8 以降とは異なる
| iOS バージョン | applicationState |
|---|---|
| iOS 7 | UIApplicationStateActive |
| iOS 8 later | UIApplicationStateInactive |
自身を呼び出した時なので気にしなくていいはず…。