Multiplatform App Tutorial: SwiftUI and Xcode 12

Learn how to use Xcode 12’s multiplatform app template and SwiftUI to write a single app that runs on every Apple platform. By Renan Benatti Dias.

4.8 (19) · 1 Review

Download materials
Save for later
Share

Since Mac Catalyst was announced, Apple has been paving the way to let iOS developers bring their apps to the Mac. Expansions to SwiftUI — Apple’s simple, declarative approach to building user interfaces — now let you use it to build an entire app. These expansions, along with the multiplatform app template that’s new in Xcode 12, let you use one code base to build apps for every Apple platform.

In this tutorial, you’ll learn about:

  • Xcode 12’s multiplatform app template
  • The App protocol
  • How the App, Scenes and Views fit together
  • The way the same code adapts to each platform
  • How to make a custom UI for each platform while reusing views

You’ll learn all of this by adding new features to RayGem — an app that displays information about different gemstones — for iOS, iPadOS and macOS.

Note: This tutorial assumes you’re familiar with SwiftUI. If you’re just getting started, check out SwiftUI: Getting Started. RayGem also makes use of Core Data. Although you don’t need a deep understanding of Core Data to follow along, here’s a great starting point if you want to learn more: Core Data with SwiftUI Tutorial: Getting Started. Also, this tutorial uses features from SwiftUI that are only available on iOS 14 and macOS 11 Big Sur. So to follow this tutorial make sure you’re running the latest macOS version and Xcode 12.

Getting Started

Download the project materials by clicking the Download Materials button at the top or bottom of the tutorial. Open RayGem.xcodeproj inside the starter folder. Build and run.

A list of gems on iPhone. Each row shows a thumbnail, plus the name and main color of the gem.

RayGem is a simple multiplatform app that lists a collection of gems, which are precious or semiprecious stones. Users can read interesting facts about them and save their favorites.

You can already scroll and tap gems to read facts about each one. The app has the code to fetch and save favorite gems from Core Data, but it can neither save nor list favorites yet. You’ll add this feature during this tutorial.

Open the different views inside the starter project to become familiar with the app. The main view of the app is in GemList.swift, showing a list of rows found in GemRow.swift that are fetched from a Core Data store. By tapping a row, you navigate to a details view located in DetailsView.swift.

Considering the Project Structure

Before you start making any changes, take a look at the starter project. Notice how the groups are different than those in your usual iOS starter project.

Xcode groups with three main groups: Shared, iOS and macOS.

When creating a new project, Xcode 12 has a new section called Multiplatform. In it, you’ll find the new template for multiplatform apps. The starter project for this tutorial was built using this template.

A window of templates from Xcode showing the multiplatform tab.

This template creates three groups:

  • iOS: iOS-specific code
  • macOS: macOS-specific code
  • Shared: code for both platforms, including models, business logic and reusable views

SwiftUI lets you share UI code between platforms, and it automatically adapts the UI depending on the device. You can create views that are reusable on each platform, but some behaviors are better suited for certain platforms. As such, having a group for each platform allows you to write specific code for each while still reusing a lot of code.

Understanding the New App and Scene Protocol

Open AppMain.swift inside the Shared group.

@main
struct AppMain: App {
  let persistenceController = PersistenceController.shared

  var body: some Scene {
    WindowGroup {
      ContentView()
        .environment(
           \.managedObjectContext, 
           persistenceController.container.viewContext)
    }
  }
}

In iOS 14, Apple introduced the new App protocol, which handles the app lifecycle and replaces the old AppDelegate.swift and SceneDelegate.swift.

Much like the View protocol, the App protocol requires you to implement a body by returning a Scene. The body will be the root view of your app. Usually, you’ll return a WindowGroup, a special type of Scene made to be a container for your app’s view hierarchy. SwiftUI and WindowGroup together take care of presenting your app differently on each platform. For instance, on macOS, WindowGroups will automatically have window management options in their menu bar and support gathering all of your app’s windows in tabs.

Swift 5.3 introduced the new @main attribute to indicate the entry point of an app. By adding this attribute to a struct that implements App, SwiftUI will use that struct as your app’s starting point.

Running on macOS

The app already has its basic functionality working on iOS, but how about macOS? Change the target from RayGem (iOS) to RayGem (macOS) and build and run again.

A window with a list of gems and a view with information of a gem.

The app doesn’t feature any iOS or macOS specific code, it’s all built using plain old SwiftUI as you would build any other app. But still, you can already run your app on iOS and macOS! Isn’t that cool?

Using the new Multiplatform app template, Xcode creates two targets for your app: one for running on iOS and iPadOS and another for running on macOS. It uses these targets to run your app on each corresponding platform.

Understanding how SwiftUI Adapts to the Platform

Notice how SwiftUI adapted the UI for each platform. On iOS, it uses a navigation view with a list. When the user taps a row, it pushes the destination view of that row. On macOS, it uses a window with a side list and a content view. When the user clicks a row, the content view updates with the destination view of that row.

Now, switch the target back to RayGem (iOS) and build and run on an iPad simulator.

A list of gems on an iPad

When the app runs on iPadOS, the list stays hidden on the left side of the view, and when the user selects a gem, the main view displays the destination view.

Polishing the macOS app

The UI already adapts to different device sizes, and even window resizing on macOS, but sometimes, you might want to add a few restrictions on some devices while keeping the behavior the same on others. Thankfully, SwiftUI includes a lot of modifiers to influence how it adapts your views to different platforms. You’ll make your app a better macOS citizen by telling SwiftUI what to do with your app.