我基本上希望使用 xcode 6 中引入的 Sizing Classes,根据 iPad 的方向(纵向或横向)对子视图进行不同的定位。我发现许多教程解释了 Iphone 在 IB 上的纵向和横向如何使用不同的大小调整类但是,似乎没有涵盖 IB 上 iPad 的单个横向或纵向模式。任何人都可以帮忙吗?
7 回答
Apple 似乎打算将两种 iPad 方向视为相同 - 但正如我们中的许多人所发现的那样,想要改变 iPad 纵向与 iPad 横向的 UI 布局是非常合理的设计理由。
不幸的是,当前的操作系统似乎没有提供对这种区别的支持……这意味着我们将回到在代码中操纵自动布局约束或类似的变通方法,以实现我们理想情况下应该能够使用 Adaptive UI 免费获得的东西.
不是一个优雅的解决方案。
难道没有办法利用 Apple 已经内置在 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 布局) ?
在尝试了几种不同的(不太优雅的)解决问题的方法后,我怀疑可能有一种方法可以以编程方式覆盖默认大小类。还有(在 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 中子控制器布局的“常规/常规”和“紧凑/常规”版本添加了自定义标签:
当 iPad 处于两个方向时,这就是运行的样子:
瞧!运行时自定义大小类配置。
希望 Apple 在下一个版本的操作系统中不再需要这样做。同时,这可能是一种比以编程方式处理自动布局约束或在代码中进行其他操作更优雅和可扩展的方法。
~
编辑(2015 年 6 月 4 日):请记住,上面的示例代码本质上是演示该技术的概念证明。随意根据您自己的特定应用程序的需要进行调整。
~
编辑(7/24/15):令人欣慰的是,上述解释似乎有助于揭开这个问题的神秘面纱。虽然我还没有对其进行测试,但 mohamede1945 [下] 的代码看起来像是一个实用的有用优化。随意测试一下,让我们知道你的想法。(为了完整起见,我将保留上面的示例代码。)
作为对 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 中为纵向使用紧凑宽度,为横向使用常规宽度。
iPad 具有水平和垂直尺寸的“常规”尺寸特征,纵向和横向没有区别。
这些尺寸特征可以在您的自定义UIViewController
子类代码中通过 method覆盖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 的替身,而 Xcode 的 Interface Builder 不知道代码中的自定义。
请注意,iPad 上的拆分视图可能会使用与正常全屏操作不同的尺寸特征。
此答案基于此博客文章中采用的方法,并进行了一些改进。
2019-01-02 更新:更新以修复 iPad 环境中间歇性隐藏状态栏,以及UITraitCollection
. 还注意到 Apple 文档实际上建议不要覆盖traitCollection
,因此将来这种技术可能会出现问题。
RonDiamond 的长而有用的回答是理解这些原则的良好开端,但是对我有用的代码(iOS 8+)是基于覆盖方法的(UITraitCollection *)traitCollection
因此,在 InterfaceBuilder 中添加具有 Width - Compact 变化的约束,例如约束的属性 Installed。所以 Width - Any 对横向有效,Width - Compact 对于纵向有效。
要根据当前视图控制器大小在代码中切换约束,只需将以下内容添加到您的 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]];
}
}
How much different is your landscape mode going to be than your portrait mode? If its very different, it might be a good idea to create another view controller and load it when the device is in landscape
For example
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation))
//load landscape view controller here
斯威夫特 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
}}