Chapters

Hide chapters

SwiftUI by Tutorials

Fifth Edition · iOS 16, macOS 13 · Swift 5.8 · Xcode 14.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

5. Intro to Controls: Text & Image
Written by Antonio Bello

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

From what you’ve seen so far, you’ve already figured out what level of awesomeness SwiftUI brings to UI development. And you’ve probably started wondering how you could possibly have used such a medieval method to design and code the UI in your apps — a method that responds to the name of UIKit, or AppKit, if you prefer.

Armored Knights
Armored Knights

In the previous chapters, you’ve only scratched the surface of SwiftUI and learned how to create some basic UI. Additionally, you’ve wrapped your head around what SwiftUI offers and what you can do with it.

In this chapter, you’re going to work with some of the most-used controls in UI development, which are also available in UIKit and AppKit, while learning a little more about the SwiftUI equivalents.

To do so, you’ll work on Kuchi, a language flashcard app, which will keep you busy for the next seven chapters. Enjoy!

Getting Started

First, open the starter project for this chapter, and you’ll see that it’s quite empty. There’s almost no user interface; only some resources and support files. If you build and run, all you’ll get is a blank view.

In the Project Navigator, locate the Shared group, then right click on it, choose New Group, and rename as Welcome.

Create the Welcome group
Create the Welcome group

Next right-click on it, and choose New File.

In the popup that comes next, choose SwiftUI View, then click Next.

Select SwiftUI View
Select SwiftUI View

Then type WelcomeView in the Save As field, ensure that both iOS and macOS targets are selected, and click on Create. You now have a blank new view to start with.

Changing the Root View

Before doing anything, you need to configure the app to use the new WelcomeView as the starting view. Open KuchiApp.swift, and locate the body property, which contains an EmptyView inside a WindowGroup.

var body: some Scene {
  WindowGroup {
    EmptyView()
  }
}
WindowGroup {
  WelcomeView()
}
Hello World
Dugzu Doztl

struct KuchiApp_Previews: PreviewProvider {
  static var previews: some View {
    EmptyView()
  }
}
struct KuchiApp_Previews: PreviewProvider {
  static var previews: some View {
    WelcomeView()
  }
}

WelcomeView!

Now, take a look at the newly created view. Open WelcomeView, and you will notice there isn’t much in it:

Hello World image
Fumra Fibdr elaki

Text

Input requires context. If you see a blank text input field, with no indication of what its purpose is, your user won’t know what to put in there. That’s why text is important; it provides context — and you’ve probably used tons of UILabels in your previous UIKit or AppKit-based apps.

Text("Welcome to Kuchi")
Welcome to Kuchi
Tohziru li Nirvo

Modifiers

Now that you’ve displayed some text on your screen, the next natural step is to change its appearance. There are plenty of options, like size, weight, color, italic, among others, that you can use to modify how your text looks on the screen.

Kuchi Package
Fiqsa Mihtoga

Text("Welcome to Kuchi")
  .font(.system(size: 60))
Text("Welcome to Kuchi")
  .font(.system(size: 60))
  .bold()
Kuchi Steps
Yosri Zdakf

Text("Welcome to Kuchi")
  .font(.system(size: 60))
  .bold()
  .foregroundColor(.red)
Text("Welcome to Kuchi")
  .font(.system(size: 60))
  .bold()
  .foregroundColor(.red)
  .multilineTextAlignment(.center)
Text("Welcome to Kuchi")
  .font(.system(size: 60))
  .bold()
  .foregroundColor(.red)
  .multilineTextAlignment(.center)
  .lineLimit(1)
Kuchi Steps 2
Carzi Rjayl 4

Text("Welcome to Kuchi")
  .font(.system(size: 60))
  .bold()
  .foregroundColor(.red)
  .multilineTextAlignment(.center)
  .lineLimit(2)
Kuchi Steps 3
Birsi Cdirz 1

Canvas Selectable mode
Rizkid Palupsijde fodi

Canvas modifiers
Woxcin logeliepv

Inspector modifiers
Enscunmay waxekaepq

XCode documentation
YZige wejelomxodiiq

Views and modifiers libraries
Doidt ulh xofajuutx wowgiveih

Are Modifiers Efficient?

Since every modifier returns a new view, you might be wondering if this process is really the most efficient way to go about things. SwiftUI embeds a view into a new view every time you invoke a modifier. It’s a recursive process that generates a stack of views; you can think of it as a set of virtual Matryoshka dolls, where the smallest view that’s buried inside all the others is the first one on which a modifier has been called.

Russian Dolls
Nihfouy Locxx

Order of Modifiers

Is the order in which you invoke modifiers important? The answer is “yes”, although in many cases the answer becomes “it doesn’t matter” — at least not from a visual perspective.

Text("Welcome to Kuchi")
  .bold()
  .foregroundColor(.red)
Text("Welcome to Kuchi")
  .foregroundColor(.red)
  .bold()
Modifiers
Lagenoutv

Text("Welcome to Kuchi")
  .background(Color.red)
  .padding()
Text("Welcome to Kuchi")
  .padding()
  .background(Color.red)
Modifiers background
Hijazeilt xictctuuqx

Text("Welcome to Kuchi")
  .background(Color.yellow)
  .padding()
  .background(Color.red)
Modifiers color padding
Gukitaavw nawex xapdiwn

Image

An image is worth a thousand words. That may be a cliché, but it’s absolutely true when it comes to your UI. This section shows you how to add an image to your UI.

var body: some View {
  Image(systemName: "table")
}
Raw image
Lij eqaso

Changing the Image Size

When you create an image without providing any modifiers, SwiftUI will render the image at its native resolution and maintain the image’s aspect ratio. The image you’re using here is taken from SF Symbols, a set of icons that Apple introduced in the 2019 iterations of iOS, macOS, watchOS and tvOS and that we have already used in previous chapters. For more information, check out the links at the end.

var body: some View {
  Image(systemName: "table")
    .frame(width: 30, height: 30)
}
Non-resizable image
Wiv-zupolezre anete

var body: some View {
  Image(systemName: "table")
    .resizable()
    .frame(width: 30, height: 30)
}
Resizable image
Tocuyewji uyile

// 1
.cornerRadius(30 / 2)
// 2
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
// 3
.background(Color(white: 0.9))
// 4
.clipShape(Circle())
// 5
.foregroundColor(.red)
Image modifiers stack
Eforo pukasiivd tsosx

Brief Overview of Stack Views

Before moving to the next topic, you’ll need to recover the code you removed while working on the Image in the previous section.

Image(systemName: "table")
  .resizable()
  .frame(width: 30, height: 30)
  .overlay(Circle().stroke(Color.gray, lineWidth: 1))
  .background(Color(white: 0.9))
  .clipShape(Circle())
  .foregroundColor(.red)

Text("Welcome to Kuchi")
  .font(.system(size: 30))
  .bold()
  .foregroundColor(.red)
  .lineLimit(2)
  .multilineTextAlignment(.center)
HStack {
  Image(systemName: "table")
    ...

  Text("Welcome to Kuchi")
    ...
}
First hstack
Pagmf ydvoym

More on Image

Two sections ago, you played with the Image view, creating an icon at the end of the process. In this section, you’ll use Image once again to create a background image to display on the welcome screen.

ZStack {
  HStack {
    ...
  }
}
Image("welcome-background", bundle: nil)
Welcome
Cafcufu

Background image modifiers
Hizmvcuijl ogihe rahiduavh

// 1
Image("welcome-background", bundle: nil)
  // 2
  .resizable()
  // 3
  .scaledToFit()
  // 4
  .aspectRatio(1 / 1, contentMode: .fill)
  // 5
  .edgesIgnoringSafeArea(.all)
  // 6
  .saturation(0.5)
  // 7
  .blur(radius: 5)
  // 8
  .opacity(0.08)
.aspectRatio(2 / 1, contentMode: .fill)
Custom ratio
Jusxic comii

Image("welcome-background", bundle: nil)
  .resizable()
  .aspectRatio(1 / 1, contentMode: .fill)
  .edgesIgnoringSafeArea(.all)
  .saturation(0.5)
  .blur(radius: 5)
  .opacity(0.08)

Splitting Text

Now that the background image is in good shape, you need to rework the welcome text to make it look nicer. You’ll do this by making it fill two lines by using two text views instead of one. Since the text should be split vertically, all you have to do is add a VStack around the welcome text, like so:

VStack {
  Text("Welcome to Kuchi")
    .font(.system(size: 30))
    .bold()
    .foregroundColor(.red)
    .multilineTextAlignment(.center)
    .lineLimit(2)
}
VStack {
  Text("Welcome to")
    .font(.system(size: 30))
    .bold()
    .foregroundColor(.red)
    .multilineTextAlignment(.center)
    .lineLimit(2)
  Text("Kuchi")
    .font(.system(size: 30))
    .bold()
    .foregroundColor(.red)
    .multilineTextAlignment(.center)
    .lineLimit(2)
}
VStack {
  Text("Welcome to")
    .font(.system(size: 30))
    .bold()
  Text("Kuchi")
    .font(.system(size: 30))
    .bold()
}
.foregroundColor(.red)
.multilineTextAlignment(.center)
.lineLimit(2)
.multilineTextAlignment(.leading)
VStack(alignment: .leading) {
.lineLimit(1)
VStack(alignment: .leading) {
  Text("Welcome to")
    .font(.system(size: 30))
    .bold()
  Text("Kuchi")
    .font(.system(size: 30))
    .bold()
}
.foregroundColor(.red)
.lineLimit(1)
.font(.headline)
.font(.largeTitle)
VStack(alignment: .leading) {
  Text("Welcome to")
    .font(.headline)
    .bold()
  Text("Kuchi")
    .font(.largeTitle)
    .bold()
}
.foregroundColor(.red)
.lineLimit(1)
.padding(.horizontal)
Welcome to Kuchi
Monpoye ge Xotmo

Markdown

New to SwiftUI 3.0, Text now supports a subset of markdown, which is a markup language for creating formatted text. If you don’t know what it is, check out the links at the end of this chapter.

  Text("**Welcome to**")
    .font(.headline)
  Text("**Kuchi**")
    .font(.largeTitle)

Accessibility With Fonts

Initially, all of your views that display text used a font(.system(size: 30)) modifier, which changed the font used when rendering the text. Although you have the power to decide which font to use, as well as its size, Apple recommends favoring size classes over absolute sizes where you can. This is why, in the previous section, you used styles such as .headline and .largeTitle in place of .system(size: 30)

Label: Combining Image and Text

Image and Text are frequently used one next to the other. Combining them is pretty easy — you just need to embed them into an HStack. However, to simplify your work, Apple has given you a component specifically for that purpose: Label.

Label("Welcome", systemImage: "hand.wave")
Label Example
Gubag Ajaflxa

init(title: () -> Title, icon: () -> Icon)
HStack {
  Image(systemName: "table")
    .resizable()
    .frame(width: 30, height: 30)
    .overlay(Circle().stroke(Color.gray, lineWidth: 1))
    .background(Color(white: 0.9))
    .clipShape(Circle())
    .foregroundColor(.red)

  VStack(alignment: .leading) {
    Text("Welcome to")
      .font(.headline)
      .bold()
    Text("Kuchi")
      .font(.largeTitle)
      .bold()
  }
  .foregroundColor(.red)
  .lineLimit(1)
  .padding(.horizontal)        
}
Label {
  // 1
  VStack(alignment: .leading) {
    Text("Welcome to")
      .font(.headline)
      .bold()
    Text("Kuchi")
      .font(.largeTitle)
      .bold()
  }
  .foregroundColor(.red)
  .lineLimit(2)
  .multilineTextAlignment(.leading)
  .padding(.horizontal)
// 2
} icon: {
  // 3
  Image(systemName: "table")
    .resizable()
    .frame(width: 30, height: 30)
    .overlay(Circle().stroke(Color.gray, lineWidth: 1))
    .background(Color(white: 0.9))
    .clipShape(Circle())
    .foregroundColor(.red)
}
Refactored with label
Daxozjutol wepp hatiq

func makeBody(configuration: Self.Configuration) -> Self.Body
import SwiftUI

struct HorizontallyAlignedLabelStyle: LabelStyle {
  func makeBody(configuration: Configuration) -> some View {
    return EmptyView()
  }
}
func makeBody(configuration: Configuration) -> some View {
  HStack {
    configuration.icon
    configuration.title
  }
}

.labelStyle(HorizontallyAlignedLabelStyle())
Label with Custom Style
Qedex fejs Movlil Ptsfa

Key Points

  • You use the Text and Image views to display and configure text and images respectively.
  • You use Label when you want to combine a text and an image into a single component.
  • Text natively “understands” markdown.
  • You use modifiers to change the appearance of your views. Modifiers can be quite powerful when used in combination, but remember to be aware of the order of the modifiers, because in some cases it does matter.
  • Container views, such as VStack, HStack and ZStack let you group other views vertically, horizontally or even one on top of another.

Where to Go From Here?

SwiftUI is still fairly new and evolving as a technology. The best reference is always the official documentation, even though it’s not always generous with descriptions and examples:

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