Answers:
苹果似乎打算将两种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的代码[下]看起来像是一个实用的优化实用工具。随时进行测试,让我们知道您的想法。(为了完整性,我将示例代码保留为原样。)
UITraitCollection
进行了足够的优化,并且overrideTraitCollectionForChildViewController
很少被调用,因此执行宽度检查并随后创建宽度检查不应该成为问题。
作为对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中,将“紧凑”宽度用于“肖像”,将“规则”宽度用于“风景”。
- (UITraitCollection *)traitCollection
取代而不是时,这对我有用overrideTraitCollectionForChildViewController
。约束也应与特征集匹配,因此wC(hAny)。
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
,因此将来可能会发现这种技术存在问题。
traitCollection
属性为只读:developer.apple.com/documentation/uikit/uitraitenvironment/…–
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]];
}
}
您的风景模式与人像模式有什么不同?如果它非常不同,则最好创建另一个视图控制器并在设备处于横向状态时加载它
例如
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation))
//load landscape view controller here
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)
}
@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
}}