既存アプリのiOS8対応 #ios8yahoo

142 Views

October 24, 14

スライド概要

http://connpass.com/event/8629/

profile-image

2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp

シェア

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

既存アプリの iOS8対応 Mao Nishi

2.

今日話すこと • 今回のiOS8対応範囲 • ヤフオク!アプリで起きた問題 • Extension Today対応 • 掛かった工数 • ユーザの反響

3.

ヤフオク!アプリについて

5.

ヤフオク!アプリについて • • iPhone版 • 2010年10月リリース(当時はiOS4.1) • コード上でUI部品を生成している箇所多数 iPad版 • 2013年12月リリース • xib、storyboardは当然活用

6.

今回のiOS8対応範囲について

7.

ヤフオク!アプリの iOS8対応の範囲 iOS8での正常動 作を目指す iPhone/iPad 対応済み! iOS8独自機能 iPhone6/iPhone6 (Extentionなどを Plus向けにレイア ウトする 搭載) 対応済み! これから

8.

ヤフオク!アプリの iOS8対応の範囲 iOS8での正常動 作を目指す iPhone/iPad 対応済み! iOS8独自機能 iPhone6/iPhone6 (Extentionなどを Plus向けにレイア ウトする 搭載) 対応済み! これから

9.

iOS8対応時に出会った事象・ 不具合等を紹介します

10.

これからiOS8対応にあたられる 方の参考になればと思います

11.

CASE 1 回転時にレイアウトが崩れる

12.

とりあえずビルドして 動かしてみた

13.

期待する動き

14.

予期しない動き

15.

回せば回すほど.. レイアウトが崩れていく事態に

16.

原因 [[UIScreen mainScreen] applicationFrame];

17.

原因 CGRect appFrame = [[UIScreen mainScreen] applicationFrame]; /* 以下はiOS8からは端末の向きによって返却される値が変わるようになった*/ CGFloat height = appFrame.size.height; CGFloat width = appFrame.size.width;

18.

iOS7でのheightとwidth height width width height 長い方がheightという 前提でも成り立つ

19.

iOS8でのheightとwidth height height width width 長い方がheightという 前提でコードを 書いてしまっていた

20.

端末の向きにってheight、width に変化があるメソッド • • • [[UIScreen mainScreen] applicationFrame]; [[UIScreen mainScreen] bounds]; [[UIApplication sharedApplication] statusBarFrame]; これらを使っている箇所は見直しましょう

21.

回転検知時に呼ばれる処理も変更 - (void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientatio n duration:(NSTimeInterval)duration;

22.

回転検知時に呼ばれる処理も変更 - (void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientatio n duration:(NSTimeInterval)duration; - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coord inator; 回転検知ではなく、サイズが変更されたと考える

23.

実際の対応内容 //iOS7以前の画面回転開始時の処理 - (void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientation duration: (NSTimeInterval)duration { //端末の向き取得 BOOL isLandscape = UIInterfaceOrientationIsLandscape(toInterfaceOrientation); //以降width、heightを取得して回転後の座標位置変更処理を行う }

24.
[beta]
実際の対応内容
//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を取得して回転後の座標位置変更処理を行う
}

25.
[beta]
実際の対応内容
//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を取得して回転後の座標位置変更処理を行う
}

26.

CASE 2 罫線の左が切れる

27.

罫線の左側が切れる問題

28.

iOS7対応の時に行った処理 [UITableViewCell appearance].separatorInset = UIEdgeInsetsZero;

29.

iOS8の新しいプロパティlayoutMargins によりマージンが設定されている (lldb) p (UIEdgeInsets)[self.tableView layoutMargins] (UIEdgeInsets) $1 = (top = 0, left = 16, bottom = 0, right = 16)

30.

コンテンツのマージン 設定をオフにする -(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.tableView.layoutMargins = UIEdgeInsetsZero; }

31.

コンテンツのマージン 設定をオフにする -(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.tableView.layoutMargins = UIEdgeInsetsZero; }

32.

コンテンツのマージン 設定をオフにする -(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.tableView.layoutMargins = UIEdgeInsetsZero; }

33.

CASE 3 デバイストークンが 取得できない

34.

デバイストークン取得処理変更 [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge| UIRemoteNotificationTypeSound| UIRemoteNotificationTypeAlert)];

35.

デバイストークン取得処理変更 [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge| UIRemoteNotificationTypeSound| UIRemoteNotificationTypeAlert)];

36.

デバイストークン取得処理変更 //通知タイプの設定 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert; ! UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; ! [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; ! //Push通知の利用許可をとる [[UIApplication sharedApplication] registerForRemoteNotifications]; iOSバージョン毎に処理を分岐する必要がある

37.

デバイストークン取得処理変更 //通知タイプの設定 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert; ! UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; ! [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; ! //Push通知の利用許可をとる [[UIApplication sharedApplication] registerForRemoteNotifications];

38.

デバイストークン取得処理変更 //通知タイプの設定 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert; ! UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; ! [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; ! //Push通知の利用許可をとる [[UIApplication sharedApplication] registerForRemoteNotifications];

39.

デバイストークン取得処理変更 //通知タイプの設定 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert; ! UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; ! [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; ! //Push通知の利用許可をとる [[UIApplication sharedApplication] registerForRemoteNotifications];

40.

デバイストークン取得処理変更 //通知タイプの設定 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert; ! UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; ! [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; ! //Push通知の利用許可をとる [[UIApplication sharedApplication] registerForRemoteNotifications];

41.

デバイストークン取得処理変更 //通知タイプの設定 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert; ! UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; ! [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; ! //Push通知の利用許可をとる [[UIApplication sharedApplication] registerForRemoteNotifications]; iOSバージョン毎に処理を分岐する必要がある

42.

OSバージョンで分岐させてます + (void)registerNotification { //iOS8とそれ以外で設定を変更する必要がある } if ([YJUtil isIOS8]){ [AucNotificationConfigure registerNotificationAfteriOS8]; } else { [AucNotificationConfigure registerNotificationBeforeiOS7]; }

43.

InteractiveなPushにも 対応しています

44.

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]; }

45.

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]; }

46.

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]; }

47.

CASE 4 iPadでカメラが反応しない

48.

iPadでカメラ撮影する時 UIImagePickerController * picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.sourceType = UIImagePickerControllerSourceTypeCamera; ! [self presentViewController:picker animated:YES completion:nil];

49.

iPadでカメラ撮影する時 UIImagePickerController * picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.sourceType = UIImagePickerControllerSourceTypeCamera; ! [self presentViewController:picker animated:YES completion:nil];

50.
[beta]
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];
});

非同期で起動しないと固まってしまう

51.
[beta]
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];
});

非同期で起動しないと固まってしまう

52.
[beta]
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];
}

非同期で起動しないと固まってしまう

53.

iPadでアルバムから写真を選 択する際も同様

54.
[beta]
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];
}

非同期で起動しないと固まってしまう

55.
[beta]
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];
}

非同期で起動しないと固まってしまう

56.
[beta]
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];
}

非同期で起動しないと固まってしまう

57.
[beta]
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];
}

非同期で起動しないと固まってしまう

58.

CASE 5 タブ画像が表示されない

59.

タブ画像が表示されない問題

60.

- (void)setFinishedSelectedImage:(UIImage *)selectedImage withFinishedUnselectedImage:(UIImage *)unselectedImage;

61.

setFinishedSelectedImageはDepricated - (void)setFinishedSelectedImage:(UIImage *)selectedImage withFinishedUnselectedImage:(UIImage *)unselectedImage;

62.

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]; ! !

63.

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]; ! !

64.

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と共に生成する ! !

65.

CASE 6 Extentionの共通ロジックどうする 問題

66.

アプリ本体とExtentionで 利用する共通部品クラスにおいて [UIApplication sharedApplication] が使われているメソッドがある 色々な事情で共通部品クラスに 大きな修正を加えることができませんでした

67.

+ (UIApplication *)sharedApplication NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropriate instead."); NS_EXTENSION_UNAVAILABLE_IOS のメソッドはExtention内では利用できない

68.
[beta]
どうするべきか
!
!

// 特定のアプリを起動する
void launchXXXXX(NSString* message)
{
NSString* url = [NSString stringWithFormat:
@"%@://XXXXX/?message=%@", kXXXXSchemes, message];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
}

!

69.
[beta]
Preprocessor Macroを使う方法
#ifndef AUC_WIDGET

!

// 特定のアプリを起動する
void launchXXXXX(NSString* message)
{
NSString* url = [NSString stringWithFormat:
@"%@://XXXXX/?message=%@", kXXXXSchemes, message];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
}
#endif

70.
[beta]
Preprocessor Macroを使う方法
#ifndef AUC_WIDGET

!

// 特定のアプリを起動する
void launchXXXXX(NSString* message)
{
NSString* url = [NSString stringWithFormat:
@"%@://XXXXX/?message=%@", kXXXXSchemes, message];
[[UIApplication sharedApplication] openURL:[NSURL
URLWithString:url]];
}
#endif

できるだけ共通部品から取り除くべきですが、	

一手段として参考にしてください

71.

ExtentionのPreprocessor Macroの設定例

72.

Extention Today対応

74.

Extention Todayについて • ガイドライン上、スクロールできるUIはユーザに とって好ましくないとの記述がある • ヤフオク!では入札中の商品を一覧できるExtention Todayを作成したかった • 一覧から入札できればなお良い(でもウィジェッ トではキーボードは利用できない)

81.

iOS8対応に 掛かった工数

82.

iOS8対応に掛かった工数(iPhone) iOS8での不具合 修正 ウィジェット 作成 合計 制作 ー 3人日 3人日 開発 4人日 4人日 8人日

83.

iOS8対応に掛かった工数(iPad) iOS8での不具合 修正 ウィジェット 作成 合計 制作 ー 0.5人日 0.5人日 開発 3人日 1人日 4人日

84.

開発工数 iOS7対応>>>>>iOS8対応>iOS6対応

85.

リリース後の反響

89.

最後に

90.

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

91.

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