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

问题描述 投票:0回答:7

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

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

我发现我可以用

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

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

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

我不能在我的 UISplitViewController 子类中为自己这样做。

我检查了 Apple 的示例应用程序 Adaptive Photos。在这个应用程序中,作者使用特殊的 TraitOverrideViewController 作为 root 并在他的 viewController setter 中使用一些魔法来使其一切正常。

对我来说这看起来很可怕。有什么办法可以覆盖特征吗?或者如果没有,我怎样才能设法对故事板使用相同的技巧?换句话说,如何将一些 viewController 作为 root 注入,仅用于处理带有故事板的 UISplitViewController 的特征?

swift uisplitviewcontroller ios8 xcode-storyboard
7个回答
8
投票

好吧,我希望有另一种方法可以解决这个问题,但现在我只是将 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

现在它也应该适合你。如果您找到更好的方法或此方法有任何缺陷,请告诉我。


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

5
投票

更新:

Apple 不建议这样做:

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

我不会再覆盖这个属性了!现在我在父 viewControler 类中调用

overrideTraitCollectionForChildViewController:

旧答案:

我知道问题提出已经一年多了,但我认为我的答案将帮助像我这样没有通过接受的答案取得成功的人。

解决方案非常简单,您只需重写

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 上强制使用紧凑尺寸类别。

希望有帮助。


3
投票

额外的顶级 VC 对于简单的应用程序来说效果很好,但它不会传播到模态呈现的 VC,因为它们没有父 VC。所以你需要在不同的地方再次插入。

我发现更好的方法是对 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

2
投票

是的,它必须使用自定义容器View Controller来覆盖函数viewWillTransitionToSize。您使用故事板将容器视图控制器设置为初始值。 另外,您可以参考这篇好文章,它使用程序来实现它。据此,你的判断肖像可能有一些局限性:

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


1
投票

支持@Ilyca

斯威夫特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
    }
}

0
投票

使用 iOS 17 或更高版本时,您可以通过设置视图控制器的

traitOverrides
属性来直接覆盖特征属性。示例:

override func viewDidLoad() {
    super.viewDidLoad()
    
    traitOverrides.horizontalSizeClass = .compact
}
© www.soinside.com 2019 - 2024. All rights reserved.