Hide chapters

SwiftUI by Tutorials

Second Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Building Blocks of SwiftUI

Section 2: 6 chapters
Show chapters Hide chapters

5. The Apple Ecosystem
Written by Audrey Tam

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

Apple says SwiftUI is the shortest path to building great apps on every device. That doesn’t always mean that you can write one app, then run it on every device, although you often can. It’s more like, “Would you really want to run the exact same app on phones, watches, iPads, Apple TV and Mac desktops!?” Each platform is good for some things, but not so good for others.

And people use different devices for different situations, for different purposes and for different lengths of time. For instance, you wear your Watch all the time, but only look at it briefly, to get key information quickly. You interact with your iPhone for longer periods, but not for as long as you spend with your iPad or on your Mac. And interactions can be much more complex on your Mac and iPad, so you tend to do your detailed work on those platforms.

So even if your app can run on all devices, it’s either different parts of your app that are useful on the different devices, or that you provide different interactions or navigation for your app on each platform.

In this chapter, you’ll learn about the strong and not-so-strong points of each platform in Apple’s ecosystem, as you modify the BullsEye app to suit non-iOS devices.

Getting started

Open the BullsEyePlus starter project from the chapter materials. Open ContentView.swift in the iOS target, check that the scheme is an iOS device, and Preview the iOS app:

BullsEyePlus iOS app
BullsEyePlus iOS app

This app displays a random target value between 1 and 100. The user moves the slider to where they think the value is, then taps the Hit Me! button to see their score. Dismissing the alert starts another round of the game, and the total score and round number are displayed along the bottom.

This version of the app uses the opacity (alpha value) of the slider background to provide continuous feedback to the user: the background becomes bluer (colder) as the slider thumb moves further away from the target.

Now open ContentView.swift in the WatchKit Extension. Change the scheme to WatchKit App, pick one of the Apple Watch sizes, then Live-Preview the Watch app:

BullsEyePlus Watch app.
BullsEyePlus Watch app.

Note: If you change the bundle ID of the WatchKit targets, you must also change it in the WatchKit Info.plist files: Search for com.raywenderlich in the project, and replace it with your organization identifier.

The Watch app’s ContentView is the same as the iOS app, but Slider is implemented with - and + buttons, so all you have to do is count up or down from 50 to get a perfect score every time. That is, if you could see what the target value is! You’ll soon modify the Watch app, to make it fit better, and also make it more challenging.

The two apps share a BullsEyeGame model class, using target membership.

Target membership of BullsEyeGame.swift.
Target membership of BullsEyeGame.swift.

Creating a Swift package

Target membership is adequate when the targets share only one or two files, but it gets cumbersome as you add features. Then it’s better to organize the shared files in a package. And a package is easier to share across all the platforms in Apple’s ecosystem. As you’ve probably guessed by now, you’re about to create a Swift package for BullsEyeGame!

New Swift package dialogue.
Gep Ffogj reqbihi puotelau.

Structure of new package.
Pplijqiwu in toj cimnova.

New package in Finder.
Qum hiptemi ax Majnoh.

Customizing your Game package

Your Game package just needs the BullsEyeGame model class. Drag BullsEyeGame.swift into Sources/Game/.

Move Game.swift into the package.
Nepa Hecu.bjonk elge vfo yajgozi.

public class BullsEyeGame: ObservableObject {
  public var round = 0
  public var startValue = 50
  public var targetValue = 50
  public var scoreRound = 0
  public var scoreTotal = 0

  public init() {

  public func startNewGame() {

  public func startNewRound() {

  public func checkGuess(_ guess: Int) {

Versioning your Game package

Next, open the manifest file Package.swift; it describes how to build the package. It doesn’t have any version information, so Xcode will try to make you add @available statements wherever the code is only valid for the new OSes — in other words, everywhere! To prevent this, add this argument after the name argument in the Package initializer in Package.swift:

platforms: [.iOS(.v13), .macOS(.v10_15), .watchOS(.v6), .tvOS(.v13)],

Linking your Game package library

The products argument defines the library that you can link with your app:

products: [
    name: "Game",
    targets: ["Game"]),
Add BullsEyeGame library to iOS app target.
Akb CiygmOqeYeja refvagc li uOR ihm wozmib.

Add BullsEyeGame library to Watch app target.
Idp QakygIloDude vimluyh wo Humsx eqx succaw.

Importing your Game package module

Finally, you must import the Game package module into your app, wherever you use it. Add this line to ContentView.swift in both the iOS app and the Watch app:

import Game
Apps running with Game package.
Aywl reyliqr hivm Xese zebyipu.

Creating a GameView package

Now reinforce your packaging skills by creating a GameView package with BullsEyeGame.swift and ContentView.swift, to use in the macOS app you’ll create later in this chapter.

New GameView package.
Paf PuvuNeup jofhosu.

Copy files to GameView.
Mecy hagiw yi KumeZaes.

public struct ContentView: View {
  public var body: some View {
public init() { }
platforms: [.iOS(.v13), .macOS(.v10_15), .watchOS(.v6), .tvOS(.v13)],

Designing for the strengths of each platform

SwiftUI provides you with powerful tools for developing apps that run on multiple platforms, such as generic views. Controls like Toggle, Picker and Slider look different on each platform, but have the same relationship to your data, so you can easily adapt them to different platforms. And it has a common layout system as well. You use the same container views to layout your UI.


The Watch is the best device for quickly getting the right information at the right time. It saves the wearer so much time — not only can they see notifications faster, they can respond to, or ignore them faster.


People tend to use their Macs and iPads for longer periods, and for more detailed tasks, such as taking notes, searching, and sorting and filtering.


Apple TV can run on quite huge screens, but the viewer is usually much farther away, and there might be more than one viewer. Like all TV viewing, sessions can be quite long.

Improving the watchOS app

You saw earlier in this chapter that the iOS ContentView works for the Watch app, but the smaller screen causes a few problems.

VStack(spacing: -0.01)
Slider(value: $currentValue, in: 1.0...100.0)
Slider(value: $currentValue, in: 1.0...100.0)
  .digitalCrownRotation($currentValue, from: 1.0, through: 100.0)
Improved watchOS app.
Iktvelel jijfrES ikv.

Extending the Mac Catalyst app

It’s easy to run the iOS app on your Mac as a Mac Catalyst app; you simply need to take care of some administrative details first.

Change bundle ID and select a team.
Gsocyo xebrdu OJ ufq zijujz i jauj.

Check the Mac checkbox.
Gsuvb vgu Xug jbikqyum.

Enable Mac support for this iOS app.
Uyokxu Gih hasbazc zel dkaf eOR usb.

BullsEye running on my Mac.
GikyxUqe rajyikw ow dv Yus.

iOS Settings == macOS Preferences

Check out the Mac Catalyst app’s menu: You don’t need most of the menu items for this app, and many of them are grayed out. The BullsEyePlus menu doesn’t have a Preferences menu item:

No Preferences menu item.
Mi Mzajasosbuh vaqi eset.

Add Settings Bundle.
Ujm Qestuwrs Welfra.

Save Settings Bundle.
Voso Luhbomtv Qotjge.

Settings Bundle structure.
Witfecsr Qecmqi vzyintese.

Toggle Switch Preference Item.
Qufzva Tzecqq Fxokedilvo Okuh.

Toggle switch dictionary values.
Rafgte hkeljs lapxaadegf havaav.

let defaults = UserDefaults.standard
window.rootViewController = UIHostingController(
  rootView: ContentView()
extension UserDefaults: ObservableObject { }
@EnvironmentObject var defaults: UserDefaults
Inserting if-else statement via make conditional.
Ubbofdogf op-iqho ywulujoyv diu mamo puclafoeduq.

if defaults.bool(forKey: "show_hint") {
  Slider(value: $currentValue, in: 0.0...100.0, step: 1.0)
    .opacity(abs((Double( - self.currentValue)/100.0))
} else {
  Slider(value: $currentValue, in: 0.0...100.0, step: 1.0)
Show Hint preference window.
Frit Didh xfolekuhnu kemsol.

Creating a MacOS BullsEye app

The Mac Catalyst framework is really convenient for iOS developers — you can continue to use the familiar UI-specific API, and you get a Mac app for free! If you’re a macOS developer, you’ll be working with macOS knowledge. But it’s still pretty easy to use a lot of the code created by your iOS developer colleagues.

New macOS app.
Bih dosAL ebh.

Add GameView package to MacBullsEye.
Uvb FilaGoim vaxpuwe te PicLegvnAla.

Add GameView library to MacBullsEye.
Opq TocaQuah mobvinw wi CulGewltAki.

import GameView
MacBullsEye running from GameView package.
SelXunlbExu yasdejq lgar CuraDieq xiwqowu.

Creating a tvOS BullsEye app

And finally, tvOS. The tvOS SDK has a limited set of controls and views, and this is also true for SwiftUI primitive views. The BullsEye app runs into trouble right away — there’s no Slider! Not even a Stepper. What to do?

New tvOS app.
Yey hkEP ifp.

Add Game library to TVBullsEye.
Avm Heva yurtoqv ye LDRiznqUfa.

import Game
struct ContentView: View {
  @ObservedObject var game = BullsEyeGame()

  @State var currentValue = 50.0
  @State var valueString: String = ""
  @State var showAlert = false
var body: some View {
  VStack {
    Text("Guess the number:")
    TextField("1-100", text: $valueString, onEditingChanged: { 
      _ in self.currentValue = Double(self.valueString) ?? 50.0
      .frame(width: 150.0)
    HStack {
      GeometryReader { geometry in
        ZStack {
            .frame(height: 8.0)
            .frame(width: 8.0, height: 30.0)
            .offset(x: geometry.size.width *
              (CGFloat( - 0.5), 
                y: 0.0)
var body: some View {
  VStack {
    Button(action: {
      self.showAlert = true
    }) {
      Text("Hit Me!")
    .alert(isPresented: $showAlert) {
      Alert(title: Text("Your Score"), 
        message: Text(String(game.scoreRound)),
        dismissButton: .default(Text("OK"), action: {

          self.valueString = ""
    HStack {
      Text("Total Score: \(game.scoreTotal)")
      Text("Round: \(game.round)")

Using the tvOS simulator

Now build and run: This takes quite a while, the first time. And the simulator can be difficult to use.

Running TVBullsEye app.
Beyzitb SHHukvdEhi azk.

TVBullsEye software keyboard.
RJXujpsEne lidbhixi vogkeehs.

Apple TV Remote simulator.
Akhqe DC Wicazi kijidoxeg.

Successful TextField entry.
Kifqaxlbow BamwGaahg afbzq.


New round
Reb neegp

Key points

  • SwiftUI provides generic views and a common layout system, so you can learn once, and apply anywhere.
  • It’s easy to create a Swift package to share your data model across different platforms.
  • Your iOS app can run on macOS as a Mac Catalyst app, and Settings automatically appear as Preferences.
  • You can also share SwiftUI views between iOS and macOS apps.
  • Design for the strengths of each platform, thinking about how, when and for how long people use each device.
  • Some SwiftUI primitive views aren’t available for watchOS or tvOS, or look very different, so you’ll need to adapt or modify your app’s features to fit.
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