Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

意外と苦労する、一部の画面のみ ランドスケープ表示を許容する方法 (potatotips 第17回)

9,375 views

Published on

意外と苦労する、一部の画面のみランドスケープ表示を許容する方法

Published in: Engineering
  • Be the first to comment

意外と苦労する、一部の画面のみ ランドスケープ表示を許容する方法 (potatotips 第17回)

  1. 1. 意外と苦労する、一部の画面のみ ランドスケープ表示を許容する方法 2015/05/13【第17回】potatotips(iOS/Android開発Tips共有会)
  2. 2. 自己紹介 • Twitter: @_mono, Facebook: mono0926 • みんなでつくるスポーツニュースアプリ『Player!』 • 先月リリースして日々アップデート中 • LINEクリエイターズスタンプ作って販売中 • http://bit.ly/love-stamp • 昨年6月からずっとSwift触ってます
  3. 3. 一部の画面だけ回転(ランドスケープ表示)を 許容させるのつらい • 全部許容 or 一部の画面だけ非許容は簡単 • Stackoverflowやブログなどで色々情報あるが、 それぞれ苦労している感があるし、画面構成に よってはうまく動かなかったりでつらい • カテゴリ拡張でオーバーライド
 もどきする方法微妙(後述)
  4. 4. 愚直な方法 • AppDelegateのsupportedInterfaceOrientationsForWindowにて縦横許容 • 縦固定にしたい各UIViewControllerのsupportedInterfaceOrientationsで 回転を制限 • ルートのViewControllerの設定が効くので、UINavigationControllerや UITabBarControllerなど使っている場合はわざわざ継承して、かつ現 在表示中のViewControllerを見て適切な値を返す • わざわざ継承するの面倒 • 漏れなく実装するのが大変・デグりやすい
  5. 5. 統一的な方法として ちょくちょく見かけるやり方 • UIViewControllerカテゴリ拡張でオーバーライドもどき • func supportedInterfaceOrientations() -> Int などを • カテゴリ拡張は現状、挙動的には以下の順に優先されるが、仕様的には不定 なので極力避けたい 1. カスタムクラスでの実装 2. カテゴリによるクラス拡張での実装 3. デフォルト実装(プロジェクト設定通りになる) • selfがルートのViewControllerだったら表示中のViewControllerを っていっ て、そこに定義してある実装を返す
  6. 6. AppDelegateの application:supportedInterfaceOrientationsForWindow: に処理を集約するやり方 • 実装している場合、まずこれが呼ばれるので、その時の画面 状態によって回転可能な向きを絞るようにすれば AppDelegateに処理をまとめられる • アプリの状態はどのように判別する??
  7. 7. アプリの状態は どのように判別するか • rootのViewControllerから、最前面の画面を る • selectedViewController • topViewController • presentedViewControler • visibleViewController • アプリの画面構成に依存してしまう • がんばれば汎用的にも書けるかも?
  8. 8. 今回やってみた方法: Aspectsによ るMethod Swizzling • Aspectsとは • Method Swizzlingのラッパーライブラリ • 特定クラスインスタンスのメソッド実行の前後に処理を挟んだり、すり替えたりが簡単に出来る • GitHubのStarも1600を超えている信頼出来そうなライブラリ • Google Analyticsの埋め込みなどにも便利 • Method Swizzlingもトリッキーな方法ではあるが、カテゴリ拡張でのオーバーライドより筋が良い
  9. 9. 具体的な実装 • 縦横回転許容するViewControllerにてallowRotationプロパティを trueにする • allowRotationプロパティは、カテゴリ拡張によって定義した Associated Object private var allowRotationKey: UInt8 = 0 extension UIViewController { var allowRotation: Bool { get { return (objc_getAssociatedObject(self, &allowRotationKey) as? Bool) ?? false } set { objc_setAssociatedObject(self, &allowRotationKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN)) } } } class ModalViewController1: UIViewController { override func viewDidLoad() { super.viewDidLoad() allowRotation = true } }
  10. 10. 具体的な実装 • AppDelegateでAspectsを使ってViewController に回転周りの実装を注入 class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var allowRotation = false func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // 起動時にAspectsの設定 hookForRotation() return true } /** 適切なタイミングで回転をハンドリング */ private func hookForRotation() { // 次のスライドで } func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int { // 常にに横許容 return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue) } }
  11. 11. 具体的な実装 /** 適切なタイミングで回転をハンドリング */ private func hookForRotation() { ObjcHelper.aspect_viewControllerHookSelector("viewWillAppear:", withOptions: .PositionBefore, error: nil) { info in let vc = info.instance() as UIViewController self.allowRotation = vc.allowRotation } // 非表示時にAppDelegateのallowRotationを切り替え ObjcHelper.aspect_navigationControllerHookSelector("popViewControllerAnimated:", withOptions: .PositionBefore, error: nil) { info in let vc = info.instance() as UINavigationController let vcs = vc.viewControllers self.allowRotation = (vcs[vcs.count - 2] as UIViewController).allowRotation } ObjcHelper.aspect_viewControllerHookSelector("dismissViewControllerAnimated:completion:", withOptions: .PositionBefore, error: nil) { info in let vc = info.instance() as UIViewController if let presentingViewController = vc.presentingViewController { self.allowRotation = presentingViewController.allowRotation } } // supportedInterfaceOrientationsの実装をすり替え ObjcHelper.aspect_viewControllerHookSelector("supportedInterfaceOrientations", withOptions: .PositionInstead, error: nil) { info in let vc = info.instance() as UIViewController let invocation = info.originalInvocation() var ret = Int(self.allowRotation ? UIInterfaceOrientationMask.AllButUpsideDown.rawValue : UIInterfaceOrientationMask.Portrait.rawValue) invocation.setReturnValue(&ret) } }
  12. 12. iPad, iOS 6/7は? • 適用したプロジェクトがiPhone・iOS8オンリー なので、あまり深くチェック出来ていませんが、 現状 +アレンジ程度で正常に動くようになると思 います • iOS5以前は回転の仕組みが違うので別対応必要 • ぐぐる時もこの古い情報がまだけっこう引っか かるので注意
  13. 13. アレンジ • 一部の特殊な画面では、さらに追加実装が必要になるが、 それに耐えうるか? • 例: WebViewでの動画フルスクリーン再生 • YouTubeなどの動画が配置されている画面で、再生ボタン を押すとフルスクリーンになるが、その時だけ回転を許 容したい • AppDelegateを弄るだけで対応出来た(最後のスライド に載せたレポジトリのソース参照)
  14. 14. その他回転周りの注意点 • プロジェクト設定で縦横許容すると、ランドスケープ対応端末(iPhone 6 Plusなど)で横 画面にして起動するとスプラッシュ画面が真っ黒になる • 全体がランドスケープ対応の場合: • ランドスケープ用のスプラッシュ画像を配置 • 非対応の場合: • プロジェクト設定は縦のみにして、AppDelegateの supportedInterfaceOrientationsForWindowにて縦横許容とすると、起動時にちゃ んと縦用のスプラッシュ画面が出る
  15. 15. ソースコード • https://github.com/mono0926/ios-rotation-sandbox • 開発中のアプリは副作用があってハマったり、ビルド時 間かかったりするので、検証環境を用意すると る • ビルド時間はSwift 1.2のインクリメントビルドで、 ちょっとした変更後のビルドは体感10倍くらいになっ たものの • 今回紹介した方法をベースにアレンジする場合などにもご 活用ください

×