UISplitViewController в портреті на iPhone показує деталізацію VC замість головного


177

Я використовую Universal Xbox 6 в Xcode 6, націлений на iOS 7 і вище. Я реалізував функцію, UISplitViewControllerяка зараз підтримується на iPhone під керуванням iOS 8, і Xcode автоматично підтримує її для iOS 7. Працює дуже добре, за винятком випадків, коли ви запускаєте додаток на iPhone у портретному режимі iOS 8, детальний вигляд розділеного перегляду Контролер відображається, коли я очікував вперше побачити контролер головного перегляду. Я вважав, що це помилка з iOS 8, оскільки коли ви запускаєте додаток на iOS 7, він правильно показує головний контролер перегляду. Але iOS 8 зараз є GM, і це все ще відбувається. Як я можу налаштувати його так, що коли контролер розділеного перегляду буде згортатися (на екрані відображається лише один контролер перегляду), коли відображається контролер розділеного перегляду, він показує контролеру головного перегляду не деталі?

Я створив цей контролер розділеного перегляду в Interface Builder. Контролер розділеного перегляду - це перший контролер перегляду в контролері панелі вкладок. Як головний, так і детальний ВК є контролерами навігації з вбудованими всередину контролерами подання таблиці.

Відповіді:


238

О людино, це викликало у мене головний біль протягом декількох днів і не міг зрозуміти, як це зробити. Найгірше було те, що створення нового проекту XOS iOS з шаблоном детальної інформації про майстер добре працювало. На щастя, врешті-решт, цей маленький факт полягав у тому, як я знайшов рішення.

Я знайшов кілька публікацій, які підказують, що рішення полягає в тому, щоб реалізувати новий primaryViewControllerForCollapsingSplitViewController:метод UISplitViewControllerDelegate. Я намагався це безрезультатно. Що Apple робить у шаблоні детальної інформації, який, здається, працює, це реалізувати новий (зробити глибокий вдих, щоб сказати все це) splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:делегатський метод (знову ж таки UISplitViewControllerDelegate). Згідно з документами , цей метод:

Просить делегата налаштувати основний контролер подання та включити вторинний контролер подання в згорнутий інтерфейс.

Не забудьте прочитати в дискусійній частині цього методу більш детальну інформацію.

Apple впорається з цим:

- (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;

    }
}

Ця реалізація в основному робить наступне:

  1. Якщо secondaryViewControllerце те, що ми очікуємо (a UINavigationController), і це показує, що ми очікуємо (a DetailViewController- ваш контролер перегляду), але не має моделі ( detailItem), то " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. В іншому випадку поверніться, " NOщоб дозволити контролеру розділеного вигляду спробувати включити вміст вторинного контролера перегляду в згорнутий інтерфейс"

Результати наступні для iPhone в портреті (починаючи з портрета або повертаючись до портрета - або більш точно компактного розміру):

  1. Якщо ваш погляд правильний
    • і має модель, покажіть контролер перегляду деталей
    • але немає моделі, покажіть контролер головного перегляду
  2. Якщо ваш погляд невірний
    • показати контролер головного перегляду

Ясно, як грязь.


8
Фантастична відповідь! Я просто підкласифікуюсь UISplitViewControllerі завжди повертаюся YESз цього методу, а потім просто змінюю клас розділеного перегляду на Storyboard, як завжди хочу показати майстра на iPhone в портреті. :)
Йорданія H

2
Я хочу, щоб мій контролер головного перегляду був прихованим, якщо "iPhone" перебуває в режимі "Портрет", тому що у мене налаштування контролера перегляду деталей за замовчуванням. Як я можу це зробити. Мій майстер і деталі обидва типу VC. Зокрема, моя деталь - MMDrawerController. Будь ласка, допоможіть
Харшит Гупта,

3
Я спробував пропозицію Джої щодо підкласифікації, UISplitViewControllerале виявив, що це не спрацювало: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:ніколи не дзвонили. Натомість я скопіював шаблон Apple і помістив його в AppDelagate. Це вимагало декількох змін для створення UISplitViewController application didFinishLaunchingWithOptions:також (куди я також скопіював шаблон Apple).
Нік

7
@ joey коментар працює з налаштуваннями self.delegate = self; у viewdidload! І додавання <UISplitViewControllerDelegate> у .h Спасибі!
fellowworldcitizen

2
Це здається правильною відповіддю для мене, оскільки я маю саме таку проблему. Однак чомусь мене splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:ніколи не дзвонять. Здається, що делегат правильно встановлює applicationDidFinishLaunchingWithOptions:метод мого делегата програми . Хтось ще бачив цю проблему і НЕ працював з цим рішенням?
Тім Дін

60

Ось прийнята відповідь у Swift. Просто створіть цей підклас і призначте його своєму splitViewController у вашій дошці.

//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
  }

}

3
Чудово, це дуже допомагає. Але виникла нова проблема. Кнопка, яка веде мене до майстра, тепер зникає (ніколи не показується). Як повернути його? EDIT: Неважливо, зрозумів сам :-). ? Для інших користувачів: додайте це в DetailView: self.navigationItem.leftBarButtonItem = self.splitViewController .displayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = вірно
Том Tallak Solbu

3
Тепер у Swift, що б там не булоfunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark

2
Схоже, що метод делегата викликається лише тоді, коли розмір класу є компактним. Його дзвонять на iPhone, але не на портрет iPad, що означає, що це не вирішує проблему, оскільки портрет iPad також знаходиться в режимі згортання. Тестовано з iOS 12.1
Даніель,

21

Швидка версія правильної відповіді Mark S

Як передбачено шаблоном Apple-Master-Detail.

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
}

Уточнення

(Те, що сказав Марк S, було дещо заплутаним)

Цей метод делегата називається splitViewController: collapseSecondaryViewController: ontoPrimaryViewController: , тому що це робиться. При зміні на більш компактний розмір ширини (наприклад, при обертанні телефону від пейзажу до портрета) йому потрібно згортати контролер розділеного перегляду лише на один з них.

Ця функція повертає булеве значення, щоб вирішити, чи слід згортати деталі та показувати Майстру чи ні.

Тож у нашому випадку ми вирішимо, виходячи з того, була обрана деталь чи ні. Як ми можемо знати, чи обрана наша деталь? Якщо ми будемо слідувати шаблону Apple-Master-Detail Apple, контролер перегляду деталей повинен мати додаткову змінну, що містить інформацію про деталі, тому якщо це нуль (.None), нічого ще не вибрано, і ми повинні показати Master, щоб користувач міг щось вибрати.

Це воно.


Просто для уточнення, чому я відкотився від редагування @ sschale. Цей код є цитатою Apple's Master-Detail template, він не має бути великим чи стислим, а фактичним. :)
NiñoScript

10

З документації вам потрібно скористатися делегатом, щоб вказати UISplitViewController не включати детальний вигляд у "згорнутий інтерфейс" (тобто "режим портрету" у вашому випадку). У Swift 4 метод делегата, який потрібно реалізувати, був перейменований:

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

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

9

Моя програма написана на Swift 2.x і може добре працювати. Після перетворення його в Swift 3.0 (за допомогою перетворювача XCode) він починає показувати деталі спочатку замість майстра в портретному режимі. Проблема полягає в тому, що ім'я функції splitViewController не змінено, щоб відповідати новій UISplitViewControllerDelegate.

Після зміни назви цієї функції вручну мій додаток може працювати правильно:

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
}

У мене є та сама проблема, що і у вас, але я не розумію вашого рішення. Я не бачу змін у коді, який ви розмістили тут. Чи можете ви бути більш конкретними. Спасибі
бідність

Багато методів трохи перейменовані.
Дейв

Відповідь Тоні - це синтаксис Swift 3 до відповіді @ NiñoScript (який написаний для попередніх версій Swift)
Hellojeffy

2
для стрижа 3, не забудьте поставити self.delegate = selfна viewDidLoadметод.
Фер

7

Якщо у вас немає значень за замовчуванням, щоб детально показувати контролер перегляду, ви можете просто видалити відомість за замовчуванням між SplitViewController і вашим детальним UIViewController на дошці розповідей. Це зробить це завжди в першу чергу переходить у головний контролер.

Побічний ефект цього полягає в тому, що замість того, щоб побачити два види в ландшафті, ви побачите один вигляд у повному розмірі в SplitViewController, доки в контролері головного перегляду не буде показано деталізацію Segue.


хороший трюк. Моя програма працює лише в портретному режимі, і я можу це зробити.
Мирний місяць

Це вірно, за винятком орієнтації на пейзаж ви побачите порожню праву частину подання, можливо, заповнену сірим кольором.
ведрано

4

Для всіх людей, які не змогли знайти розділ п'ятниці cs193p:

У Swift 3.1.1 створення підкласу UISplitViewController та реалізація одного з його методів делегування працювали на мене як на принадність:

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
} }

Моя розповідь


Як вказував @olito, у Swift 4 синтаксис цього змінився на: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske

3

На мою думку, ви повинні вирішити цю проблему більш загально. Ви можете підкласифікувати UISplitViewController і реалізувати протокол у вбудованих контролерах перегляду.

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
}

Приклад реалізації в UITableViewController:

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

Сподіваюся, це допомагає. Таким чином, ви можете повторно використовувати цей клас і просто потрібно реалізувати протокол.


Метод делегата ніколи не викликається!
K_Mohit

це не називається на iPad та iPhone 6/7/8 Plus. Це ваша проблема? Подивіться: stackoverflow.com/questions/29767614 / ...
Maik639

2

Просто видаліть DetailViewController з контролерів SplitView, коли це потрібно для запуску з 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;
}

2

Це працювало для мене на iOS-11 та 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
    }
}

2

Функція перейменована в нові версії Swift, тому цей код працює на 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
    }

}

0

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;
    }
}

0

Просто встановіть preferredDisplayModeвластивість UISplitViewControllerв.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.