[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
SlideShare a Scribd company logo
意外と苦労する、一部の画面のみ
ランドスケープ表示を許容する方法
2015/05/13【第17回】potatotips(iOS/Android開発Tips共有会)
自己紹介
• Twitter: @_mono, Facebook: mono0926
• みんなでつくるスポーツニュースアプリ『Player!』
• 先月リリースして日々アップデート中
• LINEクリエイターズスタンプ作って販売中
• http://bit.ly/love-stamp
• 昨年6月からずっとSwift触ってます
一部の画面だけ回転(ランドスケープ表示)を
許容させるのつらい
• 全部許容 or 一部の画面だけ非許容は簡単
• Stackoverflowやブログなどで色々情報あるが、
それぞれ苦労している感があるし、画面構成に
よってはうまく動かなかったりでつらい
• カテゴリ拡張でオーバーライド

もどきする方法微妙(後述)
愚直な方法
• AppDelegateのsupportedInterfaceOrientationsForWindowにて縦横許容
• 縦固定にしたい各UIViewControllerのsupportedInterfaceOrientationsで
回転を制限
• ルートのViewControllerの設定が効くので、UINavigationControllerや
UITabBarControllerなど使っている場合はわざわざ継承して、かつ現
在表示中のViewControllerを見て適切な値を返す
• わざわざ継承するの面倒
• 漏れなく実装するのが大変・デグりやすい
統一的な方法として
ちょくちょく見かけるやり方
• UIViewControllerカテゴリ拡張でオーバーライドもどき
• func supportedInterfaceOrientations() -> Int などを
• カテゴリ拡張は現状、挙動的には以下の順に優先されるが、仕様的には不定
なので極力避けたい
1. カスタムクラスでの実装
2. カテゴリによるクラス拡張での実装
3. デフォルト実装(プロジェクト設定通りになる)
• selfがルートのViewControllerだったら表示中のViewControllerを っていっ
て、そこに定義してある実装を返す
AppDelegateの
application:supportedInterfaceOrientationsForWindow:
に処理を集約するやり方
• 実装している場合、まずこれが呼ばれるので、その時の画面
状態によって回転可能な向きを絞るようにすれば
AppDelegateに処理をまとめられる
• アプリの状態はどのように判別する??
アプリの状態は
どのように判別するか
• rootのViewControllerから、最前面の画面を る
• selectedViewController
• topViewController
• presentedViewControler
• visibleViewController
• アプリの画面構成に依存してしまう
• がんばれば汎用的にも書けるかも?
今回やってみた方法: Aspectsによ
るMethod Swizzling
• Aspectsとは
• Method Swizzlingのラッパーライブラリ
• 特定クラスインスタンスのメソッド実行の前後に処理を挟んだり、すり替えたりが簡単に出来る
• GitHubのStarも1600を超えている信頼出来そうなライブラリ
• Google Analyticsの埋め込みなどにも便利
• Method Swizzlingもトリッキーな方法ではあるが、カテゴリ拡張でのオーバーライドより筋が良い
具体的な実装
• 縦横回転許容する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
}
}
具体的な実装
• 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)
}
}
具体的な実装
/** 適切なタイミングで回転をハンドリング */
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)
}
}
iPad, iOS 6/7は?
• 適用したプロジェクトがiPhone・iOS8オンリー
なので、あまり深くチェック出来ていませんが、
現状 +アレンジ程度で正常に動くようになると思
います
• iOS5以前は回転の仕組みが違うので別対応必要
• ぐぐる時もこの古い情報がまだけっこう引っか
かるので注意
アレンジ
• 一部の特殊な画面では、さらに追加実装が必要になるが、
それに耐えうるか?
• 例: WebViewでの動画フルスクリーン再生
• YouTubeなどの動画が配置されている画面で、再生ボタン
を押すとフルスクリーンになるが、その時だけ回転を許
容したい
• AppDelegateを弄るだけで対応出来た(最後のスライド
に載せたレポジトリのソース参照)
その他回転周りの注意点
• プロジェクト設定で縦横許容すると、ランドスケープ対応端末(iPhone 6 Plusなど)で横
画面にして起動するとスプラッシュ画面が真っ黒になる
• 全体がランドスケープ対応の場合:
• ランドスケープ用のスプラッシュ画像を配置
• 非対応の場合:
• プロジェクト設定は縦のみにして、AppDelegateの
supportedInterfaceOrientationsForWindowにて縦横許容とすると、起動時にちゃ
んと縦用のスプラッシュ画面が出る
ソースコード
• https://github.com/mono0926/ios-rotation-sandbox
• 開発中のアプリは副作用があってハマったり、ビルド時
間かかったりするので、検証環境を用意すると る
• ビルド時間はSwift 1.2のインクリメントビルドで、
ちょっとした変更後のビルドは体感10倍くらいになっ
たものの
• 今回紹介した方法をベースにアレンジする場合などにもご
活用ください

More Related Content

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