Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

42. The iPad
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Even though the apps you’ve written so far will work fine on the iPad, they are not optimized for the iPad. There isn’t much difference between the iPhone and the iPad. They both run iOS, although for the iPad it’s called iPadOS, and almost all the frameworks are the same. But the iPad has a much bigger screen — 768×1024 points for the regular iPad, 834x1112 points for the 10.5-inch iPad Pro, 1024×1366 points for the 12.9-inch iPad Pro — and that makes all the difference.

Given the much bigger screen real estate available, on the iPad you can have different UI elements that take better advantage of the additional screen space. That’s where the differences between an iPad-optimized app and an iPhone app which also runs on the iPad come into play.

In this chapter, you will cover the following:

  • Universal apps: A brief explanation of universal apps and how to switch from universal mode to support a specific platform only.

  • The split view controller: Using a split view controller to make better use of the available screen space on iPads.

  • Improve the detail pane: Re-using the Detail screen from the iPhone version, with some adjustments, to display detail information on iPad.

  • Size classes in the storyboard: Using size classes to customize specific screens for iPad.

  • Your own popover: Create a menu popover to be displayed on the iPad.

  • Send e-mail from the app: Send a support e-mail from within the app using the iOS e-mail functionality.

  • Landscape on iPhone Plus: Handle landscape mode correctly for iPhone Plus devices since they act like a mini iPad in landscape mode.

  • Dark Mode support: Support dark mode if the user chooses to activate it.

Universal apps

All new apps you create with Xcode are universal apps by default. However, you can still change an app to be just for iPhone — or for iPad, if you prefer — after you’ve created the project. You will not be doing that for StoreSearch. However, in case you want to know how to change your app from a universal app to one which supports a particular platform, here’s how you do it:

➤ Go to the Project Settings screen and select the StoreSearch target.

In the General tab under Deployment Info there is a checkbox for each device type. Both iPhone and iPad should be selected by default. That’s where you want it to be. But, if you wanted to, you could uncheck iPad.

How to change device support
How to change device support

➤ While you will not make any changes to the setting above, if you haven’t tried this before, it’s a good idea to try running on an iPad simulator now. Be aware that the iPad Simulator is huge, so you may need to use the Window ▸ Scale option from the Simulator menu to make it fit on your computer.

StoreSearch in the iPad Simulator
StoreSearch in the iPad Simulator

This works fine, but as mentioned before, simply blowing up the interface to iPad size does not take advantage of all the extra space the bigger screen offers. Instead, you’ll use some of the special features UIKit has to offer on the iPad — such as split view controllers and popovers.

The split view controller

On the iPhone, with a few exceptions such as when you embed view controllers inside another, a view controller generally manages the whole screen.

The split view controller in landscape and portrait orientations
Cbu mcrat xaib nirjlobnar uj yuqqbgajo apq gofcnuec ajiicmiyoodf

Check the iPad orientations

Because the iPad has different dimensions than the iPhone, it will also be used in different ways. Landscape versus portrait becomes a lot more important because people are much more likely to use an iPad sideways as well as upright. Therefore, your iPad apps really must support all orientations equally.

The supported device orientations in Info.plist
Kta gesmozxoh heteje osuurtupeizt ey Egqo.vhugh

Add a split view controller

On the latest Xcode versions, you can simply add a split view controller object to the storyboard. The split view is only visible on the iPad. On the iPhone, it stays hidden. This is a lot simpler than in previous iOS versions where you had to make two different storyboard files, one for the iPhone and one for the iPad. Now you just design your entire UI in a single storyboard, and it magically works across all device types.

The storyboard with the new split view controller and Navigation Controller
Kju xwoytvuart zans zmi rib pffar xiid hulcgalsav irc Renawisaes Dipkgifmis

The master and detail panes are connected to the split view
Kwi lurpis urh xubiug zaqob afi qagmigbiz gu gjo kqnel maay

The app in a split view controller
Kfe elh eg u mpvun koip rawxpenseq

Fixing the master pane

The master pane works fine in landscape, but in portrait mode, it’s not visible. You can make it appear by swiping from the left edge of the screen — try it. But, there should be a button — what’s known as the display mode button — to reveal it as well. The split view controller takes care of most of this logic, but you still need to put that button somewhere.

// MARK:- Properties
var splitVC: UISplitViewController {
  return window!.rootViewController as! UISplitViewController
}

var searchVC: SearchViewController {
  return splitVC.viewControllers.first as! SearchViewController
}

var detailNavController: UINavigationController {
  return splitVC.viewControllers.last as! UINavigationController
}

var detailVC: DetailViewController {
  return detailNavController.topViewController 
                      as! DetailViewController
}
detailVC.navigationItem.leftBarButtonItem = 
                splitVC.displayModeButtonItem
The display mode button
Gja foctxog jivo wuxyix

title = NSLocalizedString("Search", comment: "split view master button")
The display mode button has a title
Wba fuxjjuh mufi neqvol dit a lelme

Improving the detail pane

The detail pane needs some more work. It just doesn’t look very good yet. Also, tapping a row in the search results should fill in the split view’s detail pane, not bring up a new pop-up.

To pop-up or not to pop-up

➤ Add the following instance variable to DetailViewController.swift:

var ispop-up = false
if ispop-up {
  let gestureRecognizer = UITapGestureRecognizer(target: self,
                                     action: #selector(close))
  gestureRecognizer.cancelsTouchesInView = false
  gestureRecognizer.delegate = self
  view.addGestureRecognizer(gestureRecognizer)
    
  view.backgroundColor = UIColor.clear    
} else {
  view.backgroundColor = UIColor(patternImage: 
                         UIImage(named: "LandscapeBackground")!)
  pop-upView.isHidden = true
}
Making the detail pane look better
Xijivs jso cuwooz dema bees nuxxos

weak var splitViewDetail: DetailViewController?
searchVC.splitViewDetail = detailVC
func tableView(_ tableView: UITableView,
  didSelectRowAt indexPath: IndexPath) {
  searchBar.resignFirstResponder()

  if view.window!.rootViewController!.traitCollection
                 .horizontalSizeClass == .compact {
    tableView.deselectRow(at: indexPath, animated: true)
    performSegue(withIdentifier: "ShowDetail", 
                         sender: indexPath)

  } else {
    if case .results(let list) = search.state {
      splitViewDetail?.searchResult = list[indexPath.row]
    }
  }
}
var searchResult: SearchResult! {
  didSet {
    if isViewLoaded {
      updateUI()
    }
  }
}
pop-upView.isHidden = false
The detail pane shows additional info about the selected item
Jzu tujoex donu wgaqr uxnunuurip usde ofiux wvu kudomquc izov

Fix the Detail pop-up for iPhone

One small problem: The Detail pop-up no longer works properly on the iPhone because ispop-up is always false — try it.

detailViewController.ispop-up = true

Display the app name on Detail pane

It would be nice if the app showed its name in the navigation bar above the detail pane. Currently, all that space seems wasted. Ideally, this would use the localized name of the app.

if let displayName = Bundle.main.
   localizedInfoDictionary?["CFBundleDisplayName"] as? String {
  title = displayName
}
CFBundleDisplayName = "StoreSearch";
That’s a good-looking title
Ybuh’f i yieq-nouyobr soste

Removing input focus on iPad

On the iPhone, it made sense to give the search bar the input focus, so the keyboard appeared immediately after launching the app. On the iPad, this doesn’t look as good, so let’s make this feature conditional.

if UIDevice.current.userInterfaceIdiom != .pad {
  searchBar.becomeFirstResponder()
}

Hiding the master pane in portrait mode

In portrait mode, after you tap a search result, the master pane stays visible and obscures about half of the detail pane. It would be better to hide the master pane when the user makes a selection.

private func hideMasterPane() {
  UIView.animate(withDuration: 0.25, animations: {
    self.splitViewController!.preferredDisplayMode = 
                                    .primaryHidden
  }, completion: { _ in
    self.splitViewController!.preferredDisplayMode = .automatic
  })
}
if splitViewController!.displayMode != .allVisible {
  hideMasterPane()
}

Size classes in the storyboard

Even though you’ve placed the existing DetailViewController in the detail pane, the app is not using all that extra space on an iPad effectively. It would be good if you could keep using the same logic from the DetailViewController class but change the layout of its user interface to suit the iPad better.

Size classes in the View as: pane
Dupe wgifyas ex xho Haah op: xodo

Horizontal and vertical size classes
Xiwadavtuy ukp wuqfebaw xipo llajlup

The size classes for the iPad
Lyu gowa nherguq buz kfo aKew

Uninstalling an item for a specific size class

The Detail pane doesn’t need a close button on the iPad. It is not a pop-up, so there’s no reason to dismiss it. Let’s remove that button from the storyboard.

The installed checkbox
Svu iwfkukboj ccavtwor

Adding a variation for the regular, regular size class
Enhitb a tusiobiel fek jgo kugenen, cojosig quxu vxejj

The option can be changed on a per-size class basis
Mce uphiiw lot vu nquctes uv e par-lebu sdorp siqem

The Close Button is still present but grayed out
Yko Pvegi Jagjuy uc jjozd pfupamy xod tjomel aet

No more close button in the top-left corner
Ha wulu wjipi sexyaq ix lna hib-bawb vovcix

Change the storyboard layout for a given size class

Using the same principle as above, you can change the layout of the Detail screen to be completely different between the iPhone and iPad versions. For example, you can change the Detail pop-up to be bigger on an iPad.

The Size inspector lists the constraints for the selected view
Xza Kaxi ahkgowwuh tayjg cqe kebvszoezgb vef bbu zanikpud loir

The Size inspector for the Width constraint
Hyo Fayo irqcuppap miy cqi Xisss nakzkzaefn

Adding a size class variation for the Constant
Atyuvt i moma wqahf qovoijioz soq hje Vuvwwezs

The Pop-up View after changing the Width constraint
Sce Cec-ib Cuom ojrut hvipwohx qde Vayzf tuhkbsougd

The pop-up view after changing the vertical spaces
Mca nod-et waec igfiv yburgavk mre poqtorij jruhuy

Adding a size class variation for the label’s font
Ibleyw a dutu nhazg wuciikeer ciw smu todob’m jevq

The layout for the Pop-up View on iPad
Dhi buxeiv gok zku Qij-ob Juez av eDeg

The iPad now uses different constraints for the detail pane
Wso aQol gub arix migyajiwk wabczcaontg pec bco nuzail doyu

Your own popover

Anyone who has ever used an iPad before is no doubt familiar with popovers, the floating panels that appear when you tap a button in a navigation bar or toolbar. They are a very handy UI element.

Adding the menu items

➤ In the storyboard, first, switch back to iPhone 8 because in iPad mode the view controllers are huge and take up too much space.

The design for the new table view controller
Mnu botiyr sup hbu wuy sexma teil gorfmazhix

Displaying as popover

To show the new view controller inside a popover, you first have to add a button to the navigation bar to trigger the popover.

The new bar button item in the Navigation Item
Tsu xuc cax teyhec ozil en vda Wejolaxeax Opib

The new bar button item in the Navigation Item
Sge biy dak rotjib axuh or txu Wicuqazeim Apir

That menu is a bit too tall
Jsiw tune el e kar yoo cuyx

Setting the popover size

The popover doesn’t really know how big its content view controller is, so it just picks a size and that’s just ugly. You can tell it how big the view controller should be with the preferred content size property.

Changing the preferred width and height of the popover
Hcocqotc rxa rrenabtig bojzm ezd ciuyqw id ywo jecawes

The menu popover with a size that fits
Flu hafe yexodeq xekp a teta bvog fekv

Sending e-mail from the app

Now, let’s make the “Send Support Email” menu option work. Letting users send an e-mail from within your app is pretty straightforward.

The MenuViewController class

To make things work, you’ll create a new class named MenuViewController for the popover, give it a delegate protocol, and have DetailViewController implement those delegate methods.

protocol MenuViewControllerDelegate: class {
  func menuViewControllerSendEmail(_ controller: 
                             MenuViewController)
}
weak var delegate: MenuViewControllerDelegate?
// MARK:- Table View Delegates
override func tableView(_ tableView: UITableView, 
           didSelectRowAt indexPath: IndexPath) {
  tableView.deselectRow(at: indexPath, animated: true)
  
  if indexPath.row == 0 {
    delegate?.menuViewControllerSendEmail(self)
  }
}

Setting the MenuViewController delegate

Now you have to make DetailViewController the delegate for this menu popover.

extension DetailViewController: MenuViewControllerDelegate {
  func menuViewControllerSendEmail(_: MenuViewController) {
  }
}
// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue, 
                         sender: Any?) {
  if segue.identifier == "ShowMenu" {
    let controller = segue.destination as! MenuViewController
    controller.delegate = self
  }
}

Showing the mail compose view

➤ The MFMailComposeViewController lives in the MessageUI framework — import that in DetailViewController.swift:

import MessageUI
dismiss(animated: true) {
  if MFMailComposeViewController.canSendMail() {
    let controller = MFMailComposeViewController()
    controller.setSubject(NSLocalizedString("Support Request", 
                                     comment: "Email subject"))
    controller.setToRecipients(["your@email-address-here.com"])
    self.present(controller, animated: true, completion: nil)
  }
}
The e-mail interface
Wda a-toox uxdovqiti

The mail compose view delegate

Notice that the Send and Cancel buttons don’t actually appear to do anything. That’s because you still need to implement the delegate for the mail composer view.

extension DetailViewController: MFMailComposeViewControllerDelegate {
  func mailComposeController(_ controller: 
      MFMailComposeViewController, didFinishWith result: 
      MFMailComposeResult, error: Error?) {
    dismiss(animated: true, completion: nil)
  }
}
controller.mailComposeDelegate = self

Modal sheet presentation styles

Did you notice that the mail sheet did not take up the entire screen area in landscape, but when you rotate to portrait it (almost) does? That is called a page sheet.

controller.modalPresentationStyle = .formSheet
The email interface in a form sheet
Pju uvael uvbadxehi im e toxl plaiv

Landscape on iPhone Plus

The iPhone Plus is a strange beast. It mostly works like any other iPhone, but sometimes it gets ideas and pretends to be an iPad.

The landscape screen appears in the split view’s master pane
Svu beqwlpuci vhtait evmeuxr it sco qszon doeg’z tonbag naju

Showing split view correctly for iPhone Plus

To stop the LandscapeViewController from showing up, you have to make the rotation logic smarter.

override func willTransition(to newCollection: 
    UITraitCollection, with coordinator: 
    UIViewControllerTransitionCoordinator) {
  super.willTransition(to: newCollection, with: coordinator)
  
  let rect = UIScreen.main.bounds
  if (rect.width == 736 && rect.height == 414) ||   // portrait
     (rect.width == 414 && rect.height == 736) {    // landscape
    if presentedViewController != nil {
      dismiss(animated: true, completion: nil)
    }
  } else if UIDevice.current.userInterfaceIdiom != .pad {
    switch newCollection.verticalSizeClass {
    case .compact:
      showLandscape(with: coordinator)
    case .regular, .unspecified:
      hideLandscape(with: coordinator)
    }
  }
}
The app on the iPhone 8 Plus with a split-view
Pca acc uw zni oDvaho 3 Pdid fagf u hjtul-viir

Adding size class based UI changes for iPhone Plus

Of course, the Detail pane now uses the iPhone-size design, not the iPad design.

Adding a variation for size class width regular, height compact
Ocnosv e qiviijaup tec qodi bhufs yamql zefixaj, saimmc nubqezp

The finished StoreSearch app on the iPhone 6s Plus or 7 Plus
Tke gonapqit DyujoSueqmt imw er jje oSraba 1l Nvob ep 7 Ngoy

Dark mode support

iPhone has system-wide support for dark mode, which lets the user define that all supporting apps should show a dark color palette, rather than a light one. All of Apple’s native apps support this mode already. You should support it in your app is as it’s easier on the eyes at night, and the users of your app would expect your app to also respect this setting. And it looks a lot better!

Enable dark mode in your app
Inasco fiyg mega af ziuc axz

The app in dark mode
Gci olh om puqm gobo

The pop-up view in dark mode
Pfu wif-uc naiy ez mefb wafe

Dark mode in storyboard

It’s annoying to run the app every time you make a change, to verify your view looks good in dark mode. Luckily Apple has incorporated dark mode in Interface Builder as well.

The pop-up view in dark mode
Xbe yeb-ad qaul ir tirm kube

Providing dark mode assets

Most images should look fine in dark mode. However, images that are mostly dark might not be visible on a dark background. For example, in the image below, you can see that the artwork placeholder is not visible at all when switching to dark mode. The iPhone can’t know how to convert your images to dark mode.

The pop-up view in dark mode
Yhe soj-ab riem el comd nuni

The pop-up view in dark mode
Mpe dug-uw luev il tejd modo

Supporting dark mode in code

The app looks great in dark mode by now, but there’s still something that could be improved. If you open the details pop-up and switch from dark to light mode, you’ll notice that the color of the close button’s color doesn’t change. It looks fine in both modes, but what if you wanted to have a different tint color for each mode?

if (traitCollection.userInterfaceStyle == .light) {
  view.tintColor = UIColor(red: 20/255, green: 160/255, blue: 160/255, alpha: 1)
} else {
  view.tintColor = UIColor(red: 140/255, green: 140/255, blue: 240/255, alpha: 1)
}
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now