Universal applications

It is not uncommon for people to own more than a single iOS device. People who own both an iPhone and iPad tend to expect their favorite apps to work on both of their devices. Ever since Apple launched the iPad, it has enabled and encouraged developers to create universal apps. These are apps that run on both the iPhone and the iPad.

Over time, more screen sizes were added, and the available tools to create a universal, adaptive layout got a lot better. Nowadays, we use a single storyboard file in which a layout can be previewed on any screen size currently supported by Apple. You can even reuse most of your layouts and just apply some tweaks based on the available screen's real estate instead of having to design both versions of your app from the ground up.

If your apps are not adaptive at all, you're giving your users a subpar experience in a lot of cases. As mentioned before, your users expect their favorite apps to be available on each iOS device they own, and Apple actually helps your users if you don't provide a universal app. According to Apple, an iPhone app should be capable of running on the iPad at all times. If you don't support the iPad, your users will be presented with a scaled-up, ugly-looking version of your app. This isn't a great experience, and it often takes minimal changes to add support for adaptive layouts that will look good on any device. To do this, you can make use of Size classes.

A Size Class is a property that belongs to a UITraitCollection. A traitCollection describes the traits for a certain view or view controller. Some of these traits are relevant to the amount of available screen space. Others describe Force Touch capabilities or the color gamut of the screen. For now, we'll focus on Size classes. Currently, there are just two Size classes: Regular and Compact. The following is a list of devices and their corresponding Size classes:

The preceding list is not exhaustive. The iPad has supported multitasking since iOS 9, which means that its width Size Class can switch between Regular and Compact, depending on the size available to your application.

Don't use Size classes as an indication of the device a user is holding. The iPad can look a lot like an iPhone if you base your assumptions on which Size Class is set for the width. If you really need to know the type of device the user is holding, you should check the userInterfaceIdiom property in UITraitCollection.

The HelloContacts app you were working on was created for the iPhone; this is the setting you picked when you first created the app. Try running your app on an iPad simulator and see what happens. The layout looks bad because it's just an enlarged iPhone app. Not such a great experience, is it?

Enabling your app to be universal isn't very complex. All you have to do is go to your project settings and select the Universal option in the drop-down menu of your device. Once your app is universal, it will look a lot better already because HelloContacts was set up using Auto Layout. However, your app doesn't update when the iPad changes its orientation and it also doesn't support multitasking.

If you want to add multitasking for the iPad (and you do), your app needs to work on all interface orientations. However, on the iPhone, it's recommended that the app should not support the upside down interface orientation. It's possible to specify the device-dependent interface orientation settings in your app's Info.plist file. This will make it possible for your app to support all orientations on the iPad and support all orientations except the upside down orientations for the iPhone. To do this, first uncheck all of the supported interface orientations in your project settings. Your settings should look like the following screenshot:

Next, open the Info.plist file and search for the Supported interface orientations key. Remove this key and add two new keys, Supported interface orientations (iPad) and Supported interface orientations (iPhone). Add all of the possible orientations to the Supported interface orientations (iPad) key, and add all except for the upside down orientation for the iPhone. You should end up with the settings listed in the following screenshot:

This is all of the work required to set up a universal version of HelloContacts. Build and run the app to see what it will look like when it actually adapts to the iPad's layout. It looks much better than before, and you didn't even have to change any code! In the previous chapter, we implemented some special behavior for the cell deletion so the app wouldn't crash if a contact got deleted on an iPad. The iPad doesn't display action sheets in the same fashion the iPhone does; it uses a popover instead. Let's check that out now. Long-press on a contact to see the popover appear, as shown in the following screenshot:

The popover appears, but as shown in the preceding screenshot, its positioning is kind of awkward. Let's take a look at how the popover is positioned and how to fix it. The current implementation to present the popover looks like this:

if let popOver = confirmDialog.popoverPresentationController { 
popOver.sourceView = cell
}

The preceding code sets the sourceView property for the popover. This property tells the popover which view will contain it. In this case, the cell will act as the parent view for the popover view. In addition to the sourceView property, there is a property called sourceRect. The sourceRect property specifies a box in which the popover should be anchored. The coordinates for this box should be relative to the containing view's coordinates. Update the implementation with the following code to position the popover's anchor at the center of the contact image view:

if let popOver = confirmDialog.popoverPresentationController { 
popOver.sourceView = cell

if let cell = cell as? ContactCollectionViewCell {
let imageFrame = cell.contactImage.frame

let popOverX = imageFrame.origin.x + imageFrame.size.width / 2
let popOverY = imageFrame.origin.y + imageFrame.size.height / 2

popOver.sourceRect = CGRect(x: popOverX, y: popOverY, width:
0, height: 0)

}
}

It might not seem like anything special, but this popover code is actually the first piece of adaptive code that you've written. It checks the current environment by looking for a popoverPresentationController property. This property is only available in certain environments, and if it is, you must configure a special part of code that only applies to that specific environment. The beauty in this is that no assumptions are made about the device or screen size; it simply checks the capabilities of the current environment and acts based on them. If Apple were to implement this same popover behavior for the iPhone tomorrow, your code would still work perfectly.

Another important aspect for making your apps adaptive is multitasking. On the iPad, your app can be run alongside another app, meaning that both apps must share the screen. Your app can run in different sizes, so the more flexible your layouts are, the better this will work. Because you've modified the Info.plist file in a way that enables multitasking for your app, you can easily see how multitasking works. Run your app on the iPad simulator and swipe left from the right edge of the simulator screen and pick an app to be opened side by side with HelloContacts. You can change the available size for HelloContacts and change the orientation on the iPad, and the layout will update accordingly.

Congratulations! You just enabled HelloContacts to run on any iOS device with any possible screen size. Isn't that amazing? Now, let's take a look at navigation.