在 iOS 6 中启用自动布局同时保持与 iOS 5 的向后兼容性

153

如何在保证应用程序与早期版本的iOS设备兼容的同时,最大限度地利用iOS 6新的自动布局功能?


你能解决这个问题吗?有什么线索吗? - Janak Nirmal
@Jennis 还没有。 iOS 6将于明天(2012年9月19日)正式发布。希望它包括一些额外的相关文档。 - sglantz
我还没有找到任何东西。好奇的人想知道! - Ben Kreeger
我认为这是不可能的。就像在iOS4上不可能使用storyboard一样。 - Léo Natan
除了提供两个Nib文件,我不认为这是可能的。但我同意你的看法。 - Léo Natan
弹簧和支架可能适用于简单的布局。https://dev59.com/a2jWa4cB1Zd3GeqPtLOM - Rich Apodaca
6个回答

120

可以在每个.storyboard或.xib文件中启用或禁用自动布局。只需选择特定的文件,并使用Xcode中的文件检查器修改“使用自动布局”属性:

autolayout property in the File inspector

使用启用了自动布局的界面文件,并将部署目标设置为 iOS 6.0 之前的版本会导致编译错误,例如:

MainStoryboard.storyboard 中的错误:3:iOS 版本低于 6.0 的自动布局

如果您想在项目中使用自动布局并仍然保持与 iOS4-5 的兼容性,其中一个选项是创建两个targets:一个用于部署目标 iOS 6.0,另一个用于早期的 iOS 版本,例如:

enter image description here

您可以为每个故事板和XIB文件创建两个版本,并使用启用了6.0目标的自动布局,另一个版本则使用旧版目标,例如:

enter image description here

你需要将MainStoryBoardAutoSize添加到iOS6目标的构建阶段,将另一个文件添加到iOS4目标中。如果想要了解如何使用多个目标,请在这里查看更多信息。

编辑:正如marchinram的回答所指出的那样,如果你从代码中加载storyboard文件,并且不使用Xcode中的“主故事板”设置来设置初始storyboard,则可以使用单个目标。

对我而言,维护多个目标和界面文件的复杂性成本似乎超过了使用自动布局的好处。除了一些特殊情况外,如果需要iOS4-5兼容性,你可能更好地仅使用普通的自动调整大小(或从代码中使用layoutSubViews)。

29
自动布局要求 iOS 6 或更高版本。它不适用于 iOS 5!请在发布前验证你的声明。iOS 5 简单地没有必需的 API(例如 NSLayoutConstraint 类)。如果你不相信我,请看看其他用户在尝试在 iOS 5 上使用自动布局时经历了什么:https://dev59.com/aWgu5IYBdhLWcg3winnU https://dev59.com/UWgu5IYBdhLWcg3wnYTo - Imre Kelényi
1
是的,我认为你是正确的。 我想我在一些wwdc视频中听到过,但现在我在iOS 5.0设备上测试它时崩溃了。 我会浏览这些视频,看看我在哪里听到的。 尽管如此,你是对的,它确实在iOS 5.0上崩溃了。 - Asad Khan
@ImreKelényi 你会如何将这个提交到应用商店?我对这个过程不是很熟悉,但我认为你需要提交一个压缩的 .app 文件存档。拥有两个目标是否会让这个过程变得更加困难?谢谢。 - Crystal

47

你真的需要两个目标吗?我按照Imre Kelényi所说的方法做到了这一点,我有两个故事板,一个启用了自动布局,另一个没有,在应用程序委托中,我只是检查他们正在使用哪个版本,然后选择正确的故事板:

#import "AppDelegate.h"

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:(v) options:NSNumericSearch] != NSOrderedAscending)

@interface AppDelegate ()
    @property (strong, nonatomic) UIViewController *initialViewController;
@end

@implementation AppDelegate

@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIStoryboard *mainStoryboard = nil;
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
        mainStoryboard = [UIStoryboard storyboardWithName:@"iPhone_iOS6" bundle:nil];
    } else {
        mainStoryboard = [UIStoryboard storyboardWithName:@"iPhone_iOS5" bundle:nil];
    }

    self.initialViewController = [mainStoryboard instantiateInitialViewController];
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = self.initialViewController;
    [self.window makeKeyAndVisible];

    return YES;
}

@end

使用两个目标同样可行,但对我而言似乎有些过度。


1
你是正确的。只要从代码加载Storyboard并且不使用Xcode中目标的“主要Storyboard”设置,这将会奏效。我在我的帖子中添加了对你答案的引用。 - Imre Kelényi
SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO 这个方法有点过度。只需要将一个仅支持 iOS 6 的类与 nil 进行比较即可。请参阅 https://developer.apple.com/library/mac/#documentation/developertools/conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-SW6 - matt
是的,很好的观点。我最初回答时没有考虑到恢复这个问题。 - marchinram
咳咳,不要使用willFinishLaunchingWithOptions -- 在iOS 5.1模拟器下运行时,我遇到了一个"Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named NSLayoutConstraint'"的问题。但是如果使用didFinishLaunchingWithOptions则不会出现这个问题。 - Elise van Looij

4
如果布局差异不大,使用“弹簧和支撑”更容易定位元素。Springs and Struts

3

受@marchinram独一目标思想的启发,这是我最终想出的解决方案。有两个故事板,一个是用于struts-and-springs,另一个则是用于autolayout。在目标摘要中,我将autolayout故事板设置为默认值。然后,在appDelegate中,我检查是否需要加载早于6.0版本的struts-and-springs故事板:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    Class cls = NSClassFromString (@"NSLayoutConstraint");
    if (cls == nil) {
        NSString *mainStoryboardName = nil;
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
            mainStoryboardName = @"MainStoryboard_iPad_StrutsAndSprings";
        } else {
            mainStoryboardName = @"MainStoryboard_iPhone_StrutsAndSprings";
        }
        UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:mainStoryboardName bundle:nil];

        UIViewController *initialViewController = [mainStoryboard instantiateInitialViewController];
        self.window.rootViewController = initialViewController;
        [self.window makeKeyAndVisible];
    }

此外,我将struts-and-springs故事板的部署目标设置为iOS 5.1,而autolayout故事板的目标设置为Project SDK(iOS 6.0)。我真的想在故事版加载默认设置之前,在willFinishLaunchingWithOptions中进行切换,但无论我尝试什么,都会导致“NSInvalidUnarchiveOperationException”错误,原因是“无法实例化名为NSLayoutConstraint的类”。

2

0

我发现将Xib的主视图大小设置为自由形式,然后使用自动布局效果非常好。不需要在代码中纠结于视图问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接