View
18.272
Download
2
Category
Preview:
DESCRIPTION
http://connpass.com/event/8629/
Citation preview
既存アプリの 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とwidthheight
heightwidth
width
長い方がheightという 前提でも成り立つ
iOS8でのheightとwidthheight
widthwidth
height
長い方がheightという 前提でコードを 書いてしまっていた
端末の向きにってheight、width に変化があるメソッド
• [[UIScreen mainScreen] applicationFrame]; • [[UIScreen mainScreen] bounds]; • [[UIApplication sharedApplication] statusBarFrame];
これらを使っている箇所は見直しましょう
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
回転検知時に呼ばれる処理も変更
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
回転検知時に呼ばれる処理も変更
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator;
回転検知ではなく、サイズが変更されたと考える
実際の対応内容//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 罫線の左が切れる
罫線の左側が切れる問題
[UITableViewCell appearance].separatorInset = UIEdgeInsetsZero;
iOS7対応の時に行った処理
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バージョン毎に処理を分岐する必要がある
+ (void)registerNotification { //iOS8とそれ以外で設定を変更する必要がある if ([YJUtil isIOS8]){ [AucNotificationConfigure registerNotificationAfteriOS8]; } else { [AucNotificationConfigure registerNotificationBeforeiOS7]; } }
OSバージョンで分岐させてます
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でカメラが反応しない
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でカメラ撮影する時
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でアルバムから写真を選択する際も同様
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]; }
iPadでアルバムから写真を選択する際も同様
非同期で起動しないと固まってしまう
CASE 5 タブ画像が表示されない
タブ画像が表示されない問題
- (void)setFinishedSelectedImage:(UIImage *)selectedImage withFinishedUnselectedImage:(UIImage *)unselectedImage;
- (void)setFinishedSelectedImage:(UIImage *)selectedImage withFinishedUnselectedImage:(UIImage *)unselectedImage;
setFinishedSelectedImageはDepricated
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]]; } !
どうするべきか
#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
Preprocessor Macroを使う方法
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対応
リリース後の反響
最後に
Recommended