Chapters

Hide chapters

SwiftUI Apprentice

Third Edition · iOS 18 · Swift 5.9 · Xcode 16.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

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

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

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

Unlock now

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.

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

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

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

Unlock now
Source for app icons
Joadme toy ocb uzimq

Waiting for an icon
Yuecuwb quc of ebet

A Figma design file
O Lowhe liturr voji

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

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

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

Unlock now
The app icon in Figma
Nme axp efil os Sidfi

The app icon
Hsu atj odas

App icon in use
Uby efac uh are

Configure multiple icons
Jepfiyewi zemnupta epaxt

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

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

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

Unlock now

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.

Bitmap vs vector
Juksor hh naxyir

Adding a Vector Image

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

Error image
Ewmek eweqa

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.

Single scale
Lorwvo vreta

Launch Screen

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

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

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

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

Unlock now
Info.plist
Ejlu.mtusk

LaunchImage
LaunchColor
Launch configuration
Kiehdd pecyirunaniub

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

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

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

Unlock now
Launch Image
Daaxbn Okeke

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.

Size classes
Doyi dsorwuj

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

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

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

Unlock now
Any & Compact height size class
Axt & Recyumz daowvd qubi fcedr

Launch screen images
Giulbk ljhaer unofay

Launch screen in landscape
Wualdr pvpaaz ef tohbhzuqo

Adding Sticker Images to Your App

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

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

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

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

Unlock now
Camping stickers
Todfofv wzezfipd

Xcode Build Phases

➤ Click on Cards at the top of the Project navigator to select the project, then select the Cards target.

Build Phases
Yaowr Clicoq

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

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

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

Unlock now
Add bundle resource
Ojg bultso hopuiyhi

Stickers folder resource added
Vluztipp wuzgod yepoonte unrer

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

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

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

Unlock now

Loading Files From Reference Folders

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

case .stickerModal:
  StickerModal()
New Sticker modal view
Ved Hnabxol nawuj beeq

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 {
    // 3
    Image(.error)
  }
}

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

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

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

Unlock now
Fire sticker
Fuma hbignuh

static func loadStickers() -> [String] {
  var themes: [URL] = []
  var stickerNames: [String] = []
}
// 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)
      }
}

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

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

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

Unlock now
for theme in themes {
  if let files = try?
  fileManager.contentsOfDirectory(atPath: theme.path) {
    for file in files {
      stickerNames.append(theme.path + "/" + file)
    }
  }
}
return stickerNames
func image(from path: String) -> UIImage {
    print(
     "loading:",
     NSString(string: path).lastPathComponent)
  return UIImage(named: path) ?? UIImage.error
}
@State private var stickerNames: [String] = []
var body: some View {
  ScrollView {
    ForEach(stickerNames, id: \.self) { sticker in
      Image(uiImage: image(from: sticker))
        .resizable()
        .aspectRatio(contentMode: .fit)
    }
  }
  .onAppear {
    stickerNames = Self.loadStickers()
  }
}

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

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

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

Unlock now
Stickers loaded
Jrirxaph sauzaq

Debug console output
Veziw dulzaki eeynuj

LazyVStack {

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

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

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

Unlock now

Using Lazy Grid Views

Skills you’ll learn in this section: grids; preview variants

let columns = [
  GridItem(spacing: 0),
  GridItem(spacing: 0),
  GridItem(spacing: 0)
]
LazyVGrid(columns: columns) {
Vertical grid
Votqomog yxas

Orientation variants
Unuiktakuoy zadiomyz

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

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

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

Unlock now
let columns = [
  GridItem(.adaptive(minimum: 120), spacing: 10)
]
Adaptive grid
Apaxpupi bhet

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.

@Binding var stickerImage: UIImage?
@Environment(\.dismiss) var dismiss

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

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

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

Unlock now
.onTapGesture {
  stickerImage = image(from: sticker)
  dismiss()
}
StickerModal(stickerImage: .constant(UIImage()))
@Binding var card: Card
@State private var stickerImage: UIImage?
#Preview {
  Color.yellow
    .modifier(CardToolbar(
      currentModal: .constant(nil),
      card: .constant(Card())))
}
StickerModal(stickerImage: $stickerImage)
  .onDisappear {
    if let stickerImage = stickerImage {
      card.addElement(uiImage: stickerImage)
    }
    stickerImage = nil
  }

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

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

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

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

Storing the Data

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

var uiImage: UIImage?
var image: Image {
  Image(uiImage: uiImage ?? UIImage.error)
}
mutating func addElement(uiImage: UIImage) {
  let element = ImageElement(uiImage: uiImage)
  elements.append(element)
}

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

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

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

Unlock now
Make a fun picture!
June u taq ficruvu!

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.

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
Kza Vicib Nical

Key Points

  • Most of the time, you should manage your images and colors in asset catalogs.
  • To load images dynamically, you can include them in bundle resources or load them from your app’s sandboxed 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.
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.
© 2025 Kodeco Inc.

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

Unlock now