如何覆盖初始UIViewController的特征集合? (与故事板)

时间:2014-08-25 10:01:31

标签: swift uisplitviewcontroller ios8 xcode-storyboard

我有一个针对iOS8的应用程序,初始视图控制器是UISplitViewController。我使用故事板,因此它可以为我实现一切。

由于我的设计,我需要SplitViewController在iPhone上以纵向模式显示主视图和详细视图。所以我正在寻找一种方法来覆盖这个UISplitViewController的特征集合。

我发现我可以使用

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

但遗憾的是,只有方法可以覆盖子控制器特征集合:

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

我在UISplitViewController子类中无法实现自我。

我查看了Apple的示例应用Adaptive Photos。在这个应用程序作者中,作为root使用特殊的TraitOverrideViewController,并在他的viewController setter中使用一些魔法来使它全部工作。

对我来说这看起来很可怕。是否有任何方法可以覆盖特征?或者如果没有,我怎么能设法使用与故事板相同的黑客?换句话说,如何以root用户身份注入一些viewController只是为了处理带故事板的UISplitViewController的特征?

6 个答案:

答案 0 :(得分:6)

好的,我希望还有另一种解决方法,但是现在我只是将Apple示例中的代码转换为Swift并将其调整为与Storyboard一起使用。

它有效,但我仍然相信这是一个很好的方式来存档这个目标。

我的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并使其成为初始。将show segue从它添加到您的真实控制器,如下所示: storyboard

你需要命名segue" toSplitVC": segue name

并将初始控制器设置为TraitOverride: assign controller

现在它也适合你。如果您发现更好的方法或任何缺陷,请告诉我。

答案 1 :(得分:5)

我知道你想在这里进行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

答案 2 :(得分:4)

我知道问题被问了一年多,但我认为我的答案会帮助像我这样没有取得成功的人。

如果解决方案非常简单,您可以覆盖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上强制使用Compact尺寸类。

希望它有所帮助。

更新:

Apple不建议这样做:

  

直接使用traitCollection属性。不要覆盖它。不要   提供自定义实现。

我不再使用这种方法了!现在我在父viewControler类中实现overrideTraitCollectionForChildViewController:

答案 3 :(得分:1)

是的,它必须使用自定义容器View Controller来覆盖函数viewWillTransitionToSize。您可以使用故事板将容器View Controller设置为初始值。 此外,您可以参考使用该程序的this good artical来实现它。根据它,你的判断肖像可能有一些限制:

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

&#34;

答案 4 :(得分:1)

额外的顶级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

答案 5 :(得分:1)

道具@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
    }
}