iPad纵向和横向模式的尺寸调整类


97

我基本上想使用xcode 6中引入的尺寸调整类,根据iPad的方向(纵向或横向)使子视图的位置不同。我发现了很多教程,它们解释了IB上纵向和横向的iPhone如何使用不同的尺寸分类。但是,似乎没有人能够涵盖IB上iPad的个人横向或纵向模式。有人可以帮忙吗?


似乎没有非编程解决方案,默认屏幕将需要使用该解决方案
SwiftArchitect

Answers:


174

苹果似乎打算将两种iPad方向都一样对待-但我们中的许多人发现,有非常合理的设计理由想要改变iPad Portrait与iPad Landscape的UI布局。

不幸的是,当前的操作系统似乎并未提供这种区分的支持……这意味着我们将回到通过代码或类似解决方法来操纵自动布局约束的方式,以实现理想情况下我们应该能够使用Adaptive UI免费获得的功能。 。

这不是一个优雅的解决方案。

难道没有办法利用苹果公司已经内置于IB和UIKit中的魔术在给定方向上使用我们选择的尺寸等级吗?

在更一般地考虑问题时,我意识到“大小类”只是解决存储在IB中的多个布局的简单方法,因此可以在运行时根据需要调用它们。

实际上,“大小类”实际上只是一对枚举值。从UIInterface.h:

typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) {
    UIUserInterfaceSizeClassUnspecified = 0,
    UIUserInterfaceSizeClassCompact     = 1,
    UIUserInterfaceSizeClassRegular     = 2,
} NS_ENUM_AVAILABLE_IOS(8_0);

因此,不管Apple决定用什么命名这些不同的变体,从根本上讲,它们只是一对整数,用作类别的唯一标识符,以区分存储在IB中的一种布局与另一种布局。

现在,假设我们在IB中创建了备用布局(使用未使用的尺寸类别)-例如,对于iPad Portrait ...,有一种方法可以让设备在运行时根据需要使用我们选择的尺寸类别(UI布局) ?

在尝试了几种不同的方法(不太优雅)后,我怀疑可能存在一种以编程方式覆盖默认size类的方法。在UIViewController.h中有:

// Call to modify the trait collection for child view controllers.
- (void)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);

因此,如果您可以将视图控制器层次结构打包为“子级”视图控制器,并将其添加到顶级父级视图控制器中,那么您可以有条件地覆盖子级,以使其与默认类不同。从操作系统。

这是在“父”视图控制器中执行此操作的示例实现:

@interface RDTraitCollectionOverrideViewController : UIViewController {
    BOOL _willTransitionToPortrait;
    UITraitCollection *_traitCollection_CompactRegular;
    UITraitCollection *_traitCollection_AnyAny;
}
@end

@implementation RDTraitCollectionOverrideViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setUpReferenceSizeClasses];
}

- (void)setUpReferenceSizeClasses {
    UITraitCollection *traitCollection_hCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    UITraitCollection *traitCollection_vRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
    _traitCollection_CompactRegular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[traitCollection_hCompact, traitCollection_vRegular]];

    UITraitCollection *traitCollection_hAny = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassUnspecified];
    UITraitCollection *traitCollection_vAny = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassUnspecified];
    _traitCollection_AnyAny = [UITraitCollection traitCollectionWithTraitsFromCollections:@[traitCollection_hAny, traitCollection_vAny]];
}

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width;
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]
    _willTransitionToPortrait = size.height > size.width;
}

-(UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController {
    UITraitCollection *traitCollectionForOverride = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny;
    return traitCollectionForOverride;
}
@end

为了快速查看它是否有效,我在IB中的子控制器布局的“ Regular / Regular”和“ Compact / Regular”版本中专门添加了自定义标签:

在此处输入图片说明 在此处输入图片说明

当iPad处于两个方向时,这就是运行时的样子: 在此处输入图片说明 在此处输入图片说明

瞧!在运行时自定义大小类配置。

希望苹果公司将在下一版操作系统中使此操作变得不必要。同时,与以编程方式弄乱自动布局约束或在代码中进行其他操作相比,这可能是一种更优雅,更可扩展的方法。

编辑(6/4/15):请记住,上面的示例代码实质上是演示该技术的概念证明。可以根据自己的特定应用随意调整。

编辑(7/24/15):令人欣慰的是,上述解释似乎有助于揭开这个问题的神秘色彩。尽管我尚未对其进行测试,但是mohamede1945的代码[下]看起来像是一个实用的优化实用工具。随时进行测试,让我们知道您的想法。(为了完整性,我将示例代码保留为原样。)


1
好帖子@RonDiamond!
amergin

3
苹果公司采用类似的方法在Safari中重新定义width size类,因此您可以放心,这或多或少是受支持的方法。您实际上不需要额外的代码;UITraitCollection进行了足够的优化,并且overrideTraitCollectionForChildViewController很少被调用,因此执行宽度检查并随后创建宽度检查不应该成为问题。
zwaldowski

1
@zwaldowski谢谢。这里的示例代码仅用于演示该技术,并且显然可以根据需要以各种方式对其进行优化。(通常,反复使用一个对象(例如,每当设备改变方向时),握住一个对象并不是一个坏主意;但是正如您所指出的,此处的性能差异可能会很小。)
RonDiamond 2015年

1
@Rashmi:正如我在解释中提到的那样,最终选择什么并不重要-您只是将布局映射到一对枚举值。因此,您实际上可以使用对您有意义的任何“大小类”。我只是确保它与其他可能默认继承的其他(合法)大小类不冲突。
RonDiamond

1
至于子视图控制器,我认为这并不重要。您应该能够以编程方式或从笔尖实例化它,只要它已作为子控制器正确添加到容器中即可。
RonDiamond 2015年

41

作为对RonDiamond非常长答案的总结。您需要做的只是在根视图控制器中。

目标c

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
    if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    } else {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }
}

迅速:

override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection! {
        if view.bounds.width < view.bounds.height {
            return UITraitCollection(horizontalSizeClass: .Compact)
        } else {
            return UITraitCollection(horizontalSizeClass: .Regular)
        }
    }

然后在Storyborad中,将“紧凑”宽度用于“肖像”,将“规则”宽度用于“风景”。


我想知道如何在新的分屏模式下解决问题……一种发现方法!
mm2001

UITraitCollection仅适用于iOS 8.0+
mohamede1945

对我不起作用,我有一个带有两个容器视图的视图,这些视图需要根据方向显示在不同的位置。我创建了所有所需的大小类,所有这些都适用于iPhone。我尝试使用您的代码来使iPad方向也分开。但是,这只会使观点变得奇怪。它不会像预期的那样改变它。任何线索可能会发生什么?
NoSixties

1
当我- (UITraitCollection *)traitCollection取代而不是时,这对我有用overrideTraitCollectionForChildViewController。约束也应与特征集匹配,因此wC(hAny)。
H. de Jonge

1
@Apollo我会,但不会回答实际问题。看看这里的如何使用setOverrideTraitCollection苹果的样品github.com/ios8/AdaptivePhotosAnAdaptiveApplication/blob/master/...
malhal

5

iPad在水平和垂直尺寸上都具有“常规”尺寸特征,在纵向和横向之间没有区别。

可以UIViewController通过方法在自定义子类代码中覆盖这些大小特征traitCollection,例如:

- (UITraitCollection *)traitCollection {
    // Distinguish portrait and landscape size traits for iPad, similar to iPhone 7 Plus.
    // Be aware that `traitCollection` documentation advises against overriding it.
    UITraitCollection *superTraits = [super traitCollection];
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        UITraitCollection *horizontalRegular = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
        UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
        UITraitCollection *regular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horizontalRegular, verticalRegular]];

        if ([superTraits containsTraitsInCollection:regular]) {
            if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
                // iPad in portrait orientation
                UITraitCollection *horizontalCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
                return [UITraitCollection traitCollectionWithTraitsFromCollections:@[superTraits, horizontalCompact, verticalRegular]];
            } else {
                // iPad in landscape orientation
                UITraitCollection *verticalCompact = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
                return [UITraitCollection traitCollectionWithTraitsFromCollections:@[superTraits, horizontalRegular, verticalCompact]];
            }
        }
    }
    return superTraits;
}

- (BOOL)prefersStatusBarHidden {
    // Override to negate this documented special case, and avoid erratic hiding of status bar in conjunction with `traitCollection` override:
    // For apps linked against iOS 8 or later, this method returns true if the view controller is in a vertically compact environment.
    return NO;
}

这使iPad具有与iPhone 7 Plus相同的尺寸特征。请注意,其他iPhone型号通常具有“紧凑宽度”特征(而不是常规宽度),而与方向无关。

以这种方式模仿iPhone 7 Plus,可以在Xcode的Interface Builder中将该模型用作iPad的替代品,而无需识别代码中的自定义项。

请注意,iPad上的Split View可能会使用与正常的全屏操作不同的尺寸特征。

该答案基于此博客文章中采用的方法,并进行了一些改进。

更新2019-01-02:已更新,以修复iPad横向中的间歇性隐藏状态栏以及可能践踏(较新)特征的情况UITraitCollection。还应注意,Apple文档实际上建议不要覆盖traitCollection,因此将来可能会发现这种技术存在问题。



4

RonDiamond的冗长而有益的回答是理解原理的一个很好的开始,但是对我有用的代码(iOS 8+)基于重写方法 (UITraitCollection *)traitCollection

因此,在InterfaceBuilder中添加带有Width-Compact变化的约束,例如,约束属性Installed。因此,宽度-任何对于横向都是有效的,宽度-对于肖像是紧凑的。

要基于当前视图控制器的大小在代码中切换约束,只需将以下内容添加到您的UIViewController类中:

- (UITraitCollection *)traitCollection
{
    UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];

    if (self.view.bounds.size.width < self.view.bounds.size.height) {
        // wCompact, hRegular
        return [UITraitCollection traitCollectionWithTraitsFromCollections:
                @[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact],
                  verticalRegular]];
    } else {
        // wRegular, hRegular
        return [UITraitCollection traitCollectionWithTraitsFromCollections:
                @[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular],
                  verticalRegular]];
    }
}

0

您的风景模式与人像模式有什么不同?如果它非常不同,则最好创建另一个视图控制器并在设备处于横向状态时加载它

例如

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) 
    //load landscape view controller here

是的,这是一种选择。但我认为它不是最理想的一种。我的观点是,如果可以为ios8中的iphone的纵向和横向模式使用不同的尺寸分类特征,那么为什么对iPad不一样?
neelIVP 2014年

在Xcode 6之前,我们可以将不同的故事板用于不同的方向。如果大多数视图控制器是相同的,那不是很有效。但这对于不同的布局非常方便。在Xcode 6中,无法这样做。也许为不同的方向创建不同的视图控制器是唯一的解决方案。
Bagusflyer 2014年

2
每次加载不同的viewController似乎效率很低,尤其是在屏幕上发生某些事情的时候。最好使用同一视图并操纵自动布局约束或元素在代码中的位置。或使用上述技巧,直到苹果解决此问题。
潘涅夫

0

Swift 5版本。它工作正常。

override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
    if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
        let collections = [UITraitCollection(horizontalSizeClass: .regular),
                           UITraitCollection(verticalSizeClass: .compact)]
        return UITraitCollection(traitsFrom: collections)
    }
    return super.overrideTraitCollection(forChild: childViewController)
}

-3

@RonDiamond解决方案的Swift 3.0代码

class Test : UIViewController {


var _willTransitionToPortrait: Bool?
var _traitCollection_CompactRegular: UITraitCollection?
var _traitCollection_AnyAny: UITraitCollection?

func viewDidLoad() {
    super.viewDidLoad()
    self.upReferenceSizeClasses = null
}

func setUpReferenceSizeClasses() {
    var traitCollection_hCompact: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassCompact)
    var traitCollection_vRegular: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassRegular)
    _traitCollection_CompactRegular = UITraitCollection(traitsFromCollections: [traitCollection_hCompact,traitCollection_vRegular])
    var traitCollection_hAny: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassUnspecified)
    var traitCollection_vAny: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassUnspecified)
    _traitCollection_AnyAny = UITraitCollection(traitsFromCollections: [traitCollection_hAny,traitCollection_vAny])
}

func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    _willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width
}

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    _willTransitionToPortrait = size.height > size.width
}

func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection {
    var traitCollectionForOverride: UITraitCollection = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny
    return traitCollectionForOverride
}}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.