跳转至

10 Adaptive Layout

Adaptability is all about guaranteeing a good user experience across all iOS devices and screen sizes. With so many options, it can be challenging to develop apps that look good on everything. Unfortunately, creating storyboards and views for each screen size and orientation doesn’t scale well, so it’s critical to build your apps with adaptive user interfaces that use adaptive layouts.

Adaptive apps rely on the trait system and trait collections. A trait collection is a set of traits and their respective values. A trait describes the current environment for your app. For example, traits can include layout direction, dynamic type size and size classes.

The main goal of adaptive layout is to allow you to create apps for all iOS devices without the need for device-specific code. In this chapter, you’ll learn how to create adaptable apps by using size classes and adaptive images. Throughout this chapter, you’ll use the tools that UIKit already provides. For more adaptive layout content, read Chapter 15, “Dynamic Type,” and Chapter 16, “Internationalization and Localization.”

One storyboard to run them all

Depending on the complexity of your app, you can use different strategies to accomplish adaptability. By using the right constraints, your screens can adapt gracefully to different screen sizes and orientations.

Go to the starter project and open the MessagingApp project. Select the iPhone 8simulator, then build and run.

img

The Profile screen looks good in portrait mode. Now, press Command-Right Arrow to switch the simulator orientation to landscape.

img

Notice the views aren’t using all of the available width. Go back to Xcode, and open Profile.storyboard. In the document outline, look for Profile Scene and select Main Stack View. Open the Size inspector.

img

Look at the constraints; there’s one for the width to make sure it’s equal to 375. Select that constraint and press delete to remove it. Since the available screen size isn’t always going to be 375, it doesn’t make sense to keep this constraint.

Select Main Stack View in the document outline, and Control-drag to View, which is located at the top of the document outline. On the modal window that pops up, select Equal Widths.

Now, build and run.

img

Rotate the device from portrait to landscape and then back to portrait. Notice how the screen adapts to the available width after deleting the width constraint.

Setting up the storyboard

Go to Main.storyboard, and press Command-Option-1 to show the File inspector. On the Interface Builder Document section, make sure that Use Trait Variations and Use Safe Area Layout Guides are both checked. Note that these options are selected by default in the latest versions of Xcode.

img

Here’s what these options do:

  • Use Trait Variations allows the storyboard to store data for more than one device family. You need this option enabled to create adaptive layouts.
  • Use Safe Area Layout Guides makes the apps use the available space to draw layouts, respecting things like the notch on the latest iPhone devices.

Previewing layouts

On the bottom bar in Interface Builder, click View as: . This action expands the Device Configuration Bar.

img

The Device Configuration Bar allows you to see how the user interface will look on different devices and conditions. In the Devices section, select iPhone 4S, which is the right-most icon shown in the Device area. Almost immediately, all of the screens in the storyboard will resize to represent how the interface will look on the selected device.

In the Orientation area, select Landscape. Choose the About Scene, and the layout will look like this:

img

Notice how the text is getting cut off, and the layout isn’t using all of the available space. To fix this problem and to make sure the interface looks good on multiple devices in either orientation, you’ll use size classes.

Size classes

When creating layouts, you should always think in terms of available space. Size classes make it possible to know how much available space there is by taking into account the device and the environment in which the app is running. For example, apps running on an iPhone 6 won’t have the same available space as apps running on an iPad.

Size classes are attributes that represent the content area available using two traits: horizontalSizeClass and verticalSizeClass. These two traits can be either regular or compact. The regular value represents expansive space, meaning there’s a fair amount of space available. The compact value represents constrained space, meaning there’s not much space available. Using these two traits, you can have four possible combinations.

img

The use of size classes allows you to have more flexibility and awareness while creating user interfaces, which significantly reduces the need for device-specific code.

Here’s a list of the values of size classes on different devices:

img

Multitasking and size classes

As you can see in the previous table, for the iPad, you usually have regular width and height. This changes when the system is using a split view since there’s less space available.

The following example shows how a split view can change the size classes:

img

Working with size classes

Go back to Xcode. Open Main.storyboard and select the About Scene. The view contains two elements: an image view and a label — neither have constraints. Build and run to see the About Scene.

Go to the About tab, and rotate the device using Command-Right Arrow.

img

Notice the user interface isn’t correct. As it turns out, in this specific case, adding constraints isn’t enough because the final product should adapt depending on the orientation of the device.

On the Device Configuration Bar, set the device back to iPhone 8 in portrait. Click Vary for Traits from the popup that appears and select both options: width and height. The bar will turn blue, like this:

img

On the left side, you can see an icon for the device you’ve selected on the View as. Click this icon to see the list of devices that will be affected by the constraints you’re about to create.

Now you can add constraints to the image. Select the image and then use Align to select Horizontally in Container:

img

Next, set the top constraint to 20, and the width to 120. Make sure Constrain to marginis unchecked, and click Add 2 Constraints.

img

Now, select the label. Click the Pin menu. Make the top, leading and trailing constraints equal to 20, and set the bottom to 60. Make sure Constrain to margin is unchecked.

img

Add those four constraints, and then click Done Varying on the bottom bar.

Select the image view, and using the Pin menu, create a constraint for the aspect ratio, and click Add 1 Constraint.

img

Since this constraint was created without any traits specified, it will affect all size classes.

Verify that the value for the aspect ratio is 1:1. You can do this by selecting the constraint in the document outline and modifying the multiplier value in the Size inspector.

img

Build and run.

img

Excellent, the app now looks good in portrait mode; however, try switching to landscape using Command-Right Arrow, and you’ll see that the views disappear.

img

The constraints you created are installed only for Compact Width and Regular Height size classes. The iPhone 8 in portrait mode has that size class, but in landscape, the device screen changes to Compact Width and Compact Height.

Open Main.storyboard, and on the Device Configuration Bar, select landscape orientation.

img

The constraints in the document outline look faded; this means they’re not installed for the current size class. Move the image view and the label, so they’re side-by-side horizontally.

img

On the right side of the Device Configuration Bar, click Vary for Traits. From the popup that appears, select both options: width and height.

Select the image view and create the constrains using the Pin menu. Make sure Constrain to margin is unchecked, and then create the following constraints for the leading edge and width. When you’re done, click Add 2 Constraints:

img

Using the Align menu, select Vertically in Container for the image view.

img

Now, select the label and create and set the top, leading, and trailing constraints, so they’re equal to 20. Set the bottom constraint equal to 60 using the Pin menu.

img

Click Done Varying on the bottom bar.

Build and run.

img

The app now adapts perfectly no matter the orientation.

Changing properties

Apart from constraints, you can also change the value of some properties using size classes.

On Main.storyboard, go to the About Scene and select the label. Now, look at the Attributes inspector.

img

The Color and Font properties both have the plus sign on the left side, which means they can have variations depending on the size classes.

Click the plus button on the left side of the Font property. Select compact for width and height variations, and then Add Variation. This creates a new element containing the value for the font property for the specified variation. Change the font size to 12. The Attributes inspector will look like this:

img

Build and run. Rotate the device and see how the font size for the label changes depending on the orientation.

Trait environment and trait collections

Every time a device changes its orientation, traits containing the new configuration are propagated throughout the app from the screen to the presented view.

You can respond to these changes in code using traitCollectionDidChange(_:); this is called on any object that conforms to UITraitEnvironment.

Open AboutViewController.swift and add the following code inside of the class:

private func setupContactUsButton(
  verticalSizeClass: UIUserInterfaceSizeClass
) {
  //1
  NSLayoutConstraint.deactivate(contactButtonConstraints)

  //2
  if verticalSizeClass == .compact {
    //3
    contactUsButton.setTitle("Contact Us", for: .normal)
    //4
    contactButtonConstraints = [
      contactUsButton.widthAnchor.constraint(
        equalToConstant: 160),
      contactUsButton.heightAnchor.constraint(
        equalToConstant: 40),
      contactUsButton.trailingAnchor.constraint(equalTo:
        view.safeAreaLayoutGuide.trailingAnchor,
        constant: -20),
      contactUsButton.bottomAnchor.constraint(equalTo:
        view.safeAreaLayoutGuide.bottomAnchor, 
        constant: -10),
    ]
  } else {
    //5
    contactUsButton.setTitle("", for: .normal)
    //6
    contactButtonConstraints = [
      contactUsButton.widthAnchor.constraint(
        equalToConstant: 40),
      contactUsButton.heightAnchor.constraint(
        equalToConstant: 40),
      contactUsButton.centerXAnchor.constraint(
        equalTo: view.centerXAnchor),
      contactUsButton.bottomAnchor.constraint(equalTo:
        view.safeAreaLayoutGuide.bottomAnchor, 
        constant: -10),
    ]
  }
  //7
  NSLayoutConstraint.activate(contactButtonConstraints)
}

With this code, you:

  1. Deactivate all the constraints in contactButtonConstraints.
  2. Check if verticalSizeClass is equal to .compact.
  3. Change the contactUsButton button title to Contact Us.
  4. Make contactButtonConstraints equal to the set of constraints determined for this configuration. In this case, width equal to 160, height equal to 40, trailing equal to -20 from safeAreaLayoutGuide.trailingAnchor, and bottom equal to -10 from safeAreaLayoutGuide.bottomAnchor.
  5. In case verticalSizeClass is not equal to .compact. Set the title to an empty string.
  6. Make contactButtonConstraints equal to the set of constraints determined for this configuration.
  7. Activate the constraints in contactButtonConstraints.

Below setupContactUsButton(verticalSizeClass:), add the following code:

override func traitCollectionDidChange(_ 
  previousTraitCollection: UITraitCollection?
) {
  //1
  super.traitCollectionDidChange(previousTraitCollection)
  //2
  if traitCollection.verticalSizeClass !=
    previousTraitCollection?.verticalSizeClass {
      //3
      setupContactUsButton(
        verticalSizeClass: traitCollection.verticalSizeClass)
  }
}

Here’s what you did:

  1. Call super.traitCollectionDidChange(previousTraitCollection) so that elements in the higher hierarchy stay up to date with the changes.
  2. Check if the verticalSizeClass for the current traitCollection is different from the previous one.
  3. Call setupContactUsButton(verticalSizeClass:).

Go to viewDidLoad(), and add these two lines at the end:

//1
view.addSubview(contactUsButton)

//2
setupContactUsButton(
  verticalSizeClass: traitCollection.verticalSizeClass)

With this code, you:

  1. Add contactUsButton to the view so that it can be part of the view hierarchy.
  2. Call setupContactUsButton(verticalSizeClass:) so that contactUsButton is properly set up the first time the view appears.

Build and run. Rotate the device, and check how the contact us button at the bottom is displayed differently depending on the device configuration.

Here it is in portrait mode:

img

Here it is in landscape mode:

img

Adaptive presentation

A view controller can be presented in different ways, depending on the environment. By default, the system will try to accommodate the view controller, but you can decide how you want your view controller to adapt.

Build and run.

Go to the Messages tab and tap Options.

img

A sheet with some options appears; you can hide it by dragging the view to the bottom. Now, tap Options again and put the device in landscape using Command-Right Arrow.

img

There’s no way for you to dismiss the view controller while the device is in landscape — time to fix that.

Right-click over the Controllers folder and select New file…. Choose Cocoa Touch Classand click Next. Set Class to SettingsTableViewController. Set Subclass of to UITableViewController. Set the Language to Swift. Also, verify that create XIB files is unchecked, then click Next. Finally, click Create.

img

Open SettingsTableViewController.swift and remove everything except the class declaration. When you’re done, your code will look like this:

import UIKit

class SettingsTableViewController: UITableViewController {
}

Type this inside the class:

//1
override func awakeFromNib() {
  super.awakeFromNib()

  //2
  navigationItem.leftBarButtonItem = UIBarButtonItem(
    barButtonSystemItem: .done,
    target: self,
    action: #selector(dismissModal))

  //3
  modalPresentationStyle = .popover
  //4
  popoverPresentationController!.delegate = self
}

Here’s what you did:

  1. Override awakeFromNib. You want to execute code before the view controller loads.
  2. Set leftBarButtonItem of navigationItem equal to an instance of UIBarButtonItem with an action that calls dismissModal().
  3. Set the value of modalPresentationStyle to .popover so that you have a default presentation style.
  4. Set the view controller as the popoverPresentationController delegate.

Below the code you just added, add this:

@objc private func dismissModal() {
  dismiss(animated: true)
}

This new function gets called when the button on the navigation bar is tapped. It calls dismiss(animated: true) on the view controller.

Next, add the following new extension outside of the class:

extension SettingsTableViewController: 
  UIPopoverPresentationControllerDelegate {
  //1
  func adaptivePresentationStyle(
    for controller: UIPresentationController
  ) -> UIModalPresentationStyle {
    //2
    switch (
      traitCollection.horizontalSizeClass, 
      traitCollection.verticalSizeClass) {
    //3
    case (.compact, .compact):
      return .fullScreen
    default:
      return .none
    }
  }

  //4
  func presentationController(
    _ controller: UIPresentationController, 
    viewControllerForAdaptivePresentationStyle
      style: UIModalPresentationStyle
  ) -> UIViewController? {
    //5
    return UINavigationController(
      rootViewController: controller.presentedViewController)
  }
}

Here’s what you did:

  1. Implement adaptivePresentationStyle(for:) to choose the modal presentation style depending on the size classes.
  2. Create a switch statement using the horizontal and vertical size classes of traitCollection.
  3. When both size classes are compact, return fullscreen as the presentation style; otherwise, return none so that the default presentation style is used, which in this case, is popover as indicated in awakeFromNib.
  4. Implement presentationController(_:viewControllerForAdaptivePresentationStyle:). This allows you to return a different view controller than the one being presented.
  5. Return a UINavigationController whose root view controller is the presentedViewController. Thanks to this, the controller will have a navigator bar, where the UIBarButtonItem you created in awakeFromNib will appear. Note that this navigation view controller is ignored when the presentation mode is popover.

Open Main.storyboard and look for the table view controller containing the options — it’s connected through a segue to the Contacts Scene.

Select Table View Controller, and in the Identity inspector, set Class to SettingsTableViewController. Now the view controller uses the class you set up with the UIPopoverPresentationControllerDelegate.

img

Build and run.

Go to the Messages tab. Tap Options and the sheet appears just like before. Tap outside of the popover to dismiss it, then put the simulator in landscape mode using Command-Right Arrow. Tap Options again.

img

There’s now a navigation bar at the top, and you can close the view controller by tapping Done.

UIKit and adaptive interfaces

UIKit provides tools to make adaptable user interfaces. Some of these tools include:

  • Split view controller
  • Layout guides
  • UIAppearance proxy

The split view controller

The split view controller acts as a container view controller that manages two child view controllers. If you’ve used the Settings app on an iPad, you may have noticed that the experience is different from what you have on an iPhone 6, for example. The app changes to display the information in a master-detail configuration. This happens thanks to the split view controller.

Go to Main.storyboard. Press Command-Shift-L, and type split into the search bar.

img

Drag a Split View Controller into the storyboard.

img

Remove all of the newly added view controllers except the Master View Controller.

Remove the connection between the Tab Bar Controller and the Navigation Controllerconnected to the Contacts Scene.

Control-drag from the Tab Bar Controller to the Master View Controller, and select view controllers.

img

Control-drag from the Master View Controller to the Navigation Controller Controllerconnected to the Contacts Scene, and select master view controller.

img

Look for the Messages Scene and remove the segue going to it. Embed the scene in a navigation view controller using Editor ▸ Embed In ▸ Navigation Controller.

Control-drag from the Master View Controller to the navigation controller you just created, and select detail view controller on the popup that appears.

In the Project navigator, right-click over the Controllers folder and click New file…. Select Cocoa Touch Class, and click Next. Set Class to SplitViewController. Set Subclass of to UISplitViewController, and the Language to Swift. Also, ensure that create XIB files is unchecked, then click Next. Finally, click Create.

Open SplitViewController.swift, and replace everything in the class with the following code:

override func viewDidLoad() {
  super.viewDidLoad()

  //1
  guard 
    let leftNavController = 
      viewControllers.first as? UINavigationController,
    let masterViewController = 
      leftNavController.viewControllers.first 
      as? ContactListTableViewController,
    let detailViewController = (viewControllers.last 
      as? UINavigationController)?.topViewController 
      as? MessagesViewController
    else { fatalError() }

  //2
  let firstContact = masterViewController.contacts.first
  detailViewController.contact = firstContact
  //3
  masterViewController.delegate = detailViewController
  //4
  detailViewController.navigationItem
    .leftItemsSupplementBackButton = true
  detailViewController.navigationItem
    .leftBarButtonItem = displayModeButtonItem
}

With this code, you:

  1. Get the master view controller and detail view controller from the viewControllers property of the split view controller.
  2. By default, the split view controller will show the first contact. You obtain it by calling first on the contacts array of the masterViewController.
  3. Set masterViewController delegate to detailViewController.
  4. Make detailViewController replace its left navigation item with a button that will toggle the display mode of the split view controller. This button will be visible only on iPad; you’ll get a button in the top left to toggle the table view display.

Go to Main.storyboard. Select Master View Controller and press Command-Option-4 to show the Identity inspector. Set Class to SplitViewController. In the document outline, select the tab bar item.

img

In the Attributes inspector, set Title to Messages, and set Image to chat-tab.

img

In the Tab Bar Controller, click-drag the Messages tab bar item to the middle.

There’s only one thing missing: You need to wire the master and detail view so that when a user taps a contact, the corresponding messages appear.

Open ContactListTableViewController.swift, and add this code before the class declaration:

protocol ContactListTableViewControllerDelegate: class {
  func contactListTableViewController(
    _ contactListTableViewController: 
      ContactListTableViewController,
    didSelectContact selectedContact: Contact
  )
}

You’ll implement this protocol on the detail view controller so that it can display the proper messages when the selected contact changes.

Add the following code to the top of the class:

weak var delegate: ContactListTableViewControllerDelegate?

This code declares the delegate property.

Now, replace the tableView(_:didSelectRowAt:) implementation with the following:

override func tableView(
  _ tableView: UITableView, 
  didSelectRowAt indexPath: IndexPath
) {
  //1
  guard
    let messagesViewController = 
      delegate as? MessagesViewController,
    let messagesNavigationController = 
      messagesViewController.navigationController 
    else {
      return
  }

  //2
  let selectedContact = contacts[indexPath.row]
  messagesViewController.contactListTableViewController(
    self, 
    didSelectContact: selectedContact)

  //3
  splitViewController?.showDetailViewController(
    messagesNavigationController, 
    sender: nil)
}

This code:

  1. Checks that the delegate is not null and gets a reference to the navigation controller containing messagesViewController.
  2. Calls contactListTableViewController(_:didSelectContact:) on the delegate passing the selected contact.
  3. Calls showDetailViewController on splitViewController, passing messagesNavigationController. This makes the split view controller show the detail view.

You can remove prepare(for:sender:) since you won’t need it anymore.

Open MessagesViewController.swift, and at the bottom, add the following extension:

extension MessagesViewController: 
  ContactListTableViewControllerDelegate {
    func contactListTableViewController(
      _ contactListTableViewController: 
        ContactListTableViewController,
      didSelectContact selectedContact: Contact
    ) {
      contact = selectedContact
  }
}

Here, you implement ContactListTableViewControllerDelegate. This implementation ensures the contact property gets the selected contact. The property contact has a didSet observer that will reload the table view with the corresponding data.

Build and run.

Select the messages tab on the tab bar. By default, the split view controller shows the first detail item. Tap the back button so that you can see the master view with the contacts list.

You can tap any of the contacts and see how it works.

img

Stop the simulator, and select the iPad Pro (11-inch) as the simulator. Build and run.

Go to the messages tab. Tap the back button and see how you get a different user interface. Rotate the device using Command-Right Arrow. Now you get a master-detail view similar to the one used in the Settings app or the Mail app.

img

This functionality is all possible thanks to the split view controller — and you got it all almost for free. Pretty cool! :]

Use your layout guides

The system comes with predefined layout guides that can make apps adapt better to different devices. One clear example is the Safe Area Layout Guide that helps prevent content from getting behind the iPhone X notch. You can learn more about this in Chapter 7, “Layout Guides”.

UIAppeareance

UIAppeareance serves as a proxy to have access to the mutable appearance of some classes, like UINavigationBar, UIButton and UIBarButtonItem. By changing the attributes for these classes, you can create consistent themes that you can use throughout the app.

Open AppDelegate.swift, and in application(_:didFinishLaunchingWithOptions:), before return true, add this:

//1
let verticalRegularTrait = 
  UITraitCollection(verticalSizeClass: .regular)
//2
let regularAppearance = 
  UINavigationBar.appearance(for: verticalRegularTrait)
let regularFont = UIFont.systemFont(ofSize: 20)
//3
regularAppearance.titleTextAttributes = 
  [NSAttributedString.Key.font: regularFont]

//4
let verticalCompactTrait = 
  UITraitCollection(verticalSizeClass: .compact)
//5
let compactAppearance = 
  UINavigationBar.appearance(for: verticalCompactTrait)
let compactFont = UIFont.systemFont(ofSize: 14)
//6
compactAppearance.titleTextAttributes = 
  [NSAttributedString.Key.font: compactFont]

This code:

  1. Creates a new trait collection with a regular vertical size class.
  2. Grabs a reference to the appearance for the NavigationBar for the previously declared trait collection verticalRegularTrait.
  3. Sets titleTextAttributes so that it uses the regularFont. This will make the font size 20, when the screen has a lot of space available vertically — for example, an iPhone 8 in portrait mode.
  4. Creates a new trait collection with a compact vertical size class.
  5. Grabs a reference to the appearance for the NavigationBar for verticalCompactTrait.
  6. Sets titleTextAttributes so that it used the compactFont. This will make the font size 14 when the screen has little space available vertically — for example, an iPhone 8 in landscape mode.

Build a run. Make sure to select the iPhone 8 as the simulator.

Rotate the device to see how the navigation bar title looks bigger in portrait mode and smaller in portrait mode.

img

Adaptive images

Image assets should be adaptive, too. In this section, you’ll use Asset Catalogs to manage images and provide different versions of them depending on the size class. Also, you’ll explore how the alignment and slicing tool can help you select parts of an image and indicate how an image should resize when necessary.

Images and traits

Asset catalogs give you the possibility of having multiple images depending on the trait environment. You can have different image assets for different size classes.

Back in Xcode, open Assets.xcassets and select about-logo. In the Attribute inspector, set Height Class to Any & Compact.

img

This will add three slots on your image set. These new slots allow you to add images for when the height trait is compact.

img

The already existing images have the label Any Height; this means they’ll be used for any other configurations.

Look for the assets folder — it’s in the same directory as the starter and final folders. Drag all of the images, individually, to their designated slots.

Build and run.

Go to the About tab and rotate the device. When the device is in landscape, the images are bigger; when in portrait, the images are smaller.

Here’s how the app looks in portrait:

img

Here’s how the app looks in landscape:

img

Great! You’ve used the power of asset catalogs and size classes to create a better user interface.

Alignment insets and slicing

Using the Asset Catalog, you can indicate which parts of an image you want to use so that your app looks good in different scenarios. For this, you need to use alignment insets and slicing.

Alignment allows you to take part of an image by specifying margins. Meanwhile, slicinggives you the ability to have images that resize nicely, stretching just the parts you want. This is commonly used when you want to give a view a background, but the view can have different sizes.

Go to AboutViewController.swift, and in the lazy var contactUsButton, and change the title color to white, Add the following before the return statement.

button.setBackgroundImage(
  UIImage(named: "button-background"), 
  for: .normal)

Build and run.

Go to the About tab and put the device in landscape. Notice the background looks distorted.

img

In Assets.xcassets, select button-background. Choose the image in the 2x slot, and in the Attribute inspector, go to the bottom and you’ll reach the Slicing section.

img

Set Slices to Horizontal. Set a value of 44 for Left and Right. And, set Center to Stretches.

img

Click Show Slicing on the bottom bar, and you’ll see a visual representation of what you did.

img

You can also use this to change the values by dragging the dotted lines.

Build and run, and go to the About tab. Now, rotate the device so that you can see how the button background adapts for different orientations.

When in portrait:

img

When in landscape:

img

Excellent! You now have a new set of tools for creating layouts and making them adaptive. Using these tools, you can make your apps look great on any device and orientation.

Challenge

The About screen currently has constraints for Compact Width/Regular Height and Compact Width/Compact Height traits. Your challenge is to add constraints using the Regular Width and Regular Height traits, so the user interfaces look good on more devices.

Key points

  • Size Classes determine how the user interface is laid out.
  • You can modify how view controllers are presented using UIPopoverPresentationControllerDelegate.
  • UIKit provides tools that help you create adaptive interfaces such as UISplitViewController, UIAppeareance proxy and Layout Guides.
  • You can have different images for specific size classes.
  • Split View Controller is a handy tool that comes with UIKit; you can use it to display master-detail like layouts.
  • Use the UIAppeareance proxy when you want to have a consistent user interface attributes across the entire app.
  • You can use Alignment and Slicing to control how your images stretch and to show specific portions when desired.