I have an iPad app that includes a mix of xib files and storyboard files. The app uses Launch Images but since they are deprecated in iOS 13, I am migrating the app to use a Launch Screen Storyboard instead.
To do this, I added a new Launch Screen storyboard file to the project, changed the background color of the Launch Screen UIView to purple, and set the app's Target > General > Launch Screen File
to my new Launch Screen storyboard file. When I run the app, the purple Launch Screen comes up for a second as expected.
However, when using a Launch Screen Storyboard on any iPad device that has the swipe up indicator bar, the position of views on the user interface get messed up for both xib files and storyboard files. Their constraints are no longer honored.
Below are examples. The left column shows what various iPad devices look like in their correct form when the original Launch Images are used. The right column shows what those same iPad devices look like when a Launch Screen Storyboard is used instead. The interface of the screen shown below is built from a xib file, however the same behavior happens for other screens that are built as storyboard files.
(1) Why are the constraints of other views in the app getting altered by simply switching to using a Launch Screen Storyboard instead of Launch Images?
(2) How do I use a Launch Screen Storyboard without having to alter the existing xib files and storyboards such that the views are displayed correctly for all iPad devices (i.e., with or without the swipe up bar)?
(Using XCode 13.3, iOS 15.4, Objective-C)
The following is the Size Inspector for the UIView that contains the Detail View of the Split View Controller (i.e., the "1 2 3" Seg Control, "Endpoint" Button, yellow UIImageView and cyan UIView). The Size Inspector parameters of the Window referenced by the Application Delegate look the exact same as this too. No sizes like width or height are being set in the code programatically.
The size of the two iPad Landscape images in the Launch Images assets are:
It's unfortunate that your app was designed for one specific device...
I played around a bit with (essentially) putting your entire app into a "container" view, with not-great results.
If you want to see the general idea - you might be able to get it to work for you...
Add a 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
and change your ApplicationDelegate_Pad.m to this:
#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
The results...
First, using your original App Delegate code without LaunchScreen
:
Then, *using LaunchScreen
with modified App Delegate code:
At first glance, it looks like it might work, however... the Secondary pane of the split view controller is not sizing correctly (note that the "Overlays" button is not visible, because it is outside the bounds of the view).
Worth mentioning -- Apple's docs state:
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.
So, using a split view controller as a tab in a Tab Bar Controller is throwing another wrench into the process.
You might want to play around with the container idea, but I don't have high hopes for it (no idea what else might be affected in your actual app).
I think you're just going to have to "bite the bullet" and restructure your app to run on modern devices.