0

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)

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

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.

enter image description here

The size of the two iPad Landscape images in the Launch Images assets are:

  • iPad Landscape 1x iOS7+ slot: 1024x768
  • iPad Landscape 2x iOS7+ slot: 2048x1536
Silverness
  • 85
  • 8
  • It's difficult to tell from the images you posted, but it looks like - particularly with the iPad Mini screen cap - that when using your Launch Images your app was not filling the screen. Now, with a Launch Screen, your app *will* fill the screen. Because devices have different screen sizes / aspect ratios, it will not look ***exactly the same*** on different devices (as expected). – DonMag Mar 16 '22 at 20:58
  • @DonMag Yes that is correct, when using the old way of Launch Images, upon running app, the iPad Mini's launch screen also does not go full screen. It's left and right sides have black filler color. I'm looking for ways to use Launch Screen Storyboard (or any other non-deprecated method for launch screen images) without having to completely redesign multiple screens. – Silverness Mar 16 '22 at 21:10
  • If you don't want your app "content" to fill the screen, you'll need to make some structure changes... such as putting into a "container" view and then constraining it to your desired aspect ratio. – DonMag Mar 16 '22 at 21:16
  • @DonMag I am trying to avoid this route since this would mean modifications to pretty much every xib and storyboard file in the project, and there are a lot in this app. Also, I can't add a UIContainer View to a xib file (though it looks like UIView could be used in place of that). Do you know of any more global solutions, like adjusting the aspect ratio of the UIWindow that is referenced by the App Delegate? Or something with the UITabBarController? RN in the AppDelegate, I have : self.window.rootViewController = self.tabBarController; [self.window addSubview:self.tabBarController.view]; – Silverness Mar 16 '22 at 23:03
  • I have an idea that *might* help you -- emphasis on ***might***. It's not clear from the images you posted, though, exactly how you currently have your sizing setup. What are the dimensions of your Launch Images? Can you create a minimal project that produces that layout that you could put up somewhere (such as GitHub)? – DonMag Mar 17 '22 at 12:45
  • @DonMag Thank you for your assistance. I can put up a minimal project on Monday. In the meantime, I updated the post at the bottom with a screenshot of the Size Inspector and with the dimensions of the Launch Images. – Silverness Mar 17 '22 at 18:44
  • @DonMag I've added a stripped down version of this project at: https://github.com/silverness3/launch-screen-test The first tab that the app opens into is a xib file, the second tab uses a storyboard file. I added a LaunchScreen storyboard too but have not activated it in the "Target > General > Launch Screen File" so you can first see the version where the UI looks correct using Launch Images. – Silverness Mar 21 '22 at 21:46

1 Answers1

1

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:

enter image description here

Then, *using LaunchScreen with modified App Delegate code:

enter image description here

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.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks for this! I gave this a shot in the test project and in the actual one and as you show and suspected, many of the main and sub views across the app are not sizing correctly, including on differently sized devices. Prior to iOS 13, all the views on this app would actually scale nicely and without issue across all xibs and storyboards, and across all iPad devices. Agreed it is looking like a restructure might be the best route. – Silverness Mar 22 '22 at 19:42
  • 1
    @Silverness - it doesn't look like you're taking advantage of the split controller features... so, if you're going to restructure your app, you may want to get rid of those at the same time. – DonMag Mar 22 '22 at 21:02