Xcodeのマスター/ディテールテンプレートに基づいたiOS7アプリケーションがあり、iOS8に移植しています。大きく変わった領域の1つはUISplitViewController
です。
ポートレートモードの場合、ユーザーが詳細ビューコントローラーをタップすると、マスタービューコントローラーは閉じられます。
また、ユーザーが行をタップした場合、プログラムでマスタービューコントローラーを非表示にできるようにしたいと思います。
IOS 7では、マスターView Controllerはポップオーバーとして表示され、次のように非表示にすることができました:
[self.masterPopoverController dismissPopoverAnimated:YES];
IOS 8では、マスターはポップオーバーではなくなったため、上記の手法は機能しません。
マスターView Controllerを閉じようとしました:
self.dismissViewControllerAnimated(true, completion: nil)
または、Split View Controllerに詳細View Controllerを表示するように指示します。
self.splitViewController?.showDetailViewController(bookViewController!, sender: self)
しかし、これまでのところ何も機能していません。何か案は?
UISplitViewControllerを次のように拡張します。
extension UISplitViewController {
func toggleMasterView() {
let barButtonItem = self.displayModeButtonItem()
UIApplication.sharedApplication().sendAction(barButtonItem.action, to: barButtonItem.target, from: nil, forEvent: nil)
}
}
didSelectRowAtIndexPath
またはprepareForSegue
で、次を実行します。
self.splitViewController?.toggleMasterView()
これにより、マスタービューがスムーズにスライドします。
this post からdisplayModeButtonItem()を使用するというアイデアを得て、 this post ごとにタップをシミュレートしています。
ハッキングのように思えるので、私はこのソリューションに本当に満足していません。しかし、それはうまく機能し、まだ代替手段がないようです。
preferredDisplayMode
を使用します。 didSelectRowAtIndexPath
またはprepareForSegue
で:
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
self.splitViewController?.preferredDisplayMode = .Automatic
残念ながら、次のようなドキュメントがあるにもかかわらず、マスタービューはスライドする代わりに突然消えます。
このプロパティの値を変更すると、現在の表示モードが実際に変更される場合、Split View Controllerは結果の変更をアニメーション化します。
実際に変更をアニメーション化するより良い方法があればいいのですが。
MasterViewController
の- prepareForSegue:sender:
メソッドに次のコードを追加することで、Xcode 6.3マスター/ディテールアプリケーション(ユニバーサル)プロジェクトで目的の動作を実現できました。
if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
完全な- prepareForSegue:sender:
実装は次のようになります。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
if view.traitCollection.userInterfaceIdiom == .Pad && splitViewController?.displayMode == .PrimaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
}
}
}
traitCollection
を使用することは、一部のプロジェクトでdisplayMode
の代替/補足になる場合もあります。たとえば、次のコードはXcode 6.3マスター/ディテールアプリケーション(ユニバーサル)プロジェクトでも機能します。
let traits = view.traitCollection
if traits.userInterfaceIdiom == .Pad && traits.horizontalSizeClass == .Regular {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
let completion: Bool -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .Automatic
}
UIView.animateWithDuration(0.3, animations: animations, completion: completion)
}
以下のコードは、アニメーションでマスタービューを非表示にします
UIView.animateWithDuration(0.5) { () -> Void in
self.splitViewController?.preferredDisplayMode = .PrimaryHidden
}
ここにリストされている答えを少し改善するだけで、次のコードが適切に機能し、アニメーションもスムーズに処理されます:
extension UISplitViewController {
func toggleMasterView() {
var nextDisplayMode: UISplitViewControllerDisplayMode
switch(self.preferredDisplayMode){
case .PrimaryHidden:
nextDisplayMode = .AllVisible
default:
nextDisplayMode = .PrimaryHidden
}
UIView.animateWithDuration(0.5) { () -> Void in
self.preferredDisplayMode = nextDisplayMode
}
}
}
そして、前述のように、View Controllerのどこでも拡張機能を使用するだけです
self.splitViewController?.toggleMasterView()
Swift 4アップデート:
Prepareに挿入します(セグエの場合:...
if splitViewController?.displayMode == .primaryOverlay {
let animations: () -> Void = {
self.splitViewController?.preferredDisplayMode = .primaryHidden
}
let completion: (Bool) -> Void = { _ in
self.splitViewController?.preferredDisplayMode = .automatic
}
UIView.animate(withDuration: 0.3, animations: animations, completion: completion)
}
試してみる
let svc = self.splitViewController svc.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
上記の回答を変更するだけで、ビューを構成した詳細ビューコントローラーのメソッドで必要になります。
[self.splitViewController setPreferredDisplayMode:UISplitViewControllerDisplayModePrimaryHidden];
もちろん、アニメーションの恵みを欠いています。
iPadの場合、このようなメニューボタンを追加します
UIBarButtonItem *menuButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"burger_menu"]
style:UIBarButtonItemStylePlain
target:self.splitViewController.displayModeButtonItem.target
action:self.splitViewController.displayModeButtonItem.action];
[self.navigationItem setLeftBarButtonItem:menuButtonItem];
これは、ランドスケープモードとポートレートモードの両方でうまく機能します。プログラムでポップオーバーvcを閉じるには、このようなボタンアクションを強制する必要があります。
[self.splitViewController.displayModeButtonItem.target performSelector:appDelegate.splitViewController.displayModeButtonItem.action];
Swift 1.2での私の解決策
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
var screen = UIScreen.mainScreen().currentMode?.size.height
if (UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad) || screen >= 2000 && UIDevice.currentDevice().orientation.isLandscape == true && (UIDevice.currentDevice().userInterfaceIdiom == .Phone){
performSegueWithIdentifier("showDetailParse", sender: nil)
self.splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.PrimaryHidden
} else if (UIDevice.currentDevice().userInterfaceIdiom == .Phone) {
performSegueWithIdentifier("showParse", sender: nil)
}
}