我希望使用在xcode 6中引入的Sizing Classes,根据iPad(纵向或横向)的方向,以不同的位置放置我的子视图。我找到了许多教程,解释了IB中为iPhone提供不同大小类别的纵向和横向,但是似乎没有覆盖IB上iPad单独的纵向或横向模式的教程。有人能帮忙吗?
我希望使用在xcode 6中引入的Sizing Classes,根据iPad(纵向或横向)的方向,以不同的位置放置我的子视图。我找到了许多教程,解释了IB中为iPhone提供不同大小类别的纵向和横向,但是似乎没有覆盖IB上iPad单独的纵向或横向模式的教程。有人能帮忙吗?
苹果似乎想将iPad的两个方向视为相同的 - 但正如我们中的许多人发现的那样,有非常合理的设计原因希望在iPad纵向和iPad横向上改变UI布局。
不幸的是,当前的操作系统似乎没有提供支持这种区别的功能...这意味着我们需要通过编写代码来操作自动布局约束或类似的解决方法来实现理想情况下可以免费使用自适应 UI 实现的东西。
这不是一种优雅的解决方案。
难道没有办法利用苹果已经内置到IB和UIKit中的神奇功能,以使用我们选择的大小类来处理给定方向的布局吗?
〜
在更泛化地考虑问题时,我意识到“大小类”只是一种方式,用于处理存储在IB中的多个布局,以便可以在运行时调用它们。
实际上,“大小类”只是一对枚举值。从UIInterface.h:
typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) {
UIUserInterfaceSizeClassUnspecified = 0,
UIUserInterfaceSizeClassCompact = 1,
UIUserInterfaceSizeClassRegular = 2,
} NS_ENUM_AVAILABLE_IOS(8_0);
所以不管苹果决定如何给这些不同的变量命名,从根本上说,它们只是一对用作某种唯一标识符的整数,用于区分一个布局与另一个布局,在IB中存储。
假设我们在IB中创建一个备用布局(使用未使用的尺寸类别),比如iPad纵向模式...有没有办法让设备在运行时使用我们选择的尺寸类别(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中特别为子控制器布局的'Regular / Regular'和'Compact / Regular'版本添加了自定义标签:
以下是运行时的外观,在iPad处于两个方向时:
看,运行时的自定义大小类配置。
希望苹果公司能在下一个操作系统版本中消除这种需求。与通过编程方式干预自动布局约束或进行其他代码操纵相比,这可能是一种更优雅、可扩展的方法。
~
编辑(6/4/15):请注意,上面的示例代码本质上是一种证明技术的方法。请根据自己的具体应用程序进行自适应。
~
编辑(7/24/15):令人满意的是,上述解释似乎有助于揭开神秘面纱。虽然我没有测试过它,但mohamede1945 [下面] 的代码看起来是一个有用的优化。请随时测试并告诉我们您的想法。(为了完整起见,我将保留上述示例代码。)
UITraitCollection
已经足够优化,并且overrideTraitCollectionForChildViewController
调用的频率很少,所以检查宽度并在那时创建它不应该是一个问题。 - zwaldowski作为对RonDiamond非常冗长回答的总结,你所需要做的就是在你的根视图控制器中。
Objective-c
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
} else {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
}
}
Swift:
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection! {
if view.bounds.width < view.bounds.height {
return UITraitCollection(horizontalSizeClass: .Compact)
} else {
return UITraitCollection(horizontalSizeClass: .Regular)
}
}
然后在故事板中,使用紧凑宽度来设置纵向模式,使用常规宽度来设置横向模式。
- (UITraitCollection *)traitCollection
而不是 overrideTraitCollectionForChildViewController
时,这对我有用。此外,约束应该匹配 traitcollections,因此wC(hAny)。 - H. de Jonge(UITraitCollection *)traitCollection
。- (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]];
}
}
traitCollection
的苹果文档中有以下警告:重要提示:不要在您自己的对象中实现此属性 - undefinediPad在水平和垂直尺寸上都有“常规”大小特征,没有肖像和风景之分。
这些大小特征可以通过您的自定义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;
}
UITraitCollection
中可能被践踏的(更高版本的)特征。\n还指出苹果文档实际上建议不要覆盖traitCollection
,因此在将来可能会出现该技术的问题。traitCollection
属性为只读:https://developer.apple.com/documentation/uikit/uitraitenvironment/1623514-traitcollection - cleverbit你的横屏模式和竖屏模式有多大的不同?如果非常不同,那么创建另一个视图控制器并在设备处于横屏时加载它可能是个好主意。
例如:
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)
}
Swift 3.0代码,用于@RonDiamond方案
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
}}