Chapters

Hide chapters

SwiftUI Apprentice

Second Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section I: Your First App: HIITFit

Section 1: 12 chapters
Show chapters Hide chapters

Section II: Your Second App: Cards

Section 2: 9 chapters
Show chapters Hide chapters

16. Adding Assets to Your App
Written by Caroline Begbie

Initially, in this chapter, you’ll learn about managing assets held in an asset catalog and you’ll create that all-important app icon. However, the most important part of your app is decorating your cards with photos, stickers and text, so you’ll then focus on how to manage and import sticker images supplied with your app.

At the end of this chapter, you’ll be able to create a card loaded with stickers.

The Asset Catalog

Skills you’ll learn in this section: app icons; vector vs bitmap; managing images in asset catalogs; screen resolution

Asset catalogs are by far the best place to manage image and color sets.

Within an asset catalog, under one image set, you can define multiple images for different themes, different devices, different scales and even different color gamuts. When you use the name of the image set in your code, the app will automatically load the correct image for the current environment. When you supply differently scaled images for different devices in an asset catalog, the app store will automatically do app thinning and only download the relevant images for that particular device. This is potentially a huge reduction in app download size.

The asset catalog also holds the app icon, the launch screen image and launch screen background color.

Adding the App Icon

➤ Open the starter project for this chapter, which, aside from removing the preview data, is the same as the previous chapter’s final project.

➤ Click the project name Cards at the top of the Project navigator. Choose the target Cards. On the General tab, find App Icons and Launch Screen:

Source for app icons
Source for app icons

This is where you specify which icon set to use for your app. You can choose to hold the icons in folders instead of asset catalogs, but it’s much easier to keep them in the asset catalog as Apple intended. You can even change the icon for your app dynamically, by checking include all app icon assets, and defining multiple icon sets in Assets.xcassets.

➤ Open Assets.xcassets and select AppIcon.

The iOS app template created an empty icon set called AppIcon when you first created your project.

Waiting for an icon
Waiting for an icon

If you’re lucky enough to have a designer for your app, as we are, they will distribute a design file, not code. This might be Sketch files or, as in our case, a Figma file.

The designer for this app, Lea Marolt, created all the assets for the app in Figma, a “freemium” vector graphics prototyping tool. You can use Figma in the web interface at https://www.figma.com or download the companion app available from that link. In the assets folder for this chapter, you’ll find a .fig file, which you can import into Figma. As you will see, some design suggestions don’t always make it to the shipped product.

A Figma design file
A Figma design file

This figma file includes the app icon, itself designed in Figma with different vector shapes:

The app icon in Figma
The app icon in Figma

➤ In Finder, open the assets folder for this chapter and locate app-icon.png. This file has been exported from Figma and is 1024 by 1024 pixels.

➤ With the AppIcon set selected in Xcode, drag app-icon.png to the icon area.

The app icon
The app icon

➤ Build and run, and swipe up from the bottom to exit your app. You’ll see your new icon instead of the old placeholder icon.

App icon in use
App icon in use

Any asset you import into the asset catalog is highly configurable. For example, app icons are different sizes on different devices. If you wish to have different icons for different devices, you can.

➤ Select the new icon you just dragged in, and show the Attributes inspector. Change iOS from Single Size to All Sizes.

Configure multiple icons
Configure multiple icons

You can now drag different PNG image files to the various icon spots.

➤ That’s a lot of work, so change All Sizes back to Single Size.

Vector vs Bitmap

You imported a bitmap PNG image for the icon. For other assets, you can use vector formats, such as PDF or SVG. When possible, it’s always better to use vector formats. These are made up of lines, curves and fills. For a vector line, you can set a start point and an end point. When you scale the line, the vector resizes without losing any of its resolution. With a bitmap line, you must stretch or compress pixels.

This image shows two 50 pixel wide images scaled up by twelve to 600 pixels. One is bitmap and the other is vector. You can see the vector image loses none of its sharpness.

Bitmap vs vector
Bitmap vs vector

Adding a Vector Image

Later, your app will need a placeholder image to show whether there are any errors in loading an image.

➤ In Finder, drag in error-image.svg from the assets folder for this chapter to the asset catalog panel under AppIcon.

Error image
Error image

The image imports into Xcode in the 1x space, and leaves the 2x and 3x spaces empty. These spaces are for devices with different resolutions.

Device Resolutions and Image Scale

Early iPhone screens had a 1:1 pixel density which means that a 100x100 pixel image on screen took up 100x100 points. iPhone 4 introduced the retina screen. Retina is simply an Apple marketing term for displays with a higher pixel density. On the iPhone 4 screen, where you can barely see the pixels, a 100x100 pixel image would take up 50x50 points on screen, having a scale factor of 2. iPhone 6s Plus came along, introducing a 3:1 pixel density. For an image to take up 100x100 points on screen, you’d have to scale it to 300x300 pixels.

When you provide bitmap assets, you must provide them for every device resolution. However, error-image.svg is a vector format image with a native size of 512x512. You don’t need to scale it by 2x and 3x as Xcode can do this for you.

➤ With the error image selected, in the Attributes inspector, change Scales from Individual Scales to Single Scale.

Single scale
Single scale

Xcode removes the 2x and 3x options in the center panel. When you build for a 2x resolution device, Xcode will automatically add to your app bundle a 512x512 optimized bitmap image scaled to the correct 2x resolution. Bundle images are held in a .car file, the format of which is not publicly available, so you can’t inspect what Xcode has done.

Launch Screen

Skills you’ll learn in this section: launch screen; size classes

Another use for the asset catalog is to hold a launch screen image and background color that displays while your app is launching. You’ve already come across Info.plist in Chapter 7, “Saving Settings”. This .plist file is where you’ll set the launch image and color.

➤ Click Cards at the top of the Project navigator and choose the Cards target. Choose the Info tab, and you’ll see the contents of Info.plist in the Custom iOS Target Properties section.

Info.plist
Info.plist

You can add new items either by right-clicking an entry and choosing Add Row or by moving your cursor over an item and clicking the + sign that appears. You can delete items by clicking the - sign.

➤ Click the disclosure control next to Launch Screen, then add items for Image Name and for Background Color.

➤ Double-click in the Value field for Image Name and enter:

LaunchImage

➤ Double-click in the Value field for Background color and enter:

LaunchColor

You may need to resize the columns to show the Value column.

Launch configuration
Launch configuration

➤ Open Assets.xcassets, click the + sign at the bottom of the assets panel and choose Image Set. Rename Image to LaunchImage.

➤ Click the + sign at the bottom of the assets panel again and choose Color Set. Rename Color to LaunchColor.

When you run your app now, the app will use these for the launch screen. Unfortunately, the simulator doesn’t clear launch screen caches, so if you change your launch image or color, in Simulator, you’ll have to go to Device > Erase All Contents and Settings… and clear the simulator completely. On a device, deleting the app should be sufficient, but you might have to restart the device as well.

➤ Click LaunchImage in the catalog. You have the option of filling the three images. However, just as with the error image, you’ll use a single scale SVG image.

➤ In Finder, open assets/Launch Screen. Drag in launch-screen-light.svg to the 1x spot.

➤ In the Attributes inspector, change Scales to Single Scale.

Launch Image
Launch Image

This SVG with a transparent background has a native size of 200x500px. At build time, Xcode will create the appropriately scaled bitmap image from this and display it in the center of the screen. When you launch the app in landscape on iPhone, you’ll need an image with a smaller height, so you’ll use size classes to decide which image to load.

Size Classes

Size classes represent the content area available using horizontal and vertical traits. These two traits can be either regular or compact. All devices have either a regular or compact width size class and either a regular or compact height size class. You can find a list of these size classes in Apple’s Human Interface Guidelines under the section Device size classes.

This is an illustration of some iPhones and iPads laid on top of each other:

Size classes
Size classes

For height in portrait mode, all devices fit into the regular height section. In landscape, all iPhones use compact height, but some larger iPhones use regular width rather than the smaller compact width.

iPads are always regular width and regular height. However, you still have to take into account size classes on iPad, because of split screen and resizing app windows. When in portrait mode, split screen apps are both compact width. In landscape mode, the user can size between compact width and regular width.

For your app, the current launch image will fit on all devices except for iPhones in landscape. So you’ll specify a sideways image for compact height.

➤ In the Attributes inspector, change Height Class to Any & Compact.

Any & Compact height size class
Any & Compact height size class

A new space opens up for the compact height image.

➤ In Finder, from assets/Launch Screen, drag in launch-screen-landscape-light.svg to the compact height image space.

Launch screen images
Launch screen images

➤ Build and run, and your launch screen should show up briefly before your app does. Try rotating the simulator to get the different landscape launch screen. If your launch screen doesn’t show up, remember to erase the simulator contents.

Launch screen in landscape
Launch screen in landscape

Now that you’ve got the hang of adding new images for checked conditions in the Attributes inspector, you’ll be able to complete the challenge for Dark Mode at the end of the chapter.

Note: At the time of writing, there appears to be a bug in scaling. The SVG image sometimes stretches to full screen. If this bug persists, you would have to resize the image yourself to fit an iPad screen, instead of relying on the asset catalog to manage scaling.

Adding Sticker Images to Your App

Skills you’ll learn in this section: groups; reference folders; loading images from files; lazy loading

There’s one thing that an asset catalog does not allow you to do. You can’t enumerate all the images it contains.

When you release your cards app, one way of making it stand out from the crowd is to have some excellent stickers.

You could still add the stickers to an asset catalog, but you’d have to keep track of how many there are and ensure that you have a strict naming convention. All items in asset catalogs need to have names that are unique in the app bundle.

As your app becomes more popular, you’ll probably add more stickers and, maybe, categorize them into themes. It would be cumbersome to list each asset by name. You might have multiple artists working on stickers, and you wouldn’t want them to have access to your project.

A solution to this is to use reference folders. Instead of using an asset catalog, you’ll keep your sticker folder outside of your project and access it from the project as a reference folder.

➤ In Finder, take a look at the assets/Stickers/Camping folder. Shortly, you’ll add all these PNG files to your app.

Camping stickers
Camping stickers

Note: These stickers are from Pixabay. There are several sites, such as https://unsplash.com and https://www.pexels.com, where creators share their work and allow reuse of images. Before adding an image to your app, always check that the license allows commercial use and follow the license instructions. The stickers use Pixabay’s license: free for commercial use with no attribution.

Xcode Groups

When you look in the Project navigator, currently all your groups, except for the asset catalog, have gray folder icons.

Gray folder icons
Gray folder icons

➤ Click the Views group and open the File inspector.

Identity and Type for group
Identity and Type for group

Location describes how to store the group. Relative to Group means that Xcode traverses up the hierarchy of groups and folders to find an Absolute path. In your app’s case, this is the very top Cards item in the Project navigator.

Absolute location
Absolute location

When you create a new group, you can choose to mirror that group with a folder on disk. If you have a file selected inside a group connected to a disk folder, and you create a new group with File ▸ New ▸ Group, then Xcode will create both a new group and a new folder.

If your current selection is inside a logical group without a mirrored folder on disk, then Xcode won’t create a new folder for a new group. The option under File ▸ New ▸ Group will do the opposite. It changes between Group with Folder or Group without Folder depending upon whether your currently selected file is inside a mirrored group or not.

If your current file is in a logical group, the gray icon has a tiny triangle at the bottom left.

Groups
Groups

This is the file and folder organization in Finder:

Organization in Finder
Organization in Finder

Notice that File.swift isn’t in a physical subfolder because it’s contained in a logical group in Xcode.

Reference Folders

Unlike groups, Xcode doesn’t organize reference folders at all. When you bring a reference folder into the project, it will have a blue icon and its hierarchy will reflect the disk hierarchy.

➤ In Finder, locate assets/Stickers.

You’ll treat this folder as the master folder for your sticker assets. Any stickers that your artists create should go into this folder.

➤ Drag the Stickers folder into the Xcode Project navigator.

Uncheck Copy items if needed and choose Create folder references.

➤ Fill out the import screen with the following:

Add a reference folder
Add a reference folder

Warning: Whenever you drag a file or folder into Xcode, make sure you examine these settings. You’ll usually check Copy items if needed, and generally, you want to create groups, not folder references.

You now have a blue folder called Stickers in your project, with a blue sub-folder of Camping. The blue folder marks it as a reference folder. Xcode will only allow you to create folders inside this folder, not groups.

Reference folder
Reference folder

➤ In Finder, create a new folder inside Stickers called Nature and copy Camping/tree.png to this folder. Xcode will immediately update its hierarchy to reflect what’s happening on disk.

If you tried this with gray groups (don’t!), Xcode wouldn’t be able to find any files you moved in Finder.

Reference folder with new sub-folder
Reference folder with new sub-folder

In this folder hierarchy, you have two images with the same name tree.png. In an app bundle or in an asset catalog, you can’t have images with the same name, but this works here because the app contains the reference folder hierarchy.

Another advantage with reference folders, is that with Stickers as the top level folder, your artists can create new themes in different folders without touching the Xcode project.

➤ Delete the Nature folder as you don’t need it.

Note: Sometimes your project may lose the reference to the Stickers folder of images. In this case, Stickers will appear in the Project navigator in red. Choose the red folder name and, in the Attributes inspector, tap the folder icon under Location. Navigate to your Stickers folder and click Choose. Alternatively, you can delete this red item and re-import the Stickers folder as a reference folder. If you ever want confirmation of where the folder is, right-click the folder and choose Show in Finder.

Loading Files From Reference Folders

Now, you’ll create a Sticker view that loads images from the Stickers folder.

➤ In Single Card Views, create a new sub-group called Card Modal Views, and in this group, create a new SwiftUI View file called StickerModal.swift.

➤ Open CardToolbar.swift and locate .sheet(item: $currentModal).

➤ Add a new case to the switch statement before the default case:

case .stickerModal:
  StickerModal()

➤ Open SingleCardView.swift, Live Preview it, and pin the preview, so you can access it from other views.

➤ Try out your new stickers modal by tapping Stickers in live preview.

New Sticker modal view
New Sticker modal view

Loading a SwiftUI Image from a folder is not as easy as loading from an asset catalog. Asset catalogs do a lot of the heavy lifting. For example, if you add a vector file to an asset catalog, Xcode will convert to a native pixel format automatically, whereas it’s not an easy task to load a vector file from a folder.

When you load an image from a folder, you load it into an instance of UIKit’s UIImage. You also need to provide the full app bundle resource path.

➤ Open StickerModal.swift and replace body with:

var body: some View {
  // 1
  if let resourcePath = Bundle.main.resourcePath,
    // 2
    let image = UIImage(named: resourcePath +
      "/Stickers/Camping/fire.png") {
      Image(uiImage: image)
  } else {
    EmptyView()
  }
}

Going through the code:

  1. Get the full resource path of the app bundle.
  2. Load the UIImage using the full name and path of the sticker and use the uiImage parameter for creating the Image view.

➤ In Live Preview, on the pinned Single Card View, tap the Stickers button and you’ll see the sticker image.

Fire sticker
Fire sticker

However, you don’t only want one sticker, you want to see all of them. Depending on how many stickers you have, you shouldn’t load up all the UIImages at once, as loading images is resource heavy and will block the user interface.

You can load the file names up front and, as the user scrolls, load the image when it’s needed. This is called lazy loading.

➤ In StickerModal, create a new type method to load the sticker names:

static func loadStickers() -> [String] {
  var themes: [URL] = []
  var stickerNames: [String] = []
}

You’ll first load the folder names in the top level in the Stickers folder. These will be themes. You’ll be able to add new themes to your app in the future simply by adding a new folder inside the Stickers folder in Finder. You won’t have to change any code to do this.

➤ Add this code at the end of loadStickers():

// 1
let fileManager = FileManager.default
if let resourcePath = Bundle.main.resourcePath,
  // 2
  let enumerator = fileManager.enumerator(
    at: URL(fileURLWithPath: resourcePath + "/Stickers"),
    includingPropertiesForKeys: nil,
    options: [
      .skipsSubdirectoryDescendants,
      .skipsHiddenFiles
    ]) {
      // 3
      for case let url as URL in enumerator
      where url.hasDirectoryPath {
        themes.append(url)
      }
}

Going through the code:

  1. Load the default file manager and bundle resource path.
  2. Get a directory enumerator, if it exists, for the Stickers folder. For the options parameter, you skip subdirectory descendants and hidden files. Unless you skip the subdirectories, an enumerator will continue down the hierarchy. You currently just want to collect the top folder names as the themes.
  3. If the URL is a directory, add it to themes.

Next you’ll iterate through all the theme directories and retrieve the file names inside.

➤ Add this code after the previous code:

for theme in themes {
  if let files = try?
  fileManager.contentsOfDirectory(atPath: theme.path) {
    for file in files {
      stickerNames.append(theme.path + "/" + file)
    }
  }
}
return stickerNames

For each theme folder, you retrieve all the files in the directory and append the full path to stickerNames. You then return this array from the method.

➤ Create a new method in StickerModal to load a UIImage from a path:

func image(from path: String) -> UIImage {
  print("loading:", path)
  return UIImage(named: path)
    ?? UIImage(named: "error-image")
    ?? UIImage()
}

You temporarily print out the path name so that you can check whether you’re lazily loading the image. You then return the UIImage loaded from the path name. If you can’t load the image, return the error image from the asset catalog that you created earlier. As this is still optional and you need to return a non-optional, if everything fails, create a blank UIImage.

➤ Create a new property in StickerModal to hold the file names:

@State private var stickerNames: [String] = []

➤ Change body to:

var body: some View {
  ScrollView {
    ForEach(stickerNames, id: \.self) { sticker in
      Image(uiImage: image(from: sticker))
        .resizable()
        .aspectRatio(contentMode: .fit)
    }
  }
  .onAppear {
    stickerNames = Self.loadStickers()
  }
}

Instead of just showing one sticker, you iterate through all the sticker names and create an Image from the UIImage.

➤ To see the print output, build and run. Choose the first card and tap Stickers. Watch the debug console output, and you’ll see all the images are loading up front, ending with the tree and the guitar. As mentioned before, with a lot of stickers, this will block the user interface.

Stickers loaded
Stickers loaded

Debug console output
Debug console output

➤ To get the stickers to load lazily, in body, Command-click ForEach and embed it in a VStack.

➤ Change VStack { to:

LazyVStack {

➤ Build and run, and display the stickers modal screen again. Now, only the images that show on screen, plus the one just after, load. Scroll down, and you’ll see in the debug console that the guitar image loads as you approach it. Your images are now loading lazily.

Note: At the time of writing, there appears to be a SwiftUI bug that duplicates the calling of image(from:), causing the name of the image to print out twice in the debug console.

These images are much too big and would look much better in a grid. Fortunately, as well as lazy VStack and HStacks, SwiftUI provides a lazy loading grid view.

Using Lazy Grid Views

Skills you’ll learn in this section: grids

LazyVGrid and LazyHGrid provide vertical and horizontal grids. With the LazyVGrid, you define how to layout columns and, with the LazyHGrid, you layout rows.

➤ Add a new property to StickerModal:

let columns = [
  GridItem(spacing: 0),
  GridItem(spacing: 0),
  GridItem(spacing: 0)
]

➤ Change LazyVStack { to:

LazyVGrid(columns: columns) {

You still use the same ForEach and Image views but they now fit into the available space in the grid instead of taking up the whole width of the screen. The grid uses all the horizontal available space and divides it equally among the specified GridItems.

➤ To visualize this, in the design canvas panel, view Sticker Modal and click the Selectable icon. In the code panel, place the cursor on Image in body. The outlines of the Images will show in the preview.

Vertical grid
Vertical grid

Swift Tip: If this were a LazyHGrid, you would define rows in the same way as you have columns, and the grid would divide up the available vertical space. To scroll horizontally, add a horizontal axis: ScrollView(.horizontal).

➤ In the design canvas, click the Variants icon, and choose Orientation Variants.

Orientation variants
Orientation variants

Although the grid looks good in portrait mode, it would look better with more images horizontally when in landscape.

➤ Change the columns property declaration to:

let columns = [
  GridItem(.adaptive(minimum: 120), spacing: 10)
]

The columns will size to 120 points, separated by ten points.

The design canvas updates the views:

Adaptive grid
Adaptive grid

Swift Tip: As well as adaptive, GridItem.size can be fixed with a fixed size, or flexible, which sizes to the available space.

Selecting the Sticker

Now that you have the stickers showing, you’ll tap one to select it, dismiss the modal and add the sticker to the card as a card element.

➤ In StickerModal, add a property to hold the selected image:

@Binding var stickerImage: UIImage?

The parent of the modal will pass in a state property to hold the selected image.

➤ Add the environment property that holds the dismiss action:

@Environment(\.dismiss) var dismiss

You’ll use this property to dismiss the modal.

➤ In body, add a modifier to Image:

.onTapGesture {
  stickerImage = image(from: sticker)
  dismiss()
}

When the user taps an image, you’ll update the bound sticker image and dismiss the modal.

➤ Update the preview:

StickerModal(stickerImage: .constant(UIImage()))

You can now select a sticker and at the same time dismiss the modal. CardDetailView will then take over and store and show the selected sticker.

➤ Open CardToolbar.swift and add new properties to CardToolbar:

@Binding var card: Card
@State private var stickerImage: UIImage?

You receive the current card from the parent view and hold the current sticker chosen from StickerModal.

➤ Locate the sheet(item:) with the stickerModal case. This will have a compile error as you’re not yet passing the state property to StickerModal.

➤ Change StickerModal() to:

StickerModal(stickerImage: $stickerImage)
  .onDisappear {
    if let stickerImage = stickerImage {
      card.addElement(uiImage: stickerImage)
    }
    stickerImage = nil
  }

On dismissal of the modal, you should store the sticker as a card element and reset the sticker image to nil. You’ll get a compile error until you’ve added the card binding and written addElement(uiImage:).

➤ Open SingleCardView.swift and, in body, change the modal toolbar modifier to:

.modifier(CardToolbar(
  currentModal: $currentModal,
  card: $card))

Ensure that your CardToolbar parameters are in the same order that you listed the bindings in CardToolbar.swift, otherwise you will get a compile error.

Storing the Data

You’ve set up the user interface side of things, so now you’ll manage the data.

➤ In the Model group, open CardElement.swift.

Now that you’re adding image data, instead of storing an Image, which is a View, you’ll store the data and construct a view from that data.

➤ Add a new property to ImageElement:

var uiImage: UIImage?

This will hold the sticker image data.

Replace the image definition with:

var image: Image {
  Image(
    uiImage: uiImage ??
      UIImage(named: "error-image") ??
      UIImage())
}

You create a computed property that builds an Image from uiImage. If this is nil, present the error image in the asset catalog. If, for some reason this doesn’t exist, create a blank UIImage.

➤ Open Card.swift and add this new method to Card:

mutating func addElement(uiImage: UIImage) {
  let element = ImageElement(uiImage: uiImage)
  elements.append(element)
}

Here you take in a new UIImage and add a new ImageElement to the card. In the following chapter, you’ll be able to use this method for adding photos too.

➤ Build and run, select a card and add some stickers to it. Resize and reposition the stickers as you want and create a masterpiece :].

Make a fun picture!
Make a fun picture!

Challenges

Challenge 1: Set Up a Dark Mode Launch Screen

Your app currently has different launch screens for portrait and landscape, when the height size class is compact. Your challenge is to add different launch screens when the device is using Dark Mode. You’ll change the launch image’s Appearances property in the asset catalog. You’ll find the dark launch screen images in the assets folder. Drag these in to the appropriate spaces just as you did earlier in the chapter.

When you test on the simulator, to get the new launch screen to show, you’ll need to erase all device contents and settings.

Challenge 2: Set Up Launch Colors

This chapter did not cover colors specifically, but you can change appearance and device in the same way as with images. You’ve already set up a launch color in Info.plist to use as the launch background color. Change the launch color in the asset catalog. Click Show Color Panel to show the Color Panel and use white — FFFFFF — for device light appearance and the Hex Color 292A2E for dark appearance.

The Color Panel
The Color Panel

If you get stuck, the asset catalog in the project in the challenge project will show you what to do.

Key Points

  • Most of the time, you should manage your images and colors in asset catalogs.
  • If the asset catalog is not suitable for purpose, then use reference folders.
  • In asset catalogs, favor vector images over bitmaps. They are smaller in file size and retain sharpness when scaled. Xcode will automatically scale to the appropriate dimensions for the current device.
  • Think about how you can make your app special. Good app design together with artwork can really make you stand out from the crowd.

Where to Go From Here?

In this chapter you used app icons and launch screens. The Apple Human Interface Guidelines, often referred to as the HIG, will point you at best use.

For example, rather than using branding on the launch screen, they suggest making your launch screen similar to the first screen in your app, so that it appears that the app loads quickly.

You should study the HIG so that you know what Apple is looking for in an app. People who use Apple devices enjoy clean, crisp interfaces, and the guidelines will help you follow this quest. If you follow the guidelines diligently, you might even be featured by Apple in the app store.

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.
© 2026 Kodeco Inc.