既存アプリの 
iOS8対応 
Mao Nishi
今日話すこと 
• 今回のiOS8対応範囲 
• ヤフオク!アプリで起きた問題 
• Extension Today対応 
• 掛かった工数 
• ユーザの反響
ヤフオク!アプリについて
ヤフオク!アプリについて 
• iPhone版 
• 2010年10月リリース(当時はiOS4.1) 
• コード上でUI部品を生成している箇所多数 
• iPad版 
• 2013年12月リリース 
• xib、storyboardは当然活用
今回のiOS8対応範囲について
ヤフオク!アプリの 
iOS8対応の範囲 
iOS8での正常動 
作を目指す 
iOS8独自機能 
(Extentionなどを 
搭載) 
iPhone6/iPhone6 
Plus向けにレイア 
ウトする 
iPhone/iPad 対応済み! 対応済み! これから
ヤフオク!アプリの 
iOS8対応の範囲 
iOS8での正常動 
作を目指す 
iOS8独自機能 
(Extentionなどを 
搭載) 
iPhone6/iPhone6 
Plus向けにレイア 
ウトする 
iPhone/iPad 対応済み! 対応済み! これから
iOS8対応時に出会った事象・ 
不具合等を紹介します
これからiOS8対応にあたられる 
方の参考になればと思います
CASE 1 
回転時にレイアウトが崩れる
とりあえずビルドして 
動かしてみた
期待する動き
予期しない動き
回せば回すほど.. 
レイアウトが崩れていく事態に
原因 
[[UIScreen mainScreen] applicationFrame];
原因 
CGRect appFrame = [[UIScreen mainScreen] applicationFrame]; 
/* 以下はiOS8からは端末の向きによって返却される値が変わるようになった*/ 
CGFloat height = appFrame.size.height; 
CGFloat width = appFrame.size.width;
iOS7でのheightとwidth 
height 
width 
width height 
長い方がheightという 
前提でも成り立つ
iOS8でのheightとwidth 
height 
height 
width width 
長い方がheightという 
前提でコードを 
書いてしまっていた
端末の向きにってheight、width 
に変化があるメソッド 
• [[UIScreen mainScreen] applicationFrame]; 
• [[UIScreen mainScreen] bounds]; 
• [[UIApplication sharedApplication] statusBarFrame]; 
これらを使っている箇所は見直しましょう
回転検知時に呼ばれる処理も変更 
- (void)willRotateToInterfaceOrientation: 
(UIInterfaceOrientation)toInterfaceOrientatio 
n duration:(NSTimeInterval)duration;
回転検知時に呼ばれる処理も変更 
- (void)willRotateToInterfaceOrientation: 
(UIInterfaceOrientation)toInterfaceOrientatio 
n duration:(NSTimeInterval)duration; 
- (void)viewWillTransitionToSize:(CGSize)size 
withTransitionCoordinator:(id 
<UIViewControllerTransitionCoordinator>)coord 
inator; 
回転検知ではなく、サイズが変更されたと考える
実際の対応内容 
//iOS7以前の画面回転開始時の処理 
- (void)willRotateToInterfaceOrientation: 
(UIInterfaceOrientation)toInterfaceOrientation duration: 
(NSTimeInterval)duration 
{ 
//端末の向き取得 
BOOL isLandscape = 
UIInterfaceOrientationIsLandscape(toInterfaceOrientation); 
//以降width、heightを取得して回転後の座標位置変更処理を行う 
}
実際の対応内容 
//iOS7以前の画面回転開始時の処理 
- (void)willRotateToInterfaceOrientation: 
(UIInterfaceOrientation)toInterfaceOrientation duration: 
(NSTimeInterval)duration 
{ 
//端末の向き取得 
BOOL isLandscape = 
UIInterfaceOrientationIsLandscape(toInterfaceOrientation); 
//以降width、heightを取得して回転後の座標位置変更処理を行う 
} 
! 
//iOS8以降のサイズ変更時(回転時)の処理 
- (void)viewWillTransitionToSize:(CGSize)size 
withTransitionCoordinator: 
(id<UIViewControllerTransitionCoordinator>)coordinator 
{ 
//端末の向き取得 
BOOL isLandscape = (size.height <= size.width); 
//以降width、heightを取得して回転後の座標位置変更処理を行う 
}
実際の対応内容 
//iOS7以前の画面回転開始時の処理 
- (void)willRotateToInterfaceOrientation: 
(UIInterfaceOrientation)toInterfaceOrientation duration: 
(NSTimeInterval)duration 
{ 
//端末の向き取得 
BOOL isLandscape = 
UIInterfaceOrientationIsLandscape(toInterfaceOrientation); 
//以降width、heightを取得して回転後の座標位置変更処理を行う 
} 
! 
//iOS8以降のサイズ変更時(回転時)の処理 
- (void)viewWillTransitionToSize:(CGSize)size 
withTransitionCoordinator: 
(id<UIViewControllerTransitionCoordinator>)coordinator 
{ 
//端末の向き取得 
BOOL isLandscape = (size.height <= size.width); 
//以降width、heightを取得して回転後の座標位置変更処理を行う 
}
CASE 2 
罫線の左が切れる
罫線の左側が切れる問題
iOS7対応の時に行った処理 
[UITableViewCell appearance].separatorInset = 
UIEdgeInsetsZero;
iOS8の新しいプロパティlayoutMargins 
によりマージンが設定されている 
(lldb) p (UIEdgeInsets)[self.tableView layoutMargins] 
(UIEdgeInsets) $1 = (top = 0, left = 16, bottom = 0, 
right = 16)
コンテンツのマージン 
設定をオフにする 
-(void)viewDidLayoutSubviews 
{ 
[super viewDidLayoutSubviews]; 
self.tableView.layoutMargins = UIEdgeInsetsZero; 
}
コンテンツのマージン 
設定をオフにする 
-(void)viewDidLayoutSubviews 
{ 
[super viewDidLayoutSubviews]; 
self.tableView.layoutMargins = UIEdgeInsetsZero; 
}
コンテンツのマージン 
設定をオフにする 
-(void)viewDidLayoutSubviews 
{ 
[super viewDidLayoutSubviews]; 
self.tableView.layoutMargins = UIEdgeInsetsZero; 
}
CASE 3 
デバイストークンが 
取得できない
デバイストークン取得処理変更 
[[UIApplication sharedApplication] 
registerForRemoteNotificationTypes: 
(UIRemoteNotificationTypeBadge| 
UIRemoteNotificationTypeSound| 
UIRemoteNotificationTypeAlert)];
デバイストークン取得処理変更 
[[UIApplication sharedApplication] 
registerForRemoteNotificationTypes: 
(UIRemoteNotificationTypeBadge| 
UIRemoteNotificationTypeSound| 
UIRemoteNotificationTypeAlert)];
デバイストークン取得処理変更 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
! 
UIUserNotificationSettings *settings = [UIUserNotificationSettings 
settingsForTypes:types categories:nil]; 
! 
[[UIApplication sharedApplication] 
registerUserNotificationSettings:settings]; 
! 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications]; 
iOSバージョン毎に処理を分岐する必要がある
デバイストークン取得処理変更 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
! 
UIUserNotificationSettings *settings = [UIUserNotificationSettings 
settingsForTypes:types categories:nil]; 
! 
[[UIApplication sharedApplication] 
registerUserNotificationSettings:settings]; 
! 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications];
デバイストークン取得処理変更 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
! 
UIUserNotificationSettings *settings = [UIUserNotificationSettings 
settingsForTypes:types categories:nil]; 
! 
[[UIApplication sharedApplication] 
registerUserNotificationSettings:settings]; 
! 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications];
デバイストークン取得処理変更 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
! 
UIUserNotificationSettings *settings = [UIUserNotificationSettings 
settingsForTypes:types categories:nil]; 
! 
[[UIApplication sharedApplication] 
registerUserNotificationSettings:settings]; 
! 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications];
デバイストークン取得処理変更 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
! 
UIUserNotificationSettings *settings = [UIUserNotificationSettings 
settingsForTypes:types categories:nil]; 
! 
[[UIApplication sharedApplication] 
registerUserNotificationSettings:settings]; 
! 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications];
デバイストークン取得処理変更 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
! 
UIUserNotificationSettings *settings = [UIUserNotificationSettings 
settingsForTypes:types categories:nil]; 
! 
[[UIApplication sharedApplication] 
registerUserNotificationSettings:settings]; 
! 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications]; 
iOSバージョン毎に処理を分岐する必要がある
OSバージョンで分岐させてます 
+ (void)registerNotification 
{ 
//iOS8とそれ以外で設定を変更する必要がある 
if ([YJUtil isIOS8]){ 
[AucNotificationConfigure registerNotificationAfteriOS8]; 
} else { 
[AucNotificationConfigure registerNotificationBeforeiOS7]; 
} 
}
InteractiveなPushにも 
対応しています
+ (void)registerNotificationAfteriOS8 
{ 
UIMutableUserNotificationAction *bidAction = 
[[UIMutableUserNotificationAction alloc] init]; 
bidAction.identifier = XXXXXXX; 
bidAction.title = @"入札する"; 
bidAction.activationMode = UIUserNotificationActivationModeForeground; 
bidAction.destructive = NO; 
bidAction.authenticationRequired = NO; 
UIMutableUserNotificationCategory *inviteCategory = 
[[UIMutableUserNotificationCategory alloc] init]; 
inviteCategory.identifier = XXXXXXX; 
[inviteCategory setActions:@[bidAction] 
forContext:UIUserNotificationActionContextMinimal]; 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
NSSet *categories = [NSSet setWithObject:inviteCategory]; 
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types 
categories:categories]; 
[[UIApplication sharedApplication] registerUserNotificationSettings:settings]; 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications]; 
} 
iOS8以後の処理
+ (void)registerNotificationAfteriOS8 
{ 
UIMutableUserNotificationAction *bidAction = 
[[UIMutableUserNotificationAction alloc] init]; 
bidAction.identifier = XXXXXXX; 
bidAction.title = @"入札する"; 
bidAction.activationMode = UIUserNotificationActivationModeForeground; 
bidAction.destructive = NO; 
bidAction.authenticationRequired = NO; 
UIMutableUserNotificationCategory *inviteCategory = 
[[UIMutableUserNotificationCategory alloc] init]; 
inviteCategory.identifier = XXXXXXX; 
[inviteCategory setActions:@[bidAction] 
forContext:UIUserNotificationActionContextMinimal]; 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
NSSet *categories = [NSSet setWithObject:inviteCategory]; 
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types 
categories:categories]; 
[[UIApplication sharedApplication] registerUserNotificationSettings:settings]; 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications]; 
} 
iOS8以後の処理
+ (void)registerNotificationAfteriOS8 
{ 
UIMutableUserNotificationAction *bidAction = 
[[UIMutableUserNotificationAction alloc] init]; 
bidAction.identifier = XXXXXXX; 
bidAction.title = @"入札する"; 
bidAction.activationMode = UIUserNotificationActivationModeForeground; 
bidAction.destructive = NO; 
bidAction.authenticationRequired = NO; 
UIMutableUserNotificationCategory *inviteCategory = 
[[UIMutableUserNotificationCategory alloc] init]; 
inviteCategory.identifier = XXXXXXX; 
[inviteCategory setActions:@[bidAction] 
forContext:UIUserNotificationActionContextMinimal]; 
//通知タイプの設定 
UIUserNotificationType types = UIUserNotificationTypeBadge | 
UIUserNotificationTypeSound | UIUserNotificationTypeAlert; 
NSSet *categories = [NSSet setWithObject:inviteCategory]; 
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types 
categories:categories]; 
[[UIApplication sharedApplication] registerUserNotificationSettings:settings]; 
//Push通知の利用許可をとる 
[[UIApplication sharedApplication] registerForRemoteNotifications]; 
} 
iOS8以後の処理
CASE 4 
iPadでカメラが反応しない
iPadでカメラ撮影する時 
UIImagePickerController * picker = [[UIImagePickerController alloc] init]; 
picker.delegate = self; 
picker.sourceType = UIImagePickerControllerSourceTypeCamera; 
! 
[self presentViewController:picker animated:YES completion:nil];
iPadでカメラ撮影する時 
UIImagePickerController * picker = [[UIImagePickerController alloc] init]; 
picker.delegate = self; 
picker.sourceType = UIImagePickerControllerSourceTypeCamera; 
! 
[self presentViewController:picker animated:YES completion:nil];
iPadでカメラ撮影する時 
UIImagePickerController * picker = [[UIImagePickerController alloc] init]; 
picker.delegate = self; 
picker.sourceType = UIImagePickerControllerSourceTypeCamera; 
! 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self presentViewController:picker animated:YES completion:nil]; 
非同期で起動しないと固まってしまう 
});
iPadでカメラ撮影する時 
UIImagePickerController * picker = [[UIImagePickerController alloc] init]; 
picker.delegate = self; 
picker.sourceType = UIImagePickerControllerSourceTypeCamera; 
! 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self presentViewController:picker animated:YES completion:nil]; 
非同期で起動しないと固まってしまう 
});
iPadでカメラ撮影する時 
UIImagePickerController * picker = [[UIImagePickerController alloc] init]; 
picker.delegate = self; 
picker.sourceType = UIImagePickerControllerSourceTypeCamera; 
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self presentViewController:picker animated:YES completion:nil]; 
}); 
} else { 
[self presentViewController:picker animated:YES completion:nil]; 
} 
非同期で起動しないと固まってしまう
iPadでアルバムから写真を選 
択する際も同様
iPadでアルバムから写真を選択する際も同様 
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init]; 
imagePickerController.delegate = self; 
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 
! 
self.popover = [[UIPopoverController alloc] initWithContentViewController: 
imagePickerController]; 
self.popover.delegate = self; 
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
}); 
} else { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
} 
非同期で起動しないと固まってしまう
iPadでアルバムから写真を選択する際も同様 
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init]; 
imagePickerController.delegate = self; 
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 
! 
self.popover = [[UIPopoverController alloc] initWithContentViewController: 
imagePickerController]; 
self.popover.delegate = self; 
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
}); 
} else { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
} 
非同期で起動しないと固まってしまう
iPadでアルバムから写真を選択する際も同様 
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init]; 
imagePickerController.delegate = self; 
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 
! 
self.popover = [[UIPopoverController alloc] initWithContentViewController: 
imagePickerController]; 
self.popover.delegate = self; 
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
}); 
} else { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
} 
非同期で起動しないと固まってしまう
iPadでアルバムから写真を選択する際も同様 
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init]; 
imagePickerController.delegate = self; 
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 
! 
self.popover = [[UIPopoverController alloc] initWithContentViewController: 
imagePickerController]; 
self.popover.delegate = self; 
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) { 
dispatch_async(dispatch_get_main_queue(), ^ { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
}); 
} else { 
[self.popover presentPopoverFromRect:cell.frame inView:self.view 
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 
} 
非同期で起動しないと固まってしまう
CASE 5 
タブ画像が表示されない
タブ画像が表示されない問題
- (void)setFinishedSelectedImage:(UIImage *)selectedImage 
withFinishedUnselectedImage:(UIImage *)unselectedImage;
setFinishedSelectedImageはDepricated 
- (void)setFinishedSelectedImage:(UIImage *)selectedImage 
withFinishedUnselectedImage:(UIImage *)unselectedImage;
setFinishedSelectedImageはDepricated 
UIImage *m1 = [[UIImage imageNamed:@"m1.png"] 
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
! 
UIImage *m2 = [[UIImage imageNamed:@"m2.png"] 
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
! 
UITabBarItem *tab = [[UITabBarItem alloc] initWithTitle:@"" image:m1 
selectedImage:m2];
setFinishedSelectedImageはDepricated 
UIImage *m1 = [[UIImage imageNamed:@"m1.png"] 
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
! 
UIImage *m2 = [[UIImage imageNamed:@"m2.png"] 
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
! 
UITabBarItem *tab = [[UITabBarItem alloc] initWithTitle:@"" image:m1 
selectedImage:m2];
setFinishedSelectedImageはDepricated 
UIImage *m1 = [[UIImage imageNamed:@"m1.png"] 
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
! 
UIImage *m2 = [[UIImage imageNamed:@"m2.png"] 
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
! 
UITabBarItem *tab = [[UITabBarItem alloc] initWithTitle:@"" image:m1 
selectedImage:m2]; 
UIImageRenderingModeAlwaysOriginalと共に生成する
CASE 6 
Extentionの共通ロジックどうする 
問題
アプリ本体とExtentionで 
利用する共通部品クラスにおいて 
[UIApplication sharedApplication] 
が使われているメソッドがある 
色々な事情で共通部品クラスに 
大きな修正を加えることができませんでした
+ (UIApplication *)sharedApplication 
NS_EXTENSION_UNAVAILABLE_IOS("Use view 
controller based solutions where 
appropriate instead."); 
NS_EXTENSION_UNAVAILABLE_IOS 
のメソッドはExtention内では利用できない
どうするべきか 
! 
! 
// 特定のアプリを起動する 
void launchXXXXX(NSString* message) 
{ 
NSString* url = [NSString stringWithFormat: 
@"%@://XXXXX/?message=%@", kXXXXSchemes, message]; 
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; 
} 
!
Preprocessor Macroを使う方法 
#ifndef AUC_WIDGET 
! 
// 特定のアプリを起動する 
void launchXXXXX(NSString* message) 
{ 
NSString* url = [NSString stringWithFormat: 
@"%@://XXXXX/?message=%@", kXXXXSchemes, message]; 
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; 
} 
#endif
Preprocessor Macroを使う方法 
#ifndef AUC_WIDGET 
! 
// 特定のアプリを起動する 
void launchXXXXX(NSString* message) 
{ 
NSString* url = [NSString stringWithFormat: 
@"%@://XXXXX/?message=%@", kXXXXSchemes, message]; 
[[UIApplication sharedApplication] openURL:[NSURL 
URLWithString:url]]; 
} 
#endif 
できるだけ共通部品から取り除くべきですが、 
一手段として参考にしてください
ExtentionのPreprocessor Macroの設定例
Extention Today対応
Extention Todayについて 
• ガイドライン上、スクロールできるUIはユーザに 
とって好ましくないとの記述がある 
• ヤフオク!では入札中の商品を一覧できるExtention 
Todayを作成したかった 
• 一覧から入札できればなお良い(でもウィジェッ 
トではキーボードは利用できない)
iOS8対応に 
掛かった工数
iOS8対応に掛かった工数(iPhone) 
iOS8での不具合 
修正 
ウィジェット 
作成合計 
制作ー3人日3人日 
開発4人日4人日8人日
iOS8対応に掛かった工数(iPad) 
iOS8での不具合 
修正 
ウィジェット 
作成合計 
制作ー0.5人日0.5人日 
開発3人日1人日4人日
開発工数 
iOS7対応>>>>>iOS8対応>iOS6対応
リリース後の反響
最後に
http://topic.auctions.yahoo.co.jp/promo/hr/p/
http://topic.auctions.yahoo.co.jp/promo/hr/p/

既存アプリのiOS8対応 #ios8yahoo