Как переопределить коллекцию признаков для исходного UIViewController? (с раскадровкой)

У меня есть приложение, ориентированное на iOS8, а начальный контроллер представления - UISplitViewController. Я использую раскадровку, так что она любезно создает для меня все.

Из-за моего дизайна мне нужен SplitViewController для отображения как основного, так и подробного представления в портретном режиме на iPhone. Итак, я ищу способ переопределить коллекцию признаков для этого UISplitViewController.

Я обнаружил, что могу использовать

 override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) { ... }

но, к сожалению, есть только методы для переопределения коллекций признаков дочерних контроллеров:

setOverrideTraitCollection(collection: UITraitCollection!, forChildViewController childViewController: UIViewController!)

и я не могу сделать это для себя в своем подклассе UISplitViewController.

Я проверил пример приложения Adaptive Photos от Apple. И в этом приложении автор использует специальный TraitOverrideViewController как root и некоторую магию в его установщике viewController, чтобы все это работало.

Для меня это выглядит ужасно. Есть ли способ переопределить черты? Или, если нет, как я могу использовать тот же хак с раскадровкой? Другими словами, как ввести некоторый viewController в качестве корневого только для обработки черт для моего UISplitViewController с раскадровкой?


person Ilya Belikin    schedule 25.08.2014    source источник
comment
Почему это выглядит ужасно? AAPLTraitOverrideViewController.m - это чуть больше 20 строк кода. Вам понадобится около 10 минут, чтобы перевести это на Swift, и тогда вам не придется снова смотреть на это.   -  person Mundi    schedule 25.08.2014
comment
@Mundi см. Обновление. И нет, я не верю, что такой код можно будет бесплатно поддерживать. Наконец, теперь мне нужно объяснить моим колледжам, что это такое и зачем он нам нужен. Я не могу рассуждать о каждой строчке, и мне становится плохо. Зачем нам нужен didMoveToParentViewController? Или зачем нам shouldAutomaticallyForwardRotationMethods, если он устарел?   -  person Ilya Belikin    schedule 25.08.2014
comment
@Mundi и пример приложения не проверяли черты при загрузке, поэтому он будет в неправильном состоянии, если вы запустите его в портретной ориентации ... мой код выше унаследовал эту проблему. Буду обновлять.   -  person Ilya Belikin    schedule 25.08.2014


Ответы (6)


Хорошо, я бы хотел, чтобы это было по-другому, но пока я просто преобразовал код из примера Apple в Swift и настроил его для использования с раскадровками.

Это работает, но я все еще считаю, что это ужасный способ заархивировать эту цель.

Мой TraitOverride.swift:

import UIKit

class TraitOverride: UIViewController {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    var forcedTraitCollection: UITraitCollection? {
        didSet {
            updateForcedTraitCollection()
        }
    }

    override func viewDidLoad() {
        setForcedTraitForSize(view.bounds.size)
    }

    var viewController: UIViewController? {
        willSet {
            if let previousVC = viewController {
                if newValue !== previousVC {
                    previousVC.willMoveToParentViewController(nil)
                    setOverrideTraitCollection(nil, forChildViewController: previousVC)
                    previousVC.view.removeFromSuperview()
                    previousVC.removeFromParentViewController()
                }
            }
        }

        didSet {
            if let vc = viewController {
                addChildViewController(vc)
                view.addSubview(vc.view)
                vc.didMoveToParentViewController(self)
                updateForcedTraitCollection()
            }
        }
    }

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) {
        setForcedTraitForSize(size)
        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    }

    func setForcedTraitForSize (size: CGSize) {

        let device = traitCollection.userInterfaceIdiom
        var portrait: Bool {
            if device == .Phone {
                return size.width > 320
            } else {
                return size.width > 768
            }
        }

        switch (device, portrait) {
        case (.Phone, true):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .Regular)
        case (.Pad, false):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .Compact)
        default:
            forcedTraitCollection = nil
        }
    }

    func updateForcedTraitCollection() {
        if let vc = viewController {
            setOverrideTraitCollection(self.forcedTraitCollection, forChildViewController: vc)
        }
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        performSegueWithIdentifier("toSplitVC", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        if segue.identifier == "toSplitVC" {
            let destinationVC = segue.destinationViewController as UIViewController
            viewController = destinationVC
        }
    }

    override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
        return true
    }

    override func shouldAutomaticallyForwardRotationMethods() -> Bool {
        return true
    }
}

Чтобы он заработал, вам нужно добавить новый UIViewController в раскадровку и сделать его начальным. Добавьте переход от него к вашему реальному контроллеру следующим образом: storyboard

Вам нужно назвать переход "toSplitVC": имя перехода

и установите начальный контроллер как TraitOverride: assign controller

Теперь это должно работать и для вас. Дайте мне знать, если вы найдете лучший способ или какие-либо недостатки в этом.

person Ilya Belikin    schedule 29.08.2014
comment
Будьте осторожны при использовании этого кода. У меня вылетает в updateForcedTraitCollection. - person IvanMih; 15.09.2019
comment
если вы присутствуете внутри контроллера навигации, вам нужно вызвать self.navigationController! .setOverrideTraitCollection (self. ForceTraitCollection, forChild: vc) - person user1760527; 13.06.2020

Я так понимаю, вы хотели здесь перевод SWIFT ... И вы, наверное, это решили.

Ниже приведено то, что я потратил много времени, пытаясь решить - заставить мой SplitView работать на iPhone 6+ - это решение Cocoa.

Мое приложение основано на TabBar, а SplitView имеет контроллеры навигации. В конце концов, моя проблема заключалась в том, что setOverrideTraitCollection не отправлялся правильной цели.

@interface myUITabBarController ()

@property (nonatomic, retain) UITraitCollection *overrideTraitCollection;

@end

@implementation myUITabBarController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self performTraitCollectionOverrideForSize:self.view.bounds.size];
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    NSLog(@"myUITabBarController %@", NSStringFromSelector(_cmd));
    [self performTraitCollectionOverrideForSize:size];

    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

- (void)performTraitCollectionOverrideForSize:(CGSize)size
{
    NSLog(@"myUITabBarController %@", NSStringFromSelector(_cmd));

    _overrideTraitCollection = nil;

    if (size.width > 320.0)
    {
        _overrideTraitCollection = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }

    [self setOverrideTraitCollection:_overrideTraitCollection forChildViewController:self];

    for (UIViewController * view in self.childViewControllers)
    {
        [self setOverrideTraitCollection:_overrideTraitCollection forChildViewController:view];
        NSLog(@"myUITabBarController %@ AFTER  viewTrait=%@", NSStringFromSelector(_cmd), [view traitCollection]);
    }
}

@end
person David Wilson    schedule 11.01.2015
comment
Просто чтобы вы знали, я получаю графический сбой при переходе от портретной к альбомной ориентации на 6 plus с помощью этого кода. - person malhal; 29.01.2017
comment
И когда вы скрываете панель вкладок, навигационные контроллеры по строке имеют неуместную панель инструментов, которая плавает над тем местом, где была бы панель вкладок. - person malhal; 29.01.2017

ОБНОВЛЕНИЕ:

Apple не рекомендует это делать:

Используйте свойство traitCollection напрямую. Не отменяйте это. Не предоставляйте индивидуальную реализацию.

Я больше не отменяю это свойство! Теперь я вызываю overrideTraitCollectionForChildViewController: в родительском классе viewControler.

Старый ответ:

Я знаю, что прошло больше года с тех пор, как вопрос был задан, но я думаю, что мой ответ поможет кому-то вроде меня, кто не добился успеха с принятым ответом.

Если решение действительно простое, вы можете просто переопределить traitCollection: метод. Вот пример из моего приложения:

- (UITraitCollection *)traitCollection {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        return super.traitCollection;
    } else {
        switch (self.modalPresentationStyle) {
            case UIModalPresentationFormSheet:
            case UIModalPresentationPopover:
                return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];

            default:
                return super.traitCollection;
        }
    }
}

идея состоит в том, чтобы принудительно использовать класс компактного размера на iPad, если контроллер представлен в виде всплывающего окна или листа формы.

Надеюсь, это поможет.

person Oleg_Korchickiy    schedule 20.01.2016
comment
Это очень помогло! спасибо, но на самом деле, если представлена ​​форма листа, контроллер iPad возвращает класс компактного размера! Это было полезно для меня, потому что вместо этого я принудительно использовал класс UIUserInterfaceSizeClassRegular! - person Michael Pirotte; 05.02.2016
comment
К вашему сведению, вот ссылка на это примечание о том, что не следует переопределять traitCollection: developer.apple .com / reference / uikit / uitraitenvironment / - person Rob; 26.03.2017

Дополнительный VC верхнего уровня хорошо работает для простого приложения, но он не распространяется на модально представленные VC, поскольку у них нет parentVC. Значит нужно снова вставить в разные места.

Лучшим подходом, который я нашел, было просто создать подкласс UINavigationController, а затем просто использовать свой подкласс в раскадровке и в другом месте, где вы обычно использовали бы UINavigationController. Это избавляет от лишнего беспорядка VC в раскадровках, а также избавляет от лишнего беспорядка в коде.

В этом примере все iPhone будут использовать обычный класс горизонтального размера для альбомной ориентации.

@implementation MyNavigationController

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
    UIDevice *device = [UIDevice currentDevice];

    if (device.userInterfaceIdiom == UIUserInterfaceIdiomPhone && CGRectGetWidth(childViewController.view.bounds) > CGRectGetHeight(childViewController.view.bounds)) {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }

    return nil;
}

@end
person trapper    schedule 24.11.2016
comment
этот метод не следует отменять - person malhal; 29.01.2017
comment
Потому что он вызывается миллион раз, плюс уже слишком поздно влиять на этот контроллер. - person malhal; 31.01.2017
comment
Он действительно работает, я использую этот код в своем собственном приложении. Если задокументировано, что этот метод не переопределяется, мне интересно узнать, где именно. - person trapper; 31.01.2017
comment
Возможно, это сработает для ваших приложений, потому что на контроллер навигации не влияют такие черты, как контроллер разделения представления. - person malhal; 31.01.2017
comment
@malhal Думаю, ты ошибаешься. Вы не должны переопределять traitCollection. В документах нет ничего о том, чтобы не переопределять overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? - person DoesData; 23.04.2018
comment
В этом документе говорится «используйте этот метод», но не говорится о переопределении этого метода: developer.apple.com/documentation/uikit/uiviewcontroller/ - person malhal; 23.04.2018

Да, он должен использовать настраиваемый контейнер View Controller для переопределения функции viewWillTransitionToSize. Вы используете раскадровку, чтобы установить контейнер View Controller в качестве начального. Кроме того, вы можете сослаться на этот хороший художественный, который использует программу реализовать это. В соответствии с ним, ваше суждение может иметь некоторые ограничения:

var portrait: Bool {
    if device == .Phone {
       return size.width > 320
    } else {
       return size.width > 768
    }
}

Кроме как

    if **size.width > size.height**{
        self.setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClass.Regular), forChildViewController: viewController)
    }
    else{
        self.setOverrideTraitCollection(nil, forChildViewController: viewController)
    }

"

person Jenus Dong    schedule 02.12.2014

Реквизит @Ilyca

Swift 3

import UIKit

class TraitOverride: UIViewController {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    var forcedTraitCollection: UITraitCollection? {
        didSet {
            updateForcedTraitCollection()
        }
    }

    override func viewDidLoad() {
        setForcedTraitForSize(size: view.bounds.size)
    }

    var viewController: UIViewController? {
        willSet {
            if let previousVC = viewController {
                if newValue !== previousVC {
                    previousVC.willMove(toParentViewController: nil)
                    setOverrideTraitCollection(nil, forChildViewController: previousVC)
                    previousVC.view.removeFromSuperview()
                    previousVC.removeFromParentViewController()
                }
            }
        }

        didSet {
            if let vc = viewController {
                addChildViewController(vc)
                view.addSubview(vc.view)
                vc.didMove(toParentViewController: self)
                updateForcedTraitCollection()
            }
        }
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        setForcedTraitForSize(size: size)
        super.viewWillTransition(to: size, with: coordinator)
    }

    func setForcedTraitForSize (size: CGSize) {

        let device = traitCollection.userInterfaceIdiom
        var portrait: Bool {
            if device == .phone {
                return size.width > 320
            } else {
                return size.width > 768
            }
        }

        switch (device, portrait) {
        case (.phone, true):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .regular)
        case (.pad, false):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .compact)
        default:
            forcedTraitCollection = nil
        }
    }

    func updateForcedTraitCollection() {
        if let vc = viewController {
            setOverrideTraitCollection(self.forcedTraitCollection, forChildViewController: vc)
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        performSegue(withIdentifier: "toSplitVC", sender: self)
    }

    override var shouldAutomaticallyForwardAppearanceMethods: Bool {
        return true
    }

    override func shouldAutomaticallyForwardRotationMethods() -> Bool {
        return true
    }
}
person Brandon A    schedule 25.04.2017
comment
Спасибо за перевод в Swift 3. Чтобы он заработал, мне пришлось добавить override func prepare(for segue: UIStoryboardSegue, sender: Any?), как в приведенном выше коде Swift 2 (с некоторыми изменениями). - person eli; 21.06.2017
comment
Конечно. Реквизит @Ilyca за то, что он представил мне это тоже. - person Brandon A; 21.06.2017