UISplitViewController dalam potret pada iPhone menunjukkan detail VC alih-alih master

177

Saya menggunakan Universal Storyboard di Xcode 6, menargetkan iOS 7 ke atas. Saya telah menerapkan UISplitViewControlleryang sekarang didukung secara alami pada iPhone yang menjalankan iOS 8, dan Xcode akan secara otomatis mendukungnya untuk iOS 7. Ini berfungsi dengan sangat baik, kecuali ketika Anda meluncurkan aplikasi pada iPhone dalam potret yang menjalankan iOS 8, tampilan detail tampilan split ini controller ditampilkan ketika saya diharapkan untuk pertama kali melihat controller tampilan master. Saya percaya ini adalah bug dengan iOS 8 karena ketika Anda menjalankan aplikasi di iOS 7, itu benar menunjukkan controller tampilan master. Tetapi iOS 8 sekarang GM dan ini masih terjadi. Bagaimana saya bisa mengaturnya sehingga ketika pengontrol tampilan terpisah akan diciutkan (hanya satu pengontrol tampilan ditampilkan di layar), ketika pengontrol tampilan terpisah ditampilkan itu menunjukkan pengontrol tampilan master bukan detail?

Saya telah membuat pengontrol tampilan terpisah ini di Interface Builder. Pengontrol tampilan terpisah adalah pengontrol tampilan pertama dalam pengontrol tab bar. Baik master dan VC detail adalah pengontrol navigasi dengan pengontrol tampilan tabel yang tertanam di dalamnya.

Jordan H
sumber

Jawaban:

238

Oh man, ini membuat saya sakit kepala selama beberapa hari dan tidak tahu bagaimana melakukan ini. Bagian terburuknya adalah membuat proyek Xcode iOS baru dengan templat detail induk bekerja dengan baik. Untungnya, pada akhirnya, fakta kecil itu adalah bagaimana saya menemukan solusinya.

Ada beberapa posting yang saya temukan yang menyarankan bahwa solusinya adalah mengimplementasikan primaryViewControllerForCollapsingSplitViewController:metode baru UISplitViewControllerDelegate. Saya mencoba itu tidak berhasil. Apa yang dilakukan Apple dalam templat master-detail yang tampaknya berfungsi adalah mengimplementasikan splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:metode pendelegasian yang baru (tarik napas dalam-dalam untuk semua ini) (lagi aktif UISplitViewControllerDelegate). Menurut dokumen , metode ini:

Minta delegasi untuk menyesuaikan pengontrol tampilan primer dan untuk memasukkan pengontrol tampilan sekunder ke dalam antarmuka yang diciutkan.

Pastikan untuk membaca bagian diskusi dari metode itu untuk perincian yang lebih spesifik.

Cara Apple menangani ini adalah:

- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]]
        && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}

Implementasi ini pada dasarnya melakukan hal berikut:

  1. Jika secondaryViewControllerapa yang kami harapkan (a UINavigationController), dan itu menunjukkan apa yang kami harapkan (a DetailViewController- pengontrol tampilan Anda), tetapi tidak memiliki model ( detailItem), maka " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. Jika tidak, kembalilah " NOuntuk membiarkan pengontrol tampilan terpisah mencoba dan menggabungkan konten pengontrol tampilan sekunder ke dalam antarmuka yang diciutkan"

Hasilnya adalah sebagai berikut untuk iPhone dalam potret (baik mulai dalam potret atau memutar ke potret - atau lebih tepatnya kelas ukuran kompak):

  1. Jika pandangan Anda benar
    • dan memiliki model, tampilkan pengendali tampilan detail
    • tetapi tidak memiliki model, tunjukkan pengontrol tampilan master
  2. Jika pandangan Anda salah
    • tampilkan pengontrol tampilan master

Jelas seperti lumpur.

Tanda
sumber
8
Jawaban yang fantastis! Saya hanya subkelas UISplitViewControllerdan selalu kembali YESdari metode itu, lalu hanya mengubah kelas tampilan terpisah di Storyboard, karena saya selalu ingin menunjukkan master pada iPhone dalam potret. :)
Jordan H
2
Saya ingin pengontrol tampilan master saya disembunyikan jika "iPhone" dalam mode "Portrait" karena saya memiliki pengaturan pengontrol tampilan detail yang standar. Bagaimana saya bisa melakukan itu. Master dan detail saya keduanya bertipe VC. Secara khusus detail saya adalah MMDrawerController. Tolong bantu
Harshit Gupta
3
Saya mencoba saran Joey tentang subklas UISplitViewControllertetapi ternyata itu tidak berhasil: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:tidak pernah dipanggil. Alih-alih, saya menyalin template Apple dan meletakkannya di AppDelagate. Ini mengharuskan beberapa perubahan untuk membuat UISplitViewController application didFinishLaunchingWithOptions:juga (di mana saya juga menyalin templat Apple).
Nick
7
Komentar @ joey bekerja dengan menyetel self.delegate = self; di viewdidload! Dan menambahkan <UISplitViewControllerDelegate> di .h Terima kasih!
fellowworldcitizen
2
Ini sepertinya jawaban yang tepat untuk saya, karena saya memiliki masalah yang sama persis. Namun, untuk beberapa alasan saya splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:tidak pernah dipanggil. Tampaknya delegasi sedang mengatur applicationDidFinishLaunchingWithOptions:metode delegasi aplikasi saya dengan benar . Adakah orang lain yang melihat masalah ini dan BUKAN solusi ini berfungsi?
Tim Dean
60

Inilah jawaban yang diterima di Swift. Cukup buat subkelas ini dan tetapkan ke splitViewController Anda di storyboard Anda.

//GlobalSplitViewController.swift

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

  override func viewDidLoad() {
    super.viewDidLoad()

    self.delegate = self
  }

  func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController!, ontoPrimaryViewController primaryViewController: UIViewController!) -> Bool{
    return true
  }

}
Clifton Labrum
sumber
3
Hebat, ini banyak membantu. Namun muncul masalah baru. Tombol backbutton yang membawa saya ke master sekarang menghilang (tidak pernah menunjukkan). Bagaimana saya mendapatkannya kembali? EDIT: Tidak apa-apa, pikir diriku :-). Untuk pengguna lain: tambahkan ini di detailView: self.navigationItem.leftBarButtonItem = self.splitViewController? .DisplayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Tom Tallak Solbu
3
Sekarang di Swift apa pun itufunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark
2
Sepertinya metode delegasi hanya dipanggil ketika ukuran kelas kompak. Itu sedang dipanggil di iPhone, tetapi tidak di potret iPad, yang berarti itu tidak menyelesaikan masalah, karena potret iPad juga dalam mode runtuh. Diuji dengan iOS 12.1
Daniel
21

Versi cepat jawaban Mark S yang benar

Seperti yang disediakan oleh templat Master-Detail Apple.

func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.detailItem == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}

Klarifikasi

(Apa yang Mark S katakan agak membingungkan)

Metode delegasi ini disebut splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:, karena memang itulah fungsinya. Saat mengubah ke ukuran lebar yang lebih ringkas (misalnya saat memutar telepon dari landscape ke portrait), ia perlu mengecilkan pengontrol tampilan terbagi menjadi hanya satu di antaranya.

Fungsi ini mengembalikan boolean untuk memutuskan apakah harus menutup Detail dan menunjukkan Master atau tidak.

Jadi dalam kasus kami, kami akan memutuskan berdasarkan apakah ada detail yang dipilih atau tidak. Bagaimana kita tahu jika detail kita dipilih? Jika kita mengikuti templat Master-Detail Apple, pengontrol tampilan detail harus memiliki variabel opsional yang memiliki info detail, jadi jika nihil (.Tidak), belum ada yang dipilih dan kami harus memperlihatkan Master sehingga pengguna dapat memilih sesuatu.

Itu dia.

NiñoScript
sumber
Hanya untuk memperjelas mengapa saya memutar kembali dari sunting @ sschale. Kode itu adalah kutipan dari Apple's Master-Detail template, itu tidak dimaksudkan untuk menjadi hebat atau ringkas, hanya faktual. :)
NiñoScript
10

Dari dokumentasi , Anda perlu menggunakan delegasi untuk memberi tahu agar UISplitViewController tidak memasukkan tampilan detail ke dalam "antarmuka runtuh" ​​(yaitu "mode Potret" dalam kasing Anda). Di Swift 4, metode delegasi untuk menerapkan untuk yang telah diubah namanya:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}
oli
sumber
9
   #import <UIKit/UIKit.h>

    @interface SplitProductView : UISplitViewController<UISplitViewControllerDelegate>




    @end

.m:

#import "SplitProductView.h"
#import "PriceDetailTableView.h"

@interface SplitProductView ()

@end

@implementation SplitProductView

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.delegate = self;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[PriceDetailTableView class]]

        //&& ([(PriceDetailTableView *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)

        ) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}
@end
Gank
sumber
9

Aplikasi saya ditulis dalam Swift 2.x dan bisa berjalan dengan baik. Setelah mengubahnya menjadi Swift 3.0 (menggunakan XCode converter), ia mulai menampilkan detail terlebih dahulu alih-alih master dalam mode potret. Masalahnya adalah nama fungsi splitViewController tidak berubah agar sesuai dengan yang baru dari UISplitViewControllerDelegate.

Setelah mengubah nama fungsi itu secara manual, aplikasi saya sekarang dapat berfungsi dengan benar:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.game == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}
Tony
sumber
Saya memiliki masalah yang sama dengan Anda, tetapi saya tidak mengerti solusi Anda. Saya tidak melihat adanya perubahan pada kode yang Anda posting di sini. Bisakah kamu lebih spesifik. Terima kasih
bibscy
Banyak metode yang sedikit diganti namanya.
Dave
Jawaban Tony adalah sintaks Swift 3 untuk jawaban @ NiñoScript (yang ditulis untuk versi Swift sebelumnya)
Hellojeffy
2
untuk cepat 3, jangan lupa untuk menempatkan self.delegate = selfpada viewDidLoadmetode.
Fer
7

Jika Anda tidak memiliki nilai default untuk ditampilkan di controller tampilan detail, Anda bisa menghapus segue default antara SplitViewController dan UIViewController detail Anda di story board. Ini akan membuatnya selalu masuk ke Master View Controller terlebih dahulu.

Efek sampingnya adalah alih-alih melihat dua tampilan dalam lanskap, Anda akan melihat satu tampilan dalam ukuran penuh di SplitViewController hingga Show Detail Segue di master view controller diaktifkan.

Hao-Cher Hong
sumber
trik yang bagus. Aplikasi saya hanya dalam mode potret dan saya bisa melakukan ini.
Peacemoon
Ini benar kecuali dalam orientasi Lansekap Anda akan melihat bagian kanan kosong tampilan mungkin berwarna abu-abu.
vedrano
4

Untuk semua orang yang tidak dapat menemukan bagian friday cs193p:

Di Swift 3.1.1 membuat subclass dari UISplitViewController dan mengimplementasikan salah satu metode pendelegasiannya bekerja untuk saya seperti pesona:

class MainSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
}

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
    return true
} }

Papan cerita saya

Bartosz Kunat
sumber
Seperti @olito tunjukkan, dalam Swift 4 sintaks untuk ini telah berubah menjadi: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske
3

Menurut pendapat saya, Anda harus menyelesaikan masalah ini lebih umum. Anda dapat subkelas UISplitViewController dan mengimplementasikan protokol di pengontrol tampilan tertanam.

class MasterShowingSplitViewController: UISplitViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension MasterShowingSplitViewController: UISplitViewControllerDelegate {
    func splitViewController(splitViewController: UISplitViewController,
                             collapseSecondaryViewController secondaryViewController: UIViewController,
                             ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
        guard let masterNavigationController = primaryViewController as? UINavigationController,
                  master = masterNavigationController.topViewController as? SplitViewControllerCollapseProtocol else {
            return true
        }
        return master.shouldShowMasterOnCollapse()

    }
}

protocol SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool
}

Contoh implementasi di UITableViewController:

extension SettingsTableViewController: SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool {
        return tableView.indexPathForSelectedRow == nil
    }
}

Semoga ini bisa membantu. Jadi Anda bisa menggunakan kembali kelas ini dan hanya perlu mengimplementasikan protokol.

Maik639
sumber
Metode delegasi tidak pernah dipanggil!
K_Mohit
itu tidak disebut di iPad dan iPhone 6/7/8 Plus. Apakah itu masalahmu? Lihat di: stackoverflow.com/questions/29767614/…
Maik639
2

Hapus saja DetailViewController dari pengontrol SplitView saat Anda memerlukannya untuk memulai dari Master.

UISplitViewController *splitViewController = (UISplitViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"SETTINGS"];
splitViewController.delegate = self;
[self.navigationController presentViewController:splitViewController animated:YES completion:nil];
if (IPHONE) {
    NSMutableArray * cntlrs = [splitViewController.viewControllers mutableCopy];
    [cntlrs removeLastObject];
    splitViewController.viewControllers = cntlrs;
}
Borys Shcherbyna
sumber
2

Ini bekerja untuk saya di iOS-11 dan Swift 4:

//Following code in application didFinishLaunching (inside Application Delegate)
guard let splitViewController = window?.rootViewController as? UISplitViewController,
            let masterNavVC = splitViewController.viewControllers.first as? UINavigationController,
            let masterVC = masterNavVC.topViewController as? MasterViewController
else { fatalError() }
splitViewController.delegate = masterVC

//Following code in MasterViewController class
extension MasterViewController:UISplitViewControllerDelegate {
    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true
    }
}
Vishal Chaudhry
sumber
2

Fungsi ini diganti namanya dalam versi baru Swift, jadi kode ini berfungsi pada Swift 4:

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true
    }

}
Saeed Ir
sumber
0

Solusi Xamarin / C #

public partial class MainSplitViewController : UISplitViewController
{
    public MainSplitViewController(IntPtr handle) : base(handle)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        Delegate = new MainSplitViewControllerDelegate();
    }
}

public class MainSplitViewControllerDelegate : UISplitViewControllerDelegate
{
    public override bool CollapseSecondViewController(UISplitViewController splitViewController, UIViewController secondaryViewController, UIViewController primaryViewController)
    {
        return true;
    }
}
Mark Moeykens
sumber
0

Cukup atur preferredDisplayModeproperti UISplitViewControllerke.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Arash Etemad
sumber