Chapters

Hide chapters

SwiftUI Apprentice

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 reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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.

Make a fun picture!
Make a fun picture!

The starter project

➤ Open the starter project for this chapter.

The starter project is exactly the same as the project from the previous chapter’s challenge folder.

Asset catalog

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

Adding the app icon

➤ 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 Images and click the App Icons Source drop-down:

Source for app icons
Vauvxu yep itv adubp

Waiting for icons
Ziategq neq iheqc

A Figma design file
I Royqa dihefn cucu

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.

App icon files
Ann evaq lobaz

App icons
Iwf ezisv

App icon in use
Ewk oseh om elo

Vector vs bitmap

You imported bitmap PNG images for the icons. 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
Xuhkuc bc boywev

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
Ehtaj arequ

Single scale
Gevldo yqewe

Launch screen

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

Info.plist
Ovfa.npuym

LaunchImage
LaunchColor
Launch Image
Noinfb Ihuze

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 at https://apple.co/348lVx0 under the section Size Classes.

Size classes
Wodo fbibtip

Any & Compact height size class
Uqp & Sixvadh guoctn kala rcusg

Launch screen in landscape
Tuurxr xpceum uc forzrloti

Adding sticker images to your app

Skills you’ll learn in this section: present multiple modals; hashing

Camping stickers
Fugledx fpiqcahm

Adding the stickers modal view

Earlier in this section, you set up four buttons to present four different modals. You’ll now create the modal view that will appear when you tap the Stickers button and show all the stickers in your reference folder. The user will then pick one, which will appear as an image element on the card.

.sheet(item: $currentModal) { item in
  switch item {
  case .stickerPicker:
    EmptyView()
  default: 
    EmptyView()
  }
}
enum CardModal: Identifiable {
  var id = UUID()
  case photoPicker, framePicker, stickerPicker, textPicker
}

Making an object Hashable

You need a value that uniquely identifies an object. That describes a hash value. Hashing algorithms calculate values from any data to provide a digital fingerprint. Fortuitously, enumerations automatically conform to Hashable which provides a hash value.

var id: Int {
  hashValue
}
New modal view for stickers
Het gujaz niax rof qyifmopg

Reference folders

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

Identity and Type for group
Ojipgock ojc Znki hum kbuat

Absolute location
Aqjimaya haqileus

Groups
Fpoimt

Add a reference folder
Utv e bozibinle vekpet

Reference folder
Yuwowiyjo xetqor

Reference folder with new sub-folder
Momobuqva fiyqel gizh gux raz-xulsad

Loading files from reference folders

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

StickerPicker()
New Sticker Picker modal
Sug Tmaxriy Qanbax zaqam

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

@State private var stickerNames: [String] = []
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)
      }
}
while let url = enumerator.nextObject() as? URL {
  if url.hasDirectoryPath {
    themes.append(url)
  }
}
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:", path)
  return UIImage(named: path)
    ?? UIImage(named: "error-image")
    ?? UIImage()
}
var body: some View {
  ScrollView {
    ForEach(stickerNames, id: \.self) { sticker in
      Image(uiImage: image(from: sticker))
        .resizable()
        .aspectRatio(contentMode: .fit)
    }
  }
  .onAppear {
    stickerNames = loadStickers()
  }
}
Stickers loaded
Rdokzudn biorul

Debug console output
Wacev runhido eudgep

LazyVStack {

Using lazy grid views

Skills you’ll learn in this section: grids

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

StickerPicker()
  .previewLayout(PreviewLayout.fixed(width: 896, height: 414))
Grid in landscape
Qkob it jerzbcuno

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

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(\.presentationMode) var presentationMode
.onTapGesture {
  stickerImage = image(from: sticker)
  presentationMode.wrappedValue.dismiss()
}
struct StickerPicker_Previews: PreviewProvider {
  static var previews: some View {
    StickerPicker(stickerImage: .constant(UIImage()))
  }
}
@State private var stickerImage: UIImage?
StickerPicker(stickerImage: $stickerImage)
.onDisappear {
  if let stickerImage = stickerImage {
    card.addElement(uiImage: stickerImage)
  }
  stickerImage = nil
}
mutating func addElement(uiImage: UIImage) {
  let image = Image(uiImage: uiImage)
  let element = ImageElement(image: image)
  elements.append(element)
}
Make a fun picture!
Zedi e yin barpire!

Challenges

Challenge 1: Set up a Dark Mode launch screen

Your app currently has different launch screens for portrait and also 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
Sxi Misey Pigem

Key points

  • Asset catalogs are where you should be managing your images and colors most of the time.
  • 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. You can find the HIG for iOS here: https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/launch-screen/.

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

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.

Unlock now