【问题标题】:How to override trait collection for initial UIViewController? (with Storyboard)如何覆盖初始 UIViewController 的特征集合? (带故事板)
【发布时间】:2020-12-22 06:29:48
【问题描述】:

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

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

我发现我可以使用

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

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

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

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

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

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

【问题讨论】:

  • 为什么看起来很可怕? AAPLTraitOverrideViewController.m 仅仅只有 20 多行代码。将它翻译成 Swift 大概需要 10 分钟,然后你就不必再看一遍了。
  • @Mundi 查看更新。不,我不相信这种代码可以免费维护。最后,现在我需要解释一下我的大学是什么以及我们为什么需要一所。我无法对每一行进行推理,这让我感觉很糟糕。为什么我们需要 didMoveToParentViewController?或者为什么我们需要 shouldAutomaticallyForwardRotationMethods 如果它已被弃用?
  • @Mundi 和示例应用程序没有在加载时检查特征,因此如果您纵向启动它,它将处于错误状态......我上面的代码继承了这个问题。会更新的。

标签: swift uisplitviewcontroller ios8 xcode-storyboard


【解决方案1】:

更新:

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 上强制使用 Compact size 类。

希望对你有帮助。

【讨论】:

  • 这帮助很大!谢谢,但实际上如果呈现表单 iPad 控制器返回一个 Compact Size 类!这对我很有帮助,因为我改为强制使用 UIUserInterfaceSizeClassRegular 类!
  • 仅供参考,这里是关于不覆盖 traitCollection 的注释的链接:developer.apple.com/reference/uikit/uitraitenvironment/…
【解决方案2】:

好的,我希望有另一种解决方法,但现在我只是将代码从 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 从它添加到您的真实控制器,如下所示:

您需要将 segue 命名为“toSplitVC”:

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

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

【讨论】:

  • 使用此代码时要小心。对我来说,它在 updateForcedTraitCollection 中崩溃。
  • 如果你出现在导航控制器中,你需要调用 self.navigationController!.setOverrideTraitCollection(self.forcedTraitCollection, forChild: vc)
【解决方案3】:

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

【讨论】:

  • 感谢对 Swift 3 的翻译。为了使它工作,我必须像上面的 Swift 2 代码一样添加override func prepare(for segue: UIStoryboardSegue, sender: Any?)(稍作修改)。
  • 当然可以。 @Ilyca 也向我介绍了这一点。
【解决方案4】:

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

【讨论】:

  • 这个方法不应该被覆盖
  • 因为它被调用了一百万次,加上它来不及影响那个控制器。
  • 它确实有效,我在自己的应用程序中使用此代码。如果记录不覆盖此方法,那么我有兴趣看看在哪里。
  • 它可能适用于您的应用,因为导航控制器不受拆分视图控制器等特性的影响。
  • @malhal 我认为你错了。你不应该覆盖traitCollectiondocs 中没有关于不覆盖 overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? 的内容
【解决方案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

【讨论】:

  • 只是为了让你知道我在 6 plus 上使用此代码从纵向到横向时出现图形故障。
  • 当你隐藏标签栏时,下一行的导航控制器有一个错位的工具栏,浮动在标签栏的位置上方。
【解决方案6】:

是的,它必须使用自定义容器视图控制器来覆盖函数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)
    }

"

【讨论】:

    猜你喜欢
    • 2015-08-07
    • 2021-11-04
    • 1970-01-01
    • 2016-01-09
    • 1970-01-01
    • 2012-01-20
    • 2016-05-03
    相关资源
    最近更新 更多