How to load a different storyboard for different iPad Sizes

Since the introduction of the iPad Pro range, we now have to deal with three distinct iPad screen sizes. And although the 10.5″ and 9.7″ are very similar in size, the giant iPad Pro 12.9″ easily looks shockingly bad when used with a storyboard that otherwise looks handsome on the “smaller” iPad screens.

So what is a dev to do? Prepare a completely different storyboard for an iPad Pro 12.9″ of course! I’ve shown in the past how to do this for different phone sizes, so here’s how to do it for different iPad sizes. This approach will work with iOS 8 and above.

Determining the Screen Height

Sadly Apple have not implemented a new UIUserInterfaceIdiom property we could question. Every iPad device will only identify itself as an iPad rather than an iPad Pro model. But I guess this wouldn’t help us much anyway, since we have three screen sizes as of 2017, with god only knows how many more in the pipeline.

Hence, we need to determine what screen height we’re dealing with. To do this reliably though, we must also know if the user is holding the device in portrait or landscape mode when our app starts, otherwise “height” might be interpreted differently.

Lucky for us, there is a trait collection property we can use to question this, called fixedCoordinateSpace. We can call it on our UIScreen class. Here’s how:

int height = [UIScreen mainScreen].fixedCoordinateSpace.bounds.size.height;
NSLog(@"The fixed height is %i", height);

This test shall be at the heart of our endeavours. When derived like this, height will always deliver the “portrait up” height of our device, regardless if the app starts in portrait or landscape mode.

Now we’ll add another test to it, namely if the device we’re testing is in fact an iPad. If it is, we’ll compare the height parameter to the height of a 10.5″ iPad (which is 1024 pixels, just like it would be for a 9.7″ device). If it’s larger, we’re dealing with a 12.9″ device. And if it’s not an iPad at all, we’ll load an iPhone storyboard.

Here’s how we might do that:

- (UIStoryboard *)grabStoryboard {

    UIStoryboard *storyboard;
    
    // detect screen height
    int height = [UIScreen mainScreen].fixedCoordinateSpace.bounds.size.height;
    NSLog(@"The fixed height is %i", height);
    
    // determine if this is an iPad
    if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
        
        // it's an iPad 10.5" or lower
        if (height <= 1024) {
            
            storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
        } else {
            // it's an iPad Pro 12.9"
            storyboard = [UIStoryboard storyboardWithName:@"iPad-Pro" bundle:nil];
        }
        
    } else {
        // not an iPad, load regular storyboard
        storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    }
    
    return  storyboard;
}

In our project we have three storyboards: Main, iPad and iPad-Pro. Each of these will now be returned by this method depending on their screen height.

Displaying our Storyboard

Now that we have a reference to the one we need, let’s load it in our AppDelegate. We’ll do that just before returning YES in the didFinishLaunchingWithOptons method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // grab and show the storyboard
    UIStoryboard *storyboard = [self grabStoryboard];
    self.window.rootViewController = [storyboard instantiateInitialViewController];
    [self.window makeKeyAndVisible];
    
    return YES;
}

It is irrelevant which storyboard is set as the main storyboard in the iOS target, it will be overridden by this method’s last two calls.

Demo Project

I’ve got a demo project on GitHub to demonstrate this, feel free to check it out and tinker with it:





What is the NSPhotoLibrary Usage Description key?

Since iOS 10 and Xcode 8, Apple have added (yet another) security check for our apps. This time it’s about our apps accessing the user’s photo library.

While it was a courtesy to ask the user for permission to use the library before, since iOS 10 we have to add a special key to the app target’s Info.plist file and give a reason why we want to have access.

Here’s how to do it:

Navigate to your project’s target and select the Info tab at the top. In the long list below, hover over any row and click on the little plus icon that comes up. This should add a new row to the table (or in other words, add an entry to the Info.plist file).

Now type in NSPhotoLibraryUsageDescription. It should automatically turn into “Privacy – Photo Library Usage Description”.

On the right hand side of your new row we can add a string value, which will become the description in the dialogue shown in the screenshot above. It seems that we’re free to add whatever we want here, explaining briefly that your app needs access to the user’s photos.

That’s it!

On this note, there are two other keys that may come in handy for accessing the user’s camera and microphone respectively:

  • NSCameraUsageDescriptionKey
  • NSMicrophoneUsageDescriptionKey

Just as with photos, add these keys and a brief description if your app needs access to either the camera or the microphone. Something like “for image sharing” should do nicely. Here’s what that message looks like in context when iOS presents the dialogue to the user:

The dialogue is shown only once, not every time when the user wants to share an image. Should the user decline, or should s/he want to change this, we should explain to them that this can be done under iOS Settings – Privacy – Photos.

Demo Project

I’ve updated my Demo Project on GitHub with the relevant changes so that it now runs correctly under iOS 10:

For an extensive list of other useful NSkeys, check out this Stack Overflow thread:





How to delete a git branch with Xcode 8

Recently  I tried to merge some changes I had made on another branch back into my master branch, but Xcode wouldn’t let me. Spurious error messages prevented this from happening. I was happy to simply create a new master branch and overwrite it completely with the changes I had implemented on my former testing branch.

Turns out that Xcode is happy to create new git branches for our projects and screw them up several times in a row, but sadly, it is not capable of deleting branches.

So the simple answer to the title of this post is: it can’t be done!

However, a quick command in Terminal can do it for is. cd into your local project directory and issue the following:

git branch -D yourbranch

where “yourbranch” is of course the name of your branch. Make sure you’re not currently on the branch you try to delete.

Doing this allowed me to simply create a new branch using Source Control – New Branch. When we do that, Xcode will automatically use the contents of our current branch as a starting point for the new branch and switch us onto it immediately.





How to fix missing file warnings in Xcode 8

Xcode 8 has this annoying habit to show missing files as warnings. This is happening when we delete a file that is referenced by a project using Finder rather than remove it using Xcode.

Technically it’s the git version control that complains about the missing files, not Xcode. However, since git says “yo, there’s a conflict between what should be and what is”, Xcode tells us this as a warning.

Be that as it may, how do we fix it before going insane? Lucky for us, it’s easy to fix. Here’s how.

Open a Terminal session and cd into your project directory. In here, simply type

git add .

As soon as you press Enter and return to Xcode, all those nasty warnings are gone. What we’ve done here is to say to git, “listen, this is the new state of the directory, please ignore what you think it should be”.

There should be no response from git, which is good news. All we see is no more missing file warnings in Xcode, and appropriate A and D icons in front of new and removed files as a result.





How to create infinite loops in Objective-C and C

Infinite loops can be useful to execute some code until an exit signal is given. For example, a command line menu could be waiting forever until the user makes a valid choice. There are other approaches of course, but in case you need to know the syntax, here it is for all three loops.

Infinite for loop

// infinite for loop
for (;;) {
    // repeat this forever
}

Infinite while loop

// infinite while loop
while (TRUE) {
    // repeat forever
}

Infinite do-while loop

// infinite do-while loop
do {
    // repeat forever
} while (TRUE);

To use the latter variations in C, replace the TRUE condition with 1.





How to add a macOS target to your iOS App

To write cross-platform applications, it can be beneficial to have a single project with several target architectures. For example, we may want a macOS App inside a project that started out as iOS, and vice versa. Or we may want a different version of our app, perhaps a free one with less features, and an expensive one with more, based on the same code.

That’s where Xcode Targets come in. A Target is something that defines several build settings about an app so that when we press that popular button in Xcode, it knows what to do so we can see the built app in full colour. Trust me, there’s a lot going on under the hood – if you’ve ever tried to compile from the command line, you know how super helpful that button is. But I digress…

In this example I’ll show you how we can add a macOS Target to an iOS App’s Project. This will allow us to run and build either an iOS or a macOS version from common code.

Let’s begin. I’m using Xcode 8.3.3 for this by the way.

Adding the Target

In a standard Xcode Project for iOS, we already have a single target. Click on the blue project bar and select it from the list next to the File Inspector. It’s the one with the yellow icon:

Continue reading