iOS 启动屏幕故事板更改了对其余用户界面的约束

iOS Launch Screen storyboard changes constraints on rest of user interface

我有一个 iPad 应用程序,其中混合了 xib 文件和故事板文件。该应用程序使用 Launch Images 但由于它们在 iOS 13 中已被弃用,我正在迁移该应用程序以使用 Launch Screen Storyboard .

为此,我向项目添加了一个新的启动屏幕故事板文件,将启动屏幕 UIView 的背景色更改为紫色,并将应用程序的 Target > General > Launch Screen File 设置为我的新启动屏幕故事板文件。当我 运行 应用程序时,紫色启动屏幕按预期出现了一秒钟。

但是,在任何具有向上滑动指示条的 iPad 设备上使用启动屏幕故事板时,用户界面上的视图位置对于 xib 文件和故事板文件。他们的约束不再受尊重。

下面是示例。 左栏显示了使用原始启动图像时各种 iPad 设备的正确形式。右栏显示了当使用启动屏幕情节提要时这些相同的 iPad 设备的外观。下面显示的屏幕界面是从一个 xib 文件构建的,但是对于作为故事板文件构建的其他屏幕也会发生相同的行为。

(1) 为什么通过简单地切换到使用启动屏幕情节提要而不是启动图像来改变应用程序中其他视图的约束?

(2) 如何使用启动屏幕情节提要而无需更改现有的 xib 文件和情节提要,以便为所有 iPad 正确显示视图设备(即有或没有向上滑动条)?

(使用 XCode 13.3,iOS 15.4,Objective-C)

LAUNCH SCREEN IMAGES LAUNCH SCREEN STORYBOARD
iPad Mini 6th gen iPad Mini 6th Gen - ERROR: Gap at right-hand side, yellow/cyan aspect not retained, table row width is longer
iPad Pro 12.9inch 5th gen iPad Pro 12.9inch 5th gen - ERROR: Gap at right-hand side and under cyan, table row width is longer
iPad Pro 9.7inch iPad Pro 9.7inch - Looks OK

以下是 UIView 的大小检查器,其中包含拆分视图控制器的详细视图(即“1 2 3”分段控件、“端点”按钮、黄色 UIImageView 和青色 UIView)。 Application Delegate 引用的 Window 的 Size Inspector 参数看起来也与此完全相同。没有以编程方式在代码中设置宽度或高度等尺寸。

启动图像资产中的两个 iPad 风景图像的大小是:

很遗憾,您的应用是为一种特定设备设计的...

我试了一下(基本上)将您的整个应用程序放入“容器”视图中,结果 not-great。

如果您想了解总体思路 - 您可能能够让它为您工作...

添加一个RootContainerViewController:

RootContainerViewController.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface RootContainerViewController : UIViewController

@property (nonatomic, strong) UIView *container;

@end

NS_ASSUME_NONNULL_END

RootContainerViewController.m

#import "RootContainerViewController.h"

@interface RootContainerViewController ()

@end

@implementation RootContainerViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor blackColor];
    
    self.container = [UIView new];
    self.container.backgroundColor = [UIColor redColor];
    [self.container setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.view addSubview:self.container];
    
    UILayoutGuide *g = [self.view safeAreaLayoutGuide];
    
    [NSLayoutConstraint activateConstraints:@[
        
        [self.container.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
        [self.container.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
        [self.container.widthAnchor constraintEqualToAnchor:self.container.heightAnchor multiplier:1024.0 / 768.0],
        [self.container.centerXAnchor constraintEqualToAnchor:g.centerXAnchor],
        
    ]];

}

- (UIStatusBarStyle)preferredStatusBarStyle {
    if (@available(iOS 13.0, *)) {
        return UIStatusBarStyleLightContent;
    } else {
        // Fallback on earlier versions
    }
}

@end

并将您的 ApplicationDelegate_Pad.m 更改为:

#import "ApplicationDelegate_Pad.h"
#import "MPRootViewController.h"
#import "MPDetailViewController.h"

#import "RootContainerViewController.h"

@implementation ApplicationDelegate_Pad

@synthesize tabBarController;
@synthesize splitViewController;
@synthesize rootViewController;
@synthesize detailViewController;
@synthesize splitViewControllerD;

-(void) makeSplitViewController {
    
    // Create an array of controllers that will correspond to each tab in the tab bar vc.
    NSMutableArray *controllers = [NSMutableArray arrayWithArray:self.tabBarController.viewControllers];
    
    int index = 0; 
    for (UIViewController *controller in self.tabBarController.viewControllers) {
        
        // Set the split vc in the Presentation tab to hold the playlist in the root vc and the presenter controls in the detail vc.
        if (index == 0) {
            // Set up a storyboard for the root vc and initialize the root vc.
            UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"PlaylistVC" bundle:nil];
            self.rootViewController = [storyboard instantiateInitialViewController];
            
            // Initialize the detail vc and assign it to the root vc.
            detailViewController = [[MPDetailViewController alloc] initWithNibName:@"MPDetailViewController" bundle:nil];
            self.rootViewController.detailViewController = self.detailViewController;
            
            // Set up a split vc to hold the root vc and detail vc we just created.
            splitViewController = [[UISplitViewController alloc] init];
            self.splitViewController.tabBarItem = controller.tabBarItem;
            self.splitViewController.viewControllers = @[self.rootViewController, self.detailViewController];
            
            // Set the split vc's delegate.
            self.splitViewController.delegate = self.detailViewController;
            
            // Other.
            self.splitViewController.presentsWithGesture = NO;
            
            // limit Primary Column Width to 320
            [self.splitViewController setMaximumPrimaryColumnWidth:320.0];
            
            // Add the split vc to the list of controllers that will correspond to each tab in tab bar vc).
            controllers[index] = self.splitViewController;
        }
        
        // Set the split vc in the Datasets tab.
        if (index == 1) {
            // Set up a storyboard for the root vc and initialize the root vc.
            UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DatasetsVC" bundle:nil];
            self.splitViewControllerD = [storyboard instantiateViewControllerWithIdentifier:@"DatasetsVC"];
            
            // Set the title and icon of the Datasets tab bar item.  Tried to do this in Interface Builder, but it would
            // always show up blank.
            self.splitViewControllerD.tabBarItem.title = @"Data Catalog";
            
            // Add the split vc to the list of controllers that will correspond to each tab in tab bar vc.
            controllers[index] = self.splitViewControllerD;
        }
        
        index++;
    }
    
    // Set the tab bar's array of vc's with the split vc's controllers we just created.
    self.tabBarController.viewControllers = controllers;
    self.tabBarController.delegate = self;
    self.tabBarController.viewControllers = controllers;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Override point for customization after application launch.
    [super application:application didFinishLaunchingWithOptions:launchOptions];

    // This helps us to get a split view controller inside a tab.
    [self makeSplitViewController];
    
    // setup a new Root controller with a "container" view
    if (YES) {
        
        // instantiate RootContainerViewController
        RootContainerViewController *vc = [RootContainerViewController new];
        
        [vc loadViewIfNeeded];
        
        // add tabBarController as child of RootContainerViewController
        [vc addChildViewController:self.tabBarController];
        
        // add tabBarController's view to container
        [vc.container addSubview:self.tabBarController.view];
        // resizing mask
        [self.tabBarController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
        // set frame to container bounds
        [self.tabBarController.view setFrame:vc.container.bounds];
        // finsish child load
        [self.tabBarController didMoveToParentViewController:vc];
        
        // window rootViewController is now RootContainerViewController instead of tabBarController
        self.window.rootViewController = vc;

    } else {

        // Set the window view to the tab bar vc.
        self.window.rootViewController = self.tabBarController;
        [self.window addSubview:self.tabBarController.view];

    }
    
    // Make the receiver the main window and display it in front of other windows.
    [self.window makeKeyAndVisible];
    
    // iOS 15 added a default vertical content offset (i.e., padding) for table views that is non-zero that
    // pushes the table view down. Force this offset to be zero for all table views.
    if (@available(iOS 15.0, *)) {
        UITableView.appearance.sectionHeaderTopPadding = 0;
    }
    
    
    
    return YES;
}

#pragma mark - Memory management

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    /*
     Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later.
     */
}

@end

结果...

首先,使用你原来的 App Delegate 代码 没有 LaunchScreen:

然后,*使用 LaunchScreen 并修改 App Delegate 代码:

乍一看,它看起来可能有用,但是...拆分视图控制器的辅助窗格未正确调整大小(请注意“覆盖”按钮不可见,因为它在视图的边界之外。

值得一提 -- Apple 的 docs 状态:

Although it’s possible to install a split view controller as a child in some other container view controllers, doing so is not recommended in most cases.

因此,在 选项卡栏控制器 中使用拆分视图控制器作为选项卡会给流程带来麻烦。

您可能想尝试一下容器的想法,但我对它的期望不高(不知道您的实际应用程序中还有什么可能会受到影响)。

我认为您将不得不“硬着头皮”将您的应用重组为 运行 在现代设备上。