既存アプリのiOS8対応 #ios8yahoo

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対応

リリース後の反響

最後に

http://topic.auctions.yahoo.co.jp/promo/hr/p/

http://topic.auctions.yahoo.co.jp/promo/hr/p/

Recommended