Successfully reported this slideshow.
Your SlideShare is downloading. ×

Apple TV tvOS入門 Iosdc2017

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
AbemaTV on tvOS
AbemaTV on tvOS
Loading in …3
×

Check these out next

1 of 134 Ad

More Related Content

Similar to Apple TV tvOS入門 Iosdc2017 (18)

Advertisement

Recently uploaded (20)

Apple TV tvOS入門 Iosdc2017

  1. 1. iOSDC 2017/09/17 Yuji Hato Apple TV tvOS入門
  2. 2. About me Yuji Hato CyberAgent, Inc. / AbemaTV, Inc. dekatotoro @dekatotoro Contributed services
  3. 3. Apple TV
  4. 4. Apple TV
  5. 5. ・64-bit A8 processor ・32GB or 64GB of storage ・2GB of RAM ・10/100Mbps Ethernet ・WiFi 802.11a/b/g/n/ac ・1080p resolution ・HDMI 1.4 Apple TV 4th generation ・A10X Fusion ・32GB or 64GB of storage ・3GB of RAM ・Gigabit Ethernet ・Wi-Fi with MIMO 802.11ac ・2160p resolution ・HDMI 2.0 4K
  6. 6. 32GB 15,800円 64GB 20,800円 ※2016/09に値下げ ・32GB 18,400円 →15,800円(2,600円値下げ) ・64GB 24,800円 →20,800円(4,000円値下げ) Apple TV (tvOS) 4th generation 4K 32GB 19,800円 64GB 21,800円
  7. 7. Remote
  8. 8. https://developer.apple.com/tvos/human-interface-guidelines/remote-and-controllers/ Remote
  9. 9. https://developer.apple.com/tvos/human-interface-guidelines/remote-and-controllers/ Remote
  10. 10. Swipe Click Tap https://developer.apple.com/tvos/human-interface-guidelines/remote-and-controllers/ Remote
  11. 11. Development Method
  12. 12. Development Method iOSとほぼ同様に開発 ・Traditional Apps TVMLとTVJSをで開発 ・Client-Server Apps
  13. 13. Limited App Size
  14. 14. Limited App Size 200MB 4GB
  15. 15. Data Storage
  16. 16. Limited Local Storage Service Online/Local Persistent/Temporary limit iCloud Key-Value Store (KVS) Online Persistent 1MB max CloudKit Online Persistent - UserDefaults Local Persistent 500KB max Keychain Local Persistent - CacheDirectory Local Temporary May be purged TemporaryDirectory Local Temporary May be purged
  17. 17. Limited Local Storage Service Online/Local Persistent/Temporary limit iCloud Key-Value Store (KVS) Online Persistent 1MB max CloudKit Online Persistent - UserDefaults Local Persistent 500KB max Keychain Local Persistent - CacheDirectory Local Temporary May be purged TemporaryDirectory Local Temporary May be purged
  18. 18. Limited Local Storage Service Online/Local Persistent/Temporary limit iCloud Key-Value Store (KVS) Online Persistent 1MB max CloudKit Online Persistent - UserDefaults Local Persistent 500KB max Keychain Local Persistent - CacheDirectory Local Temporary May be purged TemporaryDirectory Local Temporary May be purged
  19. 19. Limited Local Storage Service Online/Local Persistent/Temporary limit iCloud Key-Value Store (KVS) Online Persistent 1MB max CloudKit Online Persistent - UserDefaults Local Persistent 500KB max Keychain Local Persistent - CacheDirectory Local Temporary May be purged TemporaryDirectory Local Temporary May be purged
  20. 20. User Interaction
  21. 21. https://developer.apple.com/tvos/human-interface-guidelines/overview/ User Interaction Focus
  22. 22. https://developer.apple.com/tvos/human-interface-guidelines/app-architecture/focus-and-selection/ Parallax User Interaction
  23. 23. https://developer.apple.com/tvos/human-interface-guidelines/visual-design/video/ Video User Interaction
  24. 24. Icons and Images
  25. 25. https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/ Icons and Images App Icon.
  26. 26. https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/app-icon/ Layered Images. Icons and Images
  27. 27. Layered Images. https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/ Icons and Images
  28. 28. https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/ Top Shelf Images. Icons and Images
  29. 29. https://developer.apple.com/tvos/human-interface-guidelines/icons-and-images/ Top Shelf Images. Icons and Images
  30. 30. Layout
  31. 31. Layout 1920 x 1080 3840 x 21604K
  32. 32. https://developer.apple.com/tvos/human-interface-guidelines/visual-design/ Layout
  33. 33. https://support.apple.com/ja-jp/HT202763 Layout
  34. 34. https://developer.apple.com/tvos/human-interface-guidelines/visual-design/ Layout
  35. 35. https://developer.apple.com/tvos/human-interface-guidelines/visual-design/ Layout
  36. 36. https://developer.apple.com/tvos/human-interface-guidelines/visual-design/layout/ Layout
  37. 37. Interface Elements
  38. 38. Tab Bars Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  39. 39. TableView Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  40. 40. CollectionView Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  41. 41. Split Views Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  42. 42. Text Fields Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  43. 43. Keyboards Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  44. 44. Search Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  45. 45. Button Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  46. 46. Navigation Bars Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  47. 47. Navigation Bars Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  48. 48. Page Controls Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  49. 49. Activity Indicators Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  50. 50. Alerts Interface Elements https://developer.apple.com/tvos/human-interface-guidelines/interface-elements/
  51. 51. Gesture
  52. 52. Gesture
  53. 53. Gesture • Swipe • Touch • Select
  54. 54. Gesture UITapGestureRecognizer • upArrow • downArrow • leftArrow • rightArrow • select • menu • playPause
  55. 55. Gesture UITapGestureRecognizer let gesture = UITapGestureRecognizer(target: self, action: #selector(menuButtonPressed(_:))) gesture.allowedPressTypes = [NSNumber(value: UIPressType.menu.rawValue)] view.addGestureRecognizer(gesture)
  56. 56. Gesture UITapGestureRecognizer gesture.allowedPressTypes = [NSNumber(value: UIPressType.menu.rawValue)]
  57. 57. Gesture UISwipeGestureRecognizer • right • left • up • down
  58. 58. Gesture let gesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeUp(_:))) gesture.direction = .up view.addGestureRecognizer(gesture) UISwipeGestureRecognizer
  59. 59. Gesture gesture.direction = .up UISwipeGestureRecognizer
  60. 60. Focus
  61. 61. https://developer.apple.com/tvos/human-interface-guidelines/overview/ Focus
  62. 62. Focus Focusはいつ更新されるの?
  63. 63. Focus • システムが更新を要求したとき • ユーザーの操作 • アプリケーションが更新を要求したとき
  64. 64. Focus Focusは誰が更新するの?
  65. 65. Focus https://developer.apple.com/library/content/documentation/General/Conceptual/AppleTV_PG/WorkingwiththeAppleTVRemote.html Focus Engine
  66. 66. Focus Focus Engineが検索する範囲は、現在Focusされて いるViewの大きさに応じて決まり、そのViewを起点 として、動きの方向にあるFocus可能な領域を見つけ て更新 Focus Engine
  67. 67. Focus Focus Engineは、View階層のFocus動作を定義する UIFocusEnvironmentプロトコルに従ってFocusを制 御 Focus Engine
  68. 68. Focus https://devstreaming-cdn.apple.com/videos/wwdc/2017/224sn8vw625k1e86/224/224_focus_interaction_in_tvos_11.pdf UIFocusEnvironment
  69. 69. Focus public var preferredFocusEnvironments: [UIFocusEnvironment] { get } public func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool public func setNeedsFocusUpdate() public func updateFocusIfNeeded() public func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) @available(tvOS 11.0, *) optional public func soundIdentifierForFocusUpdate(in context: UIFocusUpdateContext) -> UIFocusSoundIdentifier? UIFocusEnvironmentプロトコル
  70. 70. Focus public var preferredFocusEnvironments: [UIFocusEnvironment] { get } UIFocusEnvironmentプロトコル
  71. 71. Focus public func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool UIFocusEnvironmentプロトコル
  72. 72. Focus public func setNeedsFocusUpdate() public func updateFocusIfNeeded() UIFocusEnvironmentプロトコル
  73. 73. Focus public func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) UIFocusEnvironmentプロトコル
  74. 74. Focus https://devstreaming-cdn.apple.com/videos/wwdc/2017/224sn8vw625k1e86/224/224_focus_interaction_in_tvos_11.pdf UIFocusEnvironment
  75. 75. Focus @available(tvOS 11.0, *) optional public func soundIdentifierForFocusUpdate(in context: UIFocusUpdateContext) -> UIFocusSoundIdentifier? UIFocusEnvironmentプロトコル
  76. 76. Focus UIFocusEnvironmentに準拠しているクラス
  77. 77. Focus • UIView • UIViewController • UIPresentationController
  78. 78. Focus @available(tvOS 10.0, *) public protocol UIFocusItem : UIFocusEnvironment { public var canBecomeFocused: Bool { get } } UIFocusItemプロトコル
  79. 79. Focus Focus可能なUIKitのクラス
  80. 80. Focus • UIButton • UITextField • UITableView • UICollectionView • UITextView • UISegmentedControl • UISearchBar • etc..
  81. 81. Focus https://devstreaming-cdn.apple.com/videos/wwdc/2017/224sn8vw625k1e86/224/224_focus_interaction_in_tvos_11.pdf
  82. 82. Focus UILabelなどデフォルトでフォーカスされないViewを フォーカスさせるには?
  83. 83. Focus class CustomLabel: UILabel { isUserInteractionEnabled = true isUserInteractionEnabled = true override var canBecomeFocused: Bool { return true } } } } UILabel for Focus
  84. 84. Focus override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { super.didUpdateFocus(in: context, with: coordinator) // Focus if context.nextFocusedView == self { coordinator.addCoordinatedFocusingAnimations({ [weak self] context in self?.transform = CGAffineTransform(scaleX: 1.4, y: 1.4) }, completion: nil) } // UnFocus if context.previouslyFocusedView == self { coordinator.addCoordinatedUnfocusingAnimations({ [weak self] context in self?.transform = CGAffineTransform.identity }, completion: nil) } } UILabel for Focus
  85. 85. Focus // Focus if context.nextFocusedView == self { coordinator.addCoordinatedFocusingAnimations({ [weak self] context in self?.transform = CGAffineTransform(scaleX: 1.4, y: 1.4) }, completion: nil) } UILabel for Focus
  86. 86. Focus // UnFocus if context.previouslyFocusedView == self { coordinator.addCoordinatedUnfocusingAnimations({ [weak self] context in self?.transform = CGAffineTransform.identity }, completion: nil) } UILabel for Focus
  87. 87. Focus UILabel for Focus
  88. 88. Focus UITableView, UICollectionViewのFocusは?
  89. 89. Focus Focus用のdelegateがtvOS用にあります
  90. 90. Focus UITableViewDelegate for Focus @available(tvOS 9.0, *) optional public func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool @available(tvOS 9.0, *) optional public func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool @available(tvOS 9.0, *) optional public func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) @available(tvOS 9.0, *) optional public func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath?
  91. 91. Focus @available(tvOS 9.0, *) optional public func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool @available(tvOS 9.0, *) optional public func collectionView(_ collectionView: UICollectionView, shouldUpdateFocusIn context: UICollectionViewFocusUpdateContext) -> Bool @available(tvOS 9.0, *) optional public func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) @available(tvOS 9.0, *) optional public func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? @available(tvOS 9.0, *) optional public func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath @available(tvOS 9.0, *) optional public func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint UICollectionViewDelegate for Focus
  92. 92. Focus @available(tvOS 9.0, *) open var remembersLastFocusedIndexPath: Bool UITableView, UICollectionView for Focus
  93. 93. Focus UIImageViewのFocusは?
  94. 94. Focus @available(tvOS 9.0, *) open var adjustsImageWhenAncestorFocused: Bool @available(tvOS 9.0, *) open var focusedFrameGuide: UILayoutGuide { get } @available(tvOS 11.0, *) open var overlayContentView: UIView { get } @available(tvOS 11.0, *) open var masksFocusEffectToContents: Bool UIImageView for Focus
  95. 95. Focus @available(tvOS 9.0, *) open var adjustsImageWhenAncestorFocused: Bool UIImageView for Focus
  96. 96. Focus imageView.adjustsImageWhenAncestorFocused = true UIImageView for Focus https://developer.apple.com/tvos/human-interface-guidelines/overview/focus-and-parallax/
  97. 97. Focus UIImageView for Focus @available(tvOS 11.0, *) open var overlayContentView: UIView { get }
  98. 98. Focus UIImageView for Focus
  99. 99. Focus let overlayView = UIView(frame: imageView.frame) overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5) imageView.overlayContentView.addSubview(overlayView) UIImageView for Focus
  100. 100. Focus imageView.overlayContentView.addSubview(overlayView) UIImageView for Focus
  101. 101. Focus // UICollectionView delegate func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { // Focus if let cell = context.nextFocusedView as? CostomCollectionViewCell { coordinator.addCoordinatedFocusingAnimations({ context in cell.imageView.overlayContentView.alpha = 0 }, completion: nil) } // UnFocus if let cell = context.previouslyFocusedView as? CostomCollectionViewCell { coordinator.addCoordinatedUnfocusingAnimations({ context in cell.imageView.overlayContentView.alpha = 1 }, completion: nil) } } UIImageView for Focus
  102. 102. Focus // Focus if let cell = context.nextFocusedView as? CostomCollectionViewCell { coordinator.addCoordinatedFocusingAnimations({ context in cell.imageView.overlayContentView.alpha = 0 }, completion: nil) } UIImageView for Focus
  103. 103. Focus // UnFocus if let cell = context.previouslyFocusedView as? CostomCollectionViewCell { coordinator.addCoordinatedUnfocusingAnimations({ context in cell.imageView.overlayContentView.alpha = 1 }, completion: nil) } UIImageView for Focus
  104. 104. Focus UIImageView for Focus
  105. 105. Focus Focus Update Notification
  106. 106. Focus Focus Update Notification extension NSNotification.Name { @available(tvOS 11.0, *) public static let UIFocusDidUpdate: NSNotification.Name @available(tvOS 11.0, *) public static let UIFocusMovementDidFail: NSNotification.Name } @available(tvOS 11.0, *) public let UIFocusUpdateContextKey: String @available(tvOS 11.0, *) public let UIFocusUpdateAnimationCoordinatorKey: String
  107. 107. Focus Focus Update Notification @available(tvOS 11.0, *) public static let UIFocusDidUpdate: NSNotification.Name
  108. 108. Focus Focus Update Notification @available(tvOS 11.0, *) public static let UIFocusMovementDidFail: NSNotification.Name }
  109. 109. Focus UIFocusGuide?
  110. 110. Focus @available(tvOS 9.0, *) open class UIFocusGuide : UILayoutGuide { open var isEnabled: Bool @available(tvOS 10.0, *) open var preferredFocusEnvironments: [UIFocusEnvironment]! } UIFocusGuide
  111. 111. Focus UIFocusGuide
  112. 112. Focus UIFocusGuide
  113. 113. Focus UIFocusGuide
  114. 114. Focus UIFocusGuide
  115. 115. Focus UIFocusGuide
  116. 116. Focus UIFocusGuide FocusGuide
  117. 117. Focus UIFocusGuide FocusGuide preferredFocusEnvironments
  118. 118. Focus UIFocusGuide
  119. 119. Focus focusGuide = UIFocusGuide() view.addLayoutGuide(focusGuide) // Anchor the top left of the focus guide. focusGuide.topAnchor.constraint(equalTo: button.topAnchor).isActive = true focusGuide.leftAnchor.constraint(equalTo: collectionView.leftAnchor).isActive = true // Anchor the width and height of the focus guide. focusGuide.heightAnchor.constraint(equalTo: button.heightAnchor).isActive = true focusGuide.widthAnchor.constraint(equalTo: collectionView.widthAnchor).isActive = true focusGuide.preferredFocusEnvironments = [button] UIFocusGuide
  120. 120. Focus focusGuide = UIFocusGuide() view.addLayoutGuide(focusGuide) UIFocusGuide
  121. 121. Focus // Anchor the top left of the focus guide. focusGuide.topAnchor.constraint(equalTo: button.topAnchor).isActive = true focusGuide.leftAnchor.constraint(equalTo: collectionView.leftAnchor).isActive = true UIFocusGuide
  122. 122. Focus // Anchor the width and height of the focus guide. focusGuide.heightAnchor.constraint(equalTo: button.heightAnchor).isActive = true focusGuide.widthAnchor.constraint(equalTo: collectionView.widthAnchor).isActive = true UIFocusGuide
  123. 123. Focus focusGuide.preferredFocusEnvironments = [button] UIFocusGuide
  124. 124. Focus Focusがうまくいかない場合
  125. 125. Focus • canBecomeFocusedがfalseになってないか • isHiddenがtrueになってないか • alphaが0でないか • isUserInteractionEnabledがfalseになってないか • viewが隠れていないか Focusがうまくいかない場合
  126. 126. Focus Focusのdebug
  127. 127. Focus Focus Update Logging
  128. 128. Focus The result of the focus update was determined from the following preferred focus search: | | Starting preferred focus search: | |--> Searching <UIFocusSystem 0x60000028dc00>... | |--> Searching <UIScreen 0x6080001d3fb0>... | |--> Searching <UIWindow 0x7fb79cb02af0>... | |--> Searching <tvos_sample_for_iosdc.RootViewController 0x7fb79c904da0>... | |--> Searching <UIView Focus Update Logging
  129. 129. Focus po UIFocusDebugger.status() <UIButton 0x7f97bac05b90> is currently focused. UIFocusDebugger
  130. 130. Focus po UIFocusDebugger.simulateFocusUpdateRequest(from: imageView) Simulating a fake focus update request from <UIImageView 0x7ff3ce2106d0>... (<tvos_sample_for_iosdc.MainCollectionViewCell 0x7ff3ce20fe60> is currently focused) The following issues were found that would normally prevent this environment's request from being accepted by the focus system (these will be ignored for the purposes of this test): - ISSUE: This environment does not contain the currently focused item. Starting preferred focus search: |--> Searching <UIImageView 0x7ff3ce2106d0>... No more preferences for this environment, and there are no focusable items in this environment to prefer by default. This environment does not prefer a valid focusable item, nor any other environments. Simulated Result: Successfully updated focus to nil. UIFocusDebugger
  131. 131. Focus po UIFocusDebugger.checkFocusability(for: label) The following issues were found that would prevent this item from being focusable: - ISSUE: This view has isUserInteractionEnabled set to NO. Views must allow user interaction to be focusable. UIFocusDebugger
  132. 132. Conclusion
  133. 133. Conclusion • iOSとの違いはFocus • 標準のUI/UXに準拠しよう • tvOS対応は難しくない
  134. 134. Thank you 参考資料 https://developer.apple.com/videos/play/wwdc2017/209/ https://developer.apple.com/videos/play/wwdc2017/224 https://developer.apple.com/tvos/resources/

Editor's Notes

  • はい、それではApple TV tvOS入門ということで発表したいと思います。よろしくお願いします。
  • まず簡単に自己紹介です。
    波戸と申します。ネット上ではだいたいdekatotoroという名前で活動していて、
    AbemaTVのiOSとtvOSアプリの開発しています。
  • はい、今日はtvOSの概要や基本とフォーカスについてお話ししたいと思います。
    まず、Apple TVの概要について見ていきたいと思います。
  • 第4世代Apple TVは2015年10月30日に発売されました。先日発表されたApple TV 4Kは09/22日発売されます
  • スペックですが、第4世代がCPUはA8のデュアルコアでメモリが2GB、4KがA10Xでメモリは3Gになっています。
  • ちなみに値段はspecの割にiPhoneに比べると安く感じる気がします
  • はい、次に、Apple TVに付属するリモコンですが、
  • こんな感じで
  • 上のマットな部分はTouch Surfaceと呼ばれている部分で、SwipeやClick, Tapができます。物理ボタンとして、Menu、Home、Siri、Play/PauseとVolumeボタンがあります。
  • Swipeは左右上下で
    Clickは押し込む感じ
    Tapは左右上下の位置で軽く触るイメージになります。
  • 次にtvOSの開発方法ですが、
  • 大きく分けて2つあります
    Traditional AppsとClient server appsの2つです
    Traditional AppsはiOSと同様に開発するもので、
    Client server AppsはTVMLやTVJSといったApple独自のWeb技術を使って開発する手法になります。
    今回は通常のTraditional Appsの開発についての話になります。
  • アプリサイズの制限があって、
  • 以前は200MBの制限があったんですが、現在は4Gに緩和されています。
    ゲームなどサイズが大きくなるものもあると思うので、その場合は
    オンデマンドリソースやCacheDirectoryを使う必要があります。
  • tvOSのデータの保存も制限がいくつかあって、
  • 図のような感じにまとめました
  • iCloudにデータを保存と取得ができますというのと、
  • UserDefaultsが500kの制限がありますよーというのと、
  • TmpDirectory, CacheDirectoryは、OSによって消される可能性がありますーというものです
    アプリケーションの実行中は削除されませんが、tvOSではDocumentsディレクトリは使えないので、RealmとかSqliteのようなdatabase fileもcacheDirectoryに保存することになります。なので、purgeされる可能性を考えると、DBはdisk保存じゃなくてonMemoryを検討してもいいかもしれません。
  • はい、次にtvOSのUser Interactionについて代表的な部分を紹介します。
  • tvOSではFocusという概念があって、基本的にView間の移動はFocusによって行います。
  • 次に画像ですが、
    ImageViewにadjustsImageWhenAncestorFocusedというpropertyがあってこれをtrueにしておけば、focus時にparallaxのようなインタラクションになります。
  • あとは、tvOSと相性の良い動画ですが、
    デフォルトのAVPlayerControllerを使用することで、このようなseekやstart/stop操作など簡単に実装できます。
  • 次にIconと画像ですが、
  • これはAppStoreの画像で、App Iconが並んでいます。
    で、このcollectionで並んでるApp Iconはfocusしてグリグリできるようになっていて、
  • えー、App IconはLayeredImageというもので作成するのですが、
    これは設定アプリの例で、5枚の画像をレイヤードしてできたもので、
    よく見ないとわかりませんが、Focus時にグリグリすると、layeredした画像がparallaxぽい動きになります。
  • ちょっと小さくて見にくいと思いますが、LayeredImageは実際のサイズと、フォーカス時に見切れないサイズ、アンフォーカス時のサイズが定義されているので、これを参考にして作ることができます。App Icon以外にもアプリでLayeredImageを使うことができて、埋め込みのLayeredImageはlsr、サーバから取得するものはlcrというformatで使うことができます。





    埋め込みの場合はXcodeでもできますが、layeredImageを生成するためのツールも用意されています。
  • はい、あとはTop Shelf Imageですが、tvOSのホーム画面でfocusした時の上に表示する大きい画像も用意する必要があります。
  • Top Shelf Imageはコンテンツのリストを見せたりなどのカスタムもできます。
  • はい、次にLayoutについてAppleが推奨しているレイアウトを紹介します。
  • アプリの画面レイアウトは1920 x 1080、4Kの場合はその2倍で16:9の画面解像度に設計します。
  • 全体的なデザインですが、古いTVだと端が見切れて隠れてしまうことがあるので、
    コンテンツから、画面の上下から60ピクセル、側面から90ピクセルで余白を空けておくことが推奨されています。
  • 古いTVなどで端が見切れて隠れてしまう現象は、
    縦が見切れるオーバースキャンと、横幅が狭まるアンダースキャン
    などがあり、
    テレビやプロジェクター側の設定で調整すると直る場合があるんですけど、
    ユーザーがなかなか気づかないことが多いと思うので、見切れても問題ないように、先ほどのように余白を設けたデザインにしたほうが良さそうです。。
  • あとはフォーカスして拡大しても問題ないようにパディングを設けてください、というのと、
  • 大きな要素でもコンテンツを1画面に伸ばさず、複数あるというヒントを残すと良いですと推奨しています。
  • こんな感じでレイアウトのテンプレートをAppleが公開しているので
    、これを参考にすると良いと思います。
  • はい、次にUIの部品についてざっと見ていきます
  • tvOSのTab Barはこんな感じで、上に表示されます。
    UITabBarControllerを使って実装すると簡単で
    多くのアプリのメニューはtabかサイドメニューとなっています
  • Table Viewはfocusするとdefaultでfocusアニメーションがついて拡大してくれます。
  • 次にコレクションViewですが、デフォルトではfocusアニメーションはありません。cellの中にImageViewがあればImage拡大の設定ができますが、cell自体のfocusアニメーションをつけたい場合は自分で実装する必要があります。
  • はい、SplitViewはこんな感じで、この例のように
    左がlistで右がコンテンツ表示というのが多いと思いますが、
    tvOSだと結構使用するレイアウトじゃないかと思います。
  • で、TextFieldで
  • keyboradはこんな感じで、TextFieldなどにfocusした時に表示されます。
  • UISearchBarはこんな感じです。
  • こちらはButtonです、AbemaTVではカスタムボタンをいくつか使っていますが
    systemボタンを使うとデフォルトでfocusアニメーションがついて拡大してくれたりするので、デザイン上カスタマイズする必要がなければ、systemボタンが良いと思います。
  • Navigation barは上にナビゲーションを表示します。
  • 例として純正の設定アプリがNavigation barを使って、現在いるnavigation階層のtitleを表示するように使用しています。
    navigationbarにtranslucentを設定していれば、レイアウトによってはnavigation領域にもコンテンツが表示されるので、グラデーションをかけて徐々にViewが消える感じを出しているアプリが多いように思います。
  • はい、あとはUIPageControlで、iOSとほとんど一緒ですね
  • これは、ローディングに使うUIActivityIndicatorViewです。
  • これはUIAlertControllerで、popupではなく画面全体に表示される感じになります。
  • はい、次にGestureについて説明します
  • 先程説明した通りApple TVのリモコンはTouch Surfaceと物理ボタンを使って操作します。
  • リモコンのTouch SurfaceのSwipe/Touch/Selectの操作は、GestureRecognizerやEventで検知できます。
  • UITapGestureRecognizerのallowedPressTypesにUIPressTypeを指定できます。up/down/left/rightはTouch Surfaceを左右上下の位置で軽く触る、selectはTouch Surfaceを押し込む、menu/playPauseは物理ボタンの押下で発生するイベントです。
  • PressTypeがmenuのGestureを扱うコードはこんな感じで
  • allowedPressTypesにUIPressType.menuを指定します
  • UISwipeGestureRecognizerはDirectionを指定できます。Touch Surfaceの左右上下のSwipeの入力イベントになります。
  • directionがupのSwipeGestureを扱うコードはこんな感じで
  • directionにupを指定します
  • 次にtvOSで一番重要なFocusについてです
  • iOSとtvOSの一番大きな違いとしてフォーカスがあります
    iOSデバイスの場合、ユーザーは直接、タッチスクリーン上でインターフェイスを操作しますが、Apple TVでは、リモコンで画面上のある項目にナビゲートした後、リモコンのボタンを押して選択する、といった操作になるので、画面上の各項目は、そこにナビゲートすると「フォーカスされた」状態になり、ユーザーアクションの対象になるといった動作になります。
  • フォーカスはいつ更新されるのかということですが、
  • 大きく分けて3つにないります
    ユーザーの操作と、システムが更新を要求したときと、アプリケーションが更新を要求したときです。
    システムが更新を要求するときはフォーカスされているビューを、ビュー階層から削除したときとか、UITableViewやUICollectionViewがデータを再読み込みしたときなどです。
  • このフォーカスの更新は誰がやっているのということですが、
  • フォーカスエンジンがフォーカスするViewを決定してくれています。フォーカスを更新できるのはフォーカスエンジンのみで、どのビューにフォーカスをあてるか直接指定するAPIなくて、フォーカスエンジンがフォーカスを更新します。
  • Focus Engineが検索する範囲は、現在FocusされているViewの大きさに応じて決まり、そのViewを起点として動きの方向にあるFocus可能な領域を見つけて更新します
  • Focus EngineはView階層のFocus動作を定義するUIFocusEnvironmentプロトコルに従ってFocusを制御します
  • UIFocusEnvironmentはFocus可能なItemの階層で構成されています
  • UIFocusEnvironmentプロトコルの定義はこのようになっています。
  • まずpreferredFocusEnvironmentsですが、アプリケーションが、どのビューにフォーカスをあてるべきか、これ使ってフォーカスエンジンに指定できます。
  • shouldUpdateFocusは、フォーカスエンジンが移動する前に、移動先が適切かどうかアプリが判断するタイミングになります。移動元と移動先のビューを含むフォーカス環境それぞれについて、shouldUpdateFocusInContext:メソッドを呼び出して、移動元のビュー、移動先のビュー、2つの親であるビュー、という順序で呼び出していきます。いずれかひとつでも、戻り値がfalseの場合があれば、移動は行われない動きになります
  • 次にこの2つですが、setNeedsFocusUpdateでフォーカスエンジンに更新をリクエスト、即座に更新したい場合はupdateFocusIfNeededを呼び出します。ここら辺はAutoLayoutと似てますね
  • didUpdateFocusはFocusされる時に呼ばれます
  • FocusEnvironmentのチェーンを辿って呼び出されていきます
  • 最後に tvOS11で追加されたsoundIdentifierForFocusUpdateですがこれを使うとフォーカス時の音を個別に変更できるようになります
  • はい、FocusEnvironmentに準拠しているクラスですが、
  • UIView, UIViewController, UIPresentationControllerがあります
  • 先程図にでてきましたが、FocusされるものはUIFocusItemプロトコルに準拠している必要があり、canBecomeFocusedを実装してあります。
  • デフォルトでフォーカスされるものはUIButton, UITextField、TableView, CollectionViewなどがあります。
  • 他にもtvOS10からはspriteKitのSKNode、tvOS11からはSceneKit のSCNNode などもあります
  • UILabelをフォーカスできるようにする例です
    canBecomeFocusedをoverrideしてtrueを返します
  • didUpdateFocusもoverrideしてフォーカス時の動きを付けます
  • フォーカス時にtransform scaleを1.4にして
  • アンフォーカス時にtransformを戻します
  • この実装で、このようなLabelを作ることができます
  • UITableViewとUICollectionViewのフォーカスですが、
  • Focus用のDelegatがtvOS用にあります
  • tableviewですが、
    フォーカス可能かどうかのcanFocusRowAt IndexPath
    フォーカスするべきかどうかのshouldUpdateFocusIn Context
    フォーカス時のdidUpdateFocusIn Context
    フォーカスエンジンに指定するindexPathForPreferredFocusedView
    の4つがtvOS用に追加されています
  • collectionViewもtableViewと同様のものプラスで、cellの移動やanimation時のdelegateがtvOS用に追加されています
  • あとはremembersLastFocusedIndexPathというpropertyがあり
    これは例えばtableViewから別のViewに移動後、またtableViewに戻った時に前回のindexにフォーカスしたい場合にtrueにしておきます
  • はい、次にUIImageViewのフォーカスですが
  • フォーカス用のUIImageのpropertyがあります
  • adjustsImageWhenAncestorFocusedをtrueにするとフォーカス時の拡大/縮小とparallaxアニメーションがつきます
  • あとはtvOS11から追加されたoverlayContentViewですが、
  • このようなadjustsImageWhenAncestorFocusedがtrueのimageViewが並んでる画面で、画像にblack alpha0.5のviewをかぶせて、フォーカス時は外したい場合の例です。
    tvOS10までだとフォーカス時の拡大縮小やparallaxが合わせるのが大変で、画像を合成したりしてけっこう手間なのですが、tvOS11以降はこれを使うと簡単にできます
  • コードの例になります
  • overlay用のviewをoverlayContentViewにaddSubViewして
  • collectionViewのdidUpdateFocusのdelegateでoverlayContentViewを表示/非表示します
  • フォーカス時にoverlayContentViewのalphaを0にして
  • アンフォーカス時にoverlayContentViewのalphaを1に戻します
  • この例のようにprogressViewを被せたい場合など、何か画像に被せたい場合に便利です
  • 次にtvOS11でフォーカス用のNotificationが追加されています
  • フォーカスさてた時のNotificationと
  • フォーカスの移動に失敗した時のNotificationです
  • 最後にUIFocusGuideについてです
  • UIFocusGuideはフォーカス用のガイドで、UILayoutGuideを継承していて、フォーカス用にpreferredFocusEnvironmentsが定義されています
  • 例えばこのような画面で
  • このCellからButtonへのフォーカスは
  • できるのですが、
  • こちらのCellからButtonの場合
  • デフォルトだとフォーカスできません
    これは先程説明しましたが、Focus Engineが検索する範囲は、現在FocusされているViewの大きさに応じて決まるからです
  • この場合にFocusGuideを追加ます
    FocusGuideをcollectionViewと同じ横幅で置き
  • preferredFocusEnvironmentsでbuttonを返すようにします
  • そうすると右の方からもfocusが可能になります
  • コードの例です
  • フォーカスガイドを作成してviewに追加します
  • topAnchorをbuttonのtopAnchorにleftAnchorをcollectionViewのleftAnchorにします
  • heightをbuttonのheightAnchor、widthをcollectionViewのwidthAnchorにして
  • preferredFocusEnvironmentsにbuttonを設定します
    レイアウトの指定はNSLayoutAnchorと同じような感じになります
  • Focusがうまくいかな場合は、canBecomeFocusedがfalseじゃないか、isHiddenがtrueになってないかなど基本的なことをまずチェックするとよいと思います。
  • あとtvOS11からdebug機能が強化されています
  • UIFocusUpdateLoggingEnabled YESを設定するとFocusに関するログがはかれるようになります
  • これは起動時のログですが、フォーカスエンジンの初期フォーカス決定のpreferredFocusチェーンのログがはかれています
  • あとはLLDBからUIFocusDebuggerを使ってdebugできます
    focusのstatusを確認できたり
  • フォーカス更新をシミュレートしたり
  • viewがフォカースできるか確認できたりと
    デバッグの機能も強化されて開発しやすくなっています
  • はい、最後にまとめです
  • - まず、標準のUI/UXに準拠しましょう、ということで基本的にtvOSはリモコンを使って操作するので、iOSほど自由度もなく、他のアプリも標準のUIを採用しているところが多いので、準拠しておくと無難です。
    - 次にiOSとの違いはFocusということで、今日いくつか説明させていただきましたがfocus周りは少し癖があります。
    -最後、tvOS対応は難しくないということで、フォーカスさえ押さえておけば普段iOS開発をしている方はすんなり開発できるのではないかと思います。最近けっこう日本のtvOSアプリも増えてきているので、いちユーザーとして嬉しく思っています
  • はい、以上になります。
    ありがとうございました!

×