Home Business & Other SwiftUI Cookbook - Third Edition

SwiftUI Cookbook - Third Edition

By Juan C. Catalan
books-svg-icon Book
eBook $35.99 $24.99
Print $44.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $35.99 $24.99
Print $44.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Using the Basic SwiftUI Views and Controls
About this book
SwiftUI is the modern way to build user interfaces for iOS, macOS, and watchOS. It provides a declarative and intuitive way to create beautiful and interactive user interfaces. The new edition of this comprehensive cookbook includes a fully updated repository for SwiftUI 5, iOS 17, Xcode 15, and Swift 5.9. With this arsenal, it teaches you everything you need to know to build beautiful and interactive user interfaces with SwiftUI 5, from the basics to advanced topics like custom modifiers, animations, and state management. In this new edition, you will dive into the world of creating powerful data visualizations with a new chapter on Swift Charts and how to seamlessly integrate charts into your SwiftUI apps. Further, you will be able to unleash your creativity with advanced controls, including multi-column tables and two-dimensional layouts. You can explore new modifiers for text, images, and shapes that give you more control over the appearance of your views. You will learn how to develop apps for multiple platforms, including iOS, macOS, watchOS, and more. With expert insights, real-world examples, and a recipe-based approach, you’ll be equipped to build remarkable SwiftUI apps that stand out in today’s competitive market.
Publication date:
December 2023
Publisher
Packt
Pages
798
ISBN
9781805121732

 

Using the Basic SwiftUI Views and Controls

SwiftUI was launched during Apple’s Worldwide Developer Conference (WWDC) in June 2019. Since then, its popularity has kept increasing as it has been adopted widely by the Apple developer community. Apple also releases updates every year, adding new and exciting capabilities to SwiftUI.

SwiftUI is a UI framework that ditches UIKit concepts such as Auto Layout for an easier-to-use declarative programming model. SwiftUI is Apple’s preferred way to build user interfaces. It is a platform-agnostic framework that allows the fast and easy creation of applications that work across Apple platforms (iOS, iPadOS, macOS, WatchOS, and tvOS).

There is no question today about the need to learn about SwiftUI. Apple released the fifth iteration of the framework in 2023 and it is adding more features every year. Here are some other compelling reasons to be proficient in SwiftUI:

  • SwiftUI apps can work alongside UIKit apps: You can slowly convert your app’s user interface (UI) to SwiftUI, one screen at a time.
  • Industry adoption: SwiftUI has already been adopted by the industry as it was released four years ago. Just looking at job postings for iOS developers, you’ll find out that most of them require experience with SwiftUI. Learning about SwiftUI is a must for current iOS development jobs. In a few more years, SwiftUI will dominate the app development for all the Apple platforms, the same way that Swift took over from Objective-C a few years ago.
  • Low learning curve: SwiftUI offers a low learning curve for people who have used declarative programming before. It is also a great way to start learning declarative programming for those with little to no experience.
  • Live previews increase speed: SwiftUI live previews provide an instant preview of your UI without having to recompile the whole app. You can quickly prototype apps and make any changes required by your users. This greatly improves the speed of UI development.

This book is designed to be your SwiftUI reference material. Each project focuses on a single concept so that you can understand each concept thoroughly, and then combine multiple concepts to build amazing applications.

In this chapter, we will learn about views and controls, SwiftUI’s visual building blocks for app user interfaces. The following recipes will be covered:

  • Laying out components
  • Dealing with text
  • Using images
  • Adding buttons and navigating with them
  • Beyond buttons: using advanced pickers
  • Applying groups of styles using ViewModifier
  • Separating presentation from content with ViewBuilder
  • Simple graphics using San Francisco Symbols (SF symbol)
  • Integrating UIKit into SwiftUI—the best of both worlds
  • Adding SwiftUI to a legacy UIKit app
  • Exploring more views and controls
 

Technical requirements

The code in this chapter is based on Xcode 15.0 and iOS 17.0. You can download and install the latest version of Xcode from the App Store. You’ll also need to be running macOS Ventura (13.4) or newer.

Simply search for Xcode in the App Store and select and download the latest version. Launch Xcode and follow any additional installation instructions that your system may prompt you with. Once Xcode has fully launched, you’re ready to go.

All the code examples for this chapter can be found on GitHub at https://github.com/PacktPublishing/SwiftUI-Cookbook-3rd-Edition/tree/main/Chapter01-Using-the-basic-SwiftUI-Views-and-Controls/.

 

What’s new in SwiftUI

SwiftUI has been evolving since the day it was announced in 2019. Every year, Apple adds new APIs and SwiftUI becomes more and more powerful. At the time of this writing, it is possible to create an iOS app exclusively using SwiftUI, without having to integrate UIKit. Since the previous edition of this book, Apple added new functionality to SwiftUI.

In WWDC 2022, Apple added new features to SwiftUI, improved some existing features, and even deprecated some of the APIs just introduced a few years ago. These are the most relevant features:

  • Swift Charts, which allows you to create data visualizations across all Apple platforms
  • New data-driven navigation with NavigationStack and NavigationSplitView
  • Enhancements to Form, to support multi-platform apps with a single code base:
    • LabeledContent view to display pairs of data, like key-value pairs or title and description, inside forms
    • Deeper customization of multi-line TextField instances
    • New MultiDatePicker control to select more than one date
    • New mixed-state controls for Toggle and Picker views and a new format for Stepper views.
  • Table, introduced in macOS 12, now available on iPadOS 16 and iOS 16, and new toolbar customization for iPadOS
  • PhotosPicker view, a multi-platform and privacy-preserving API for picking photos and videos
  • ShareLink view, which enables the presentation of the share sheet even in WatchOS
  • Transferable protocol, to share data between apps
  • ShapeStyle extensions like gradient and shadow, which can also be applied to SFSymbols
  • Grid, to arrange content in a two-dimensional way
  • Layout protocol to create full-custom layouts

In WWDC 2023, Apple added more functionality to SwiftUI. These are the most relevant features:

  • Interactivity and animation added to widgets
  • Improvement to Xcode previews with a new Preview(_:traits:body:) macro that supports UIKit and AppKit out of the box
  • Native SwiftUI support for MapKit
  • Interactivity and pie charts added to Swift Charts
  • Navigation, date pickers, and toolbars available in WatchOS 10
  • SwiftData, a successor of CoreData, used to persist data between app launches
  • The Observable macro, and new @State, @Environment, and @Bindable property wrappers, offering a new way of sharing data throughout the app
  • New powerful animations with the new spring animation, the PhaseAnimator struct, and the Keyframe Animator and CustomAnimation protocol
  • Inspector, a new modal presentation with the inspector(isPresented:content:) view modifier
  • Symbol Effects, animated symbols added to SF Symbols 5
  • New powerful scrolling APIs: transition effects, scroll position, paged scrolling, and inset control
  • Enhancements to list and tables: item selection, expanding sections programmatically, column visibility, column header visibility, hierarchical rows, and alternating row background
  • New dialog customizations, new gestures, and new input events
 

Laying out components

In this very first recipe of the book, we will start laying out components. In SwiftUI, our user interface is composed of different elements. It is very important to understand how to group components together in different layouts. SwiftUI uses three basic layout components, VStack, HStack, and ZStack. Use the VStack view to arrange components on a vertical axis, HStack to arrange components on a horizontal axis, and—you guessed it right—use the ZStack to arrange components along the vertical and horizontal axis.

In this recipe, we will also look at spacing and adjust the position used to position elements. We will also look at how Spacer and Divider can be used for layout.

Getting ready

Let’s start by creating a new SwiftUI project called TheStacks. Use the following steps:

  1. Start Xcode (Finder | Applications | Xcode).
  2. Click on Create a new Xcode project from the left pane.
  3. The following screen asks us to choose an Xcode template. Select iOS, and then App. Click Next.
  4. A screen to select the options for the project will appear. Enter the product name, TheStacks.
  5. Make sure that Interface is set to SwiftUI, Language is set to Swift, and Storage is set to None. Click Next.
  6. Select the folder location to store the project and choose if you want to create a Git repository or not. Then click Create.

How to do it…

Let’s implement the VStack, HStack, and ZStack within a single screen to better understand how each works and the differences between them. The steps are given here:

  1. Select the Contentview/Contentview.swift file on the navigation pane (left side of Xcode).
  2. Replace the content of the body variable with a VStack and some Text views:
        var body: some View {
            VStack {
                Text("VStack Item 1")
                Text("VStack Item 2")
                Text("VStack Item 3")
            }
            .background(.blue)
        }
    
  3. Press Cmd + Option + Enter if the canvas is not visible, then click on the Resume button above the canvas window to display the resulting view:

Figure 1.1: VStack with three items

  1. Add a Spacer and a Divider between VStack Item 2 and VStack Item 3:
    Spacer()
    Divider()
       .background(.black)
    
  2. The content expands and covers the screen’s width and height, as follows:

Figure 1.2: VStack + Spacer + Divider

  1. Add an HStack and a ZStack below VStack Item 3:
    HStack{
       Text("HStack Item 1")
       Divider()
           .background(.black)
       Text("HStack Item 2")
       Divider()
            .background(.black)
       Spacer()
       Text("HStack Item 3")
    }
    .background(Color.red)
    ZStack{
       Text("ZStack Item 1")
           .padding()
           .background(.green)
           .opacity(0.8)
       Text("ZStack Item 2")
           .padding()
           .background(.green)
           .offset(x: 80, y: -400)
    }
    
  2. The preview should look like the following screenshot (it may vary depending on the device selected for previews):

Figure 1.3: VStack, HStack, and ZStack

This concludes our recipe on using stacks. Going forward, we’ll make extensive use of VStack and HStack to position various components in our views.

How it works…

In Xcode 15, a new iOS app project with SwiftUI selected as the interface option starts with a VStack that includes an Image view and a Text view located at the center of the screen. We replaced the content provided by the template with our own VStack, with three embedded Text views. SwiftUI container views like VStack determine how to display content by using the following steps:

  1. Figure out its internal spacing and subtract that from the size proposed by its parent view.
  2. Divide the remaining space into equal parts.
  3. Process the size of its least flexible view.
  4. Divide the remaining unclaimed space by the unallocated space, and then repeat Step 2.
  5. The stack then aligns its content and chooses its own size to exactly enclose its children.

Adding the Spacer() forces the view to use the maximum amount of vertical space. This is because the Spacer() is the most flexible view—it fills the remaining space after all other views have been displayed.

The Divider() component is used to draw a horizontal line across the width of its parent view. That is why adding a Divider() view stretched the VStack background from just around the Text views to the entire width of the VStack. By default, the divider line does not have a color. To set the divider color, we add the .background(.black) modifier. Modifiers are methods that can be applied to a view to return a new view. In other words, it applies changes to a view. Examples include .background(.black), .padding(), and .offset(…).

The HStack container is like the VStack but its contents are displayed horizontally from left to right. Adding a Spacer() in an HStack thus causes it to fill all available horizontal space, and a divider draws a vertical line between components in the HStack.

The ZStack is like HStack and VStack but overlays its content on top of existing items.

There’s more…

You can also use the .frame modifier to adjust the width and height of a component. Try deleting the Spacer() and Divider() from the HStack and then apply the following modifier to the HStack:

.frame(
    maxWidth: .infinity,
    maxHeight: .infinity,
    alignment: .topLeading
)
 

Dealing with text

The most basic building block of any application is text, which we use to provide or request information from a user. Some text requires special treatment, such as password fields, which must be masked for privacy reasons.

In this recipe, we will implement different types of SwiftUI Text views. A Text view is used to display one or more lines of read-only text on the screen. A TextField view is used to display multiline editable text, and a SecureField view is used to request private information that should be masked, such as passwords.

Getting ready

Create a new SwiftUI project named FormattedText.

How to do it…

We’ll implement multiple types of text-related views and modifiers. Each step in this section applies minor changes to the view, so note the UI changes that occur after each step. Let’s get started:

  1. Replace the initial ContentView body variable with our own VStack. The ContentView should look like the following code:
    struct ContentView: View {
    var body: some View {
            VStack{
                Text("Hello World")
            }
        }
    }
    
  2. Add the .fontWeight(.medium) modifier to the text and observe the text weight change in the canvas preview:
    Text("Hello World")
               .fontWeight(.medium)
    
  3. Add two state variables to the ContentView.swift file: password and someText. Place the values below the ContentView struct declaration. These variables will hold the content of the user’s password and Textfield inputs:
    struct ContentView: View {
        @State private var password = "1234"
        @State private var someText = "initial text"
    var body: some View {
    ...
    }
    
  4. Now, we will start adding more views to the VStack. Each view should be added immediately after the previous one. Add SecureField and a Text view to the VStack. The Text view displays the value entered in SecureField:
    SecureField("Enter a password", text: $password)
          .padding()
    Text("password entered: \(password)")
          .italic()
    
  5. Add TextField and a Text view to display the value entered in TextField:
    TextField("Enter some text", text: $someText)
       .padding()
    Text(someText)
       .font(.largeTitle)
       .underline()
    
  6. Now, let’s add some other Text views with modifiers to the list:
    Text("Changing text color and make it bold")
             .foregroundStyle(.blue)
             .bold()
    Text("Use kerning to change space between characters in the text")
              .kerning(7)
          Text("Changing baseline offset")
              .baselineOffset(100)
          Text("Strikethrough")
               .strikethrough()
          Text("This is a multiline text implemented in
               SwiftUI. The trailing modifier was added 
               to the text. This text also implements
               multiple modifiers")
                .background(.yellow)
                .multilineTextAlignment(.trailing)
                .lineSpacing(10)
    

Now is the moment to test the app. We can choose to run the app in a simulator or click the Play button in the canvas preview, which allows for interactivity. Play with the app and enter some text in the SecureField and TextField. Text entered in the SecureField will be masked, while text in the TextField will be shown.

The resulting preview should look like this:

Figure 1.4: FormattedText preview

How it works…

Text views have several modifiers for font, spacing, and other formatting requirements. When in doubt, position the cursor on the line of code that includes the Text view, and press the Esc key to reveal a list of all available modifiers. This is shown in the following example:

Figure 1.5: Using Xcode autocomplete to view formatting options

Unlike regular Text views, TextField and SecureField require state variables to store the value entered by the user. State variables are declared using the @State keyword. SwiftUI manages the storage of properties declared by using @State and refreshes the body each time the value of the state variable changes.

Values entered by the user are stored using the process of binding. In this recipe, we have state variables bound to the SecureField and TextField input parameters. The $ symbol is used to bind a state variable to the field. Using the $ symbol ensures that the state variable’s value is changed to correspond to the value entered by the user, as shown in the following example:

  TextField("Enter some text", text: $someText) 

Binding also notifies other views of state changes and causes the views to be redrawn on state change.

The wrapped value of bound state variables, which is the underlying value referenced by the state variable, is accessed without having to use the $ symbol. This is a convenience shortcut provided by Swift, as shown in the following code snippet:

  Text(someText)

See also

Apple documentation regarding SwiftUI Text view: https://developer.apple.com/documentation/swiftui/text.

 

Using images

Apps need to be appealing to users and need to engage them to interact with the app. To that purpose, a well-crafted and beautiful user interface with simple and intuitive interactions is very desirable.

Images play an important part in an app’s user interface as they add color and simplicity and they convey messages in a graphical way. It is fundamental to master how to use images in your apps.

In this recipe, we will learn how to add an image to a view, use an already existing UIImage, put an image in a frame, and use modifiers to present beautiful images. The images in this section were obtained from https://unsplash.com/, so special thanks to jf-brou, Kieran White, and Camilo Fierro.

Getting ready

Let’s start by creating a new SwiftUI project called UsingImages.

How to do it…

Let’s add some images to our SwiftUI project and introduce the modifiers used to style them. The steps are given here:

  1. Replace the content of the body variable with an empty VStack.
        var body: some View {
            VStack {
            }
        }
    
  2. Download the project images from the GitHub link at https://github.com/PacktPublishing/SwiftUI-Cookbook-3rd-Edition/tree/main/Resources/Chapter01/recipe3/.
  3. Drag and drop the downloaded images for this recipe into the project’s Assets.xcassets (or Assets) folder, as shown in the following screenshot:

Figure 1.6: Assets.xcassets folder in Xcode

  1. Add an Image view to VStack:
    Image("dogs1")
    
  2. Observe the result in the canvas preview.
  3. In iOS 17, the ImageResource and ColorResource structs were introduced, backward compatible to iOS 11 for UIKit and iOS 13 for SwiftUI. Xcode 15 automatically generates instances of ImageResource and ColorResource for images and colors in asset catalogs. For example, the three images in our asset catalog, shown in Figure 1.6, generate the following code:
    // MARK: - Image Symbols -
    @available(iOS 11.0, macOS 10.7, tvOS 11.0, *)
    extension ImageResource {
        /// The "dog-and-nature" asset catalog image resource.
        static let dogAndNature = ImageResource(name: "dog-and-nature", bundle: resourceBundle)
        /// The "dog2" asset catalog image resource.
        static let dog2 = ImageResource(name: "dog2", bundle: resourceBundle)
        /// The "dogs1" asset catalog image resource.
        static let dogs1 = ImageResource(name: "dogs1", bundle: resourceBundle)
    }
    #if canImport(SwiftUI)
    @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
    extension SwiftUI.Image {
        /// Initialize an 'Image' with an image resource.
        init(_ resource: ImageResource) {
            self.init(resource.name, bundle: resource.bundle)
        }
    }
    #endif
    
  4. This is auto-generated code, compiled along with our own code. Thanks to the new initializer for Image, which takes an ImageResource instead of Image("dogs1"), we can write Image(.dogs1). The advantage of this approach is the compile-time checking of the correct image name, which eliminates runtime errors from typos.
  5. Add a .resizable() modifier to the image and allow SwiftUI to adjust the image such that it fits the screen space available:
    Image(.dogs1)
        .resizable()
    
  6. The .resizable() modifier causes the full image to fit on the screen, but the proportions are distorted. That can be fixed by adding the .aspectRatio(contentMode: .fit) modifier:
    Image(.dogs1)
        .resizable()
        .aspectRatio(contentMode: .fit)
    
  7. Add the dog-and-nature image to VStack:
    Image(.dogAndNature)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width:300, height:200)
        .clipShape(Circle())
        .overlay(Circle().stroke(.blue, lineWidth: 6))
        .shadow(radius: 10)
    
  8. We can also use a UIImage instance to initialize an Image view. This is useful if the UIImage was generated with legacy code or programmatically. In our example, we use the UIImage convenience initializer, which takes an ImageResource instance. For example, to create a UIImage from the dogs2 image in our asset catalog, we would use: UIImage(resource: .dogs2).
  9. Use the UIImage and display it within the VStack. The resulting code should look like this:
    struct ContentView: View {
        var body: some View {
            VStack{
                Image("dogs1")
                .resizable()
                .aspectRatio(contentMode: .fit)
                Image("dog-and-nature")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width:300, height:200)
                    .clipShape(Circle())
                    .overlay(Circle().stroke(Color.blue,
                     lineWidth: 6))
                    .shadow(radius: 10)
                Image(uiImage: UIImage(resource: .dog2))
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 200, height: 200)
            }
        }
    }
    
  10. The completed application should then look like this:

Figure 1.7: UsingImages preview

How it works…

Adding the Image view to SwiftUI displays the image in its original proportions. The image might be too small or too big for the device’s display. For example, without any modifiers, the dog-and-nature image fills up the full iPhone 14 Pro Max screen:

Figure 1.8: The dog-and-nature image without the resizable modifier

To allow an image to shrink or enlarge to fit the device screen size, add the .resizable() modifier to the image. Adding the .resizable() modifier causes the image to fit within its view, but it may be distorted due to changes in proportion:

Figure 1.9: Image with resizable modifier

To address the issue, add the .aspectRatio(contentMode: .fit) modifier to the image:

Figure 1.10: Image with AspectRatio set

To specify the width and height of an image, add the .frame(width, height) modifier to the view and set the width and height: .frame(width: 200, height: 200).

Images can be clipped to specific shapes. The .clipShape(Circle()) modifier changes the image shape to a circle:

Figure 1.11: Image with the clipShape(Circle()) modifier

The .overlay(Circle().stroke(Color.blue, lineWidth: 6)) and .shadow(radius: 10) modifiers were used to draw a blue line around the image circle and add a shadow to the circle:

Figure 1.12: Stroke and shadow applied to image

Important Note

The order in which the modifiers are added matters. Adding the .frame() modifier before the .resizable() or .aspectRatio() modifiers may lead to different results.

Note that if you set the project deployment target to iOS 14, the ImageResource struct works without issues since Apple made the struct available for older versions of iOS. This allows us to use the new APIs in older versions of iOS in case your app needs to support them.

See also

Apple documentation regarding SwiftUI Image: https://developer.apple.com/documentation/swiftui/image.

 

Adding buttons and navigating with them

In this recipe, we will learn how to use the various buttons available in SwiftUI. We will use a Button view to trigger the change of a count when clicked and implement a NavigationStack to move between various SwiftUI views and an EditButton to remove items from a list. We will also briefly discuss the MenuButton and PasteButton only available in macOS.

Getting ready

Let’s start by creating a new SwiftUI project called Buttons.

How to do it…

Let’s create a home screen with buttons for each of the items we want to go over. Once clicked, we’ll use SwiftUI’s NavigationLink to show the view that implements the concept. The steps are given here:

  1. Add a new SwiftUI view file called ButtonView to the project: File | New | File (or press the shortcut keys Shape  Description automatically generated with low confidence+ N).
  2. Select SwiftUI View from the UI templates.
  3. In the Save As field of the pop-up menu, enter the filename ButtonView.
  4. Repeat Step 1 and enter the filename EditButtonView.
  5. Repeat Step 1 and enter the filename PasteButtonView.
  6. Repeat Step 1 and enter the filename MenuButtonView.

Important Note

Avoid using the MenuButton view because this is deprecated and only available in macOS 10.14–12.0. For similar functionality, use the Menu view instead, which is available for macOS, iOS and iPadOS.

  1. Open the ContentView.swift file and create a NavigationStack to navigate between the SwiftUI views we added to the project. The ContentView struct should look like this:
    struct ContentView: View {
        var body: some View {
            NavigationStack {
                VStack(spacing: 44) {
                    NavigationLink("Buttons"){
                        ButtonView()
                    }
                    NavigationLink("EditButtons") {
                        EditButtonView()
                    }
                    NavigationLink("MenuButtons") {
                        MenuButtonView()
                    }
                    NavigationLink("PasteButtons") {
                        PasteButtonView()
                    }
                    NavigationLink("Details about text") {
                        Text("Very long text that should not be displayed in a single line because it is not good design")
                        .padding()
                        .navigationTitle(Text("Detail"))
                    }
                }
                .navigationTitle(Text("Main View"))
            }
        }
    } 
    
  2. Upon completion, the ContentView preview should look like this:

Figure 1.13: ButtonsApp ContentView

  1. Open the ButtonView.swift file in the project navigator and replace the existing struct with the following code:
    struct ButtonView: View {
        @State var count = 0
        var body: some View {
            VStack {
                Text("Welcome to your second view")
                Text("Current count value: \(count)")
                    .padding()
                Button {
                    count += 1
                } label: {
                    Text("Tap to Increment count")
                    .fontWeight(.bold)
                    .foregroundStyle(.white)
                    .padding()
                    .background(.blue)
                    .clipShape(Capsule())
                }
            }.navigationBarTitle("Button View")
        }
    }
    #Preview {
            NavigationStack {
                ButtonView()
            }
    }
    
  2. Open the EditButtonView.swift file in the project navigator and replace the existing struct with the following code that implements an EditButton:
    struct EditButtonView: View {
        @State private var animals = ["Cats", "Dogs", "Goats"]
        var body: some View {
            List{
                ForEach(animals, id: \.self){ animal in
                    Text(animal)
                }
         .onDelete(perform: removeAnimal)
            }
            .toolbar {
               EditButton()
            }
            .navigationTitle("EditButtonView")
        }
        func removeAnimal(at offsets: IndexSet){
            animals.remove(atOffsets: offsets)
        }
    }
    #Preview {
            NavigationStack {
                EditButtonView()
            }
    }
    
  3. Open the MenuButtonView.swift file and replace the existing struct with the following code for MenuButtonView:
    struct MenuButtonView: View {
        var body: some View {
            Menu("Choose a country") {
                Button("Canada") { print("Selected Canada") }
                Button("Mexico") { print("Selected Mexico") }
                Button("USA") { print("Selected USA") }
            }
            .navigationTitle("MenuButtons")
        }
    }
    #Preview {
            NavigationStack {
                MenuButtonView()
            }
    }
    
  4. Open the PasteButtonView.swift file and implement the text regarding PasteButtons:
    struct PasteButtonView: View {
        @State var text  = String()
        var body: some View {
            VStack{
                Text("PasteButton controls how you paste in macOS but is not available in iOS. For more information, check the \"See also\" section of this recipe")
                    .padding()
            }
    .navigationTitle("PasteButton")
        }
    }
    #Preview {
            NavigationStack {
                PasteButtonView()
            }
    }
    

Go back to ContentView, run the code in the canvas preview or simulator, and play around with it to see what the results look like.

How it works…

A NavigationLink must be placed in a NavigationStack or NavigationSplitView prior to being used.

In this recipe, we use a NavigationLink with two parameters—destination and label. The destination parameter represents the view that would be displayed when the label is clicked, while the label parameter represents the text to be displayed within NavigationLink. Since our label is a simple Text view, we use the convenience initializer init(_:destination:) of NavigatioLink to keep our code more concise.

NavigationLink buttons can be used to move from one SwiftUI view to another—for example, moving from ContentView to EditButtonView. They can also be used to display text details without creating a SwiftUI view in a separate file, such as in the last NavigationLink, where a click just presents a long piece of text with more information. This is made possible because the Text struct conforms to the view protocol.

The .navigationTitle("Main View")) modifier adds a title to the ContentView screen.

The .navigationTitle() modifier is also added to EditButtonView and other views. Since these views do not contain NavigationStack structs, the titles would not be displayed when viewing the page directly from the preview, but would show up when running the code and navigating from ContentView.swift to the view provided in NavigationLink. To solve this, we use a NavigationStack in the PreviewProvider structs. To make the previews more useful, note how we have enclosed the view in a NavigationStack so we can see the title in the canvas preview window.

The EditButton view is used in conjunction with List views to make lists editable. We will go over List and Scroll views in Chapter 2, Displaying Scrollable Content with Lists and Scroll Views, but EditButtonView provides a peek into how to create an editable list.

The MenuButtonView uses the Menu struct, introduced in iOS 14, to display a floating menu of actions. Check out the Exploring more views and controls recipe at the end of this chapter for more information on Menu.

PasteButtons are only available on macOS. Refer to the See also section of this recipe for code on how the PasteButton is implemented.

See also

 

Beyond buttons: using advanced pickers

In this recipe, we will learn how to implement pickers—namely, Picker, Toggle, Slider, Stepper, and DatePicker. Pickers are typically used to prompt the user to select from a set of mutually exclusive values. Toggle views are used to switch between on/off states. Slider views are used for selecting a value from a bounded linear range of values. As with Slider views, Stepper views also provide a UI for selecting from a range of values. However, steppers use the + and : signs to allow users to increment the desired value by a certain amount. Finally, DatePicker views are used for selecting dates.

Getting ready

Create a new SwiftUI project named UsingPickers.

How to do it…

Let’s create a SwiftUI project that implements various pickers. Each picker will have a @State variable to hold the current value of the picker. The steps are given here:

  1. In the ContentView.swift file, create @State variables that will hold the values selected by the pickers and other controls. Place the variables between the ContentView struct and the body:
        @State private var choice = 0
        @State private var showText = false
        @State private var transitModes = ["Bike", "Car", "Bus"]
        @State private var sliderVal: Float = 0
        @State private var stepVal = 0
        @State private var gameTime = Date()
    
  2. Replace the body of the ContentView struct with a Form view. Then, add a Section view and a Picker view to the form:
    Form {
        Section {
           Picker("Transit Modes", selection: $choice) {
              ForEach( 0 ..< transitModes.count, id: \.self) { index in
                  Text("\(transitModes[index])")
                   }
            }
            .pickerStyle(.segmented)
            Text("Current choice: \(transitModes[choice])")
       }
    }
    
  3. Under the existing Section view, add another Section view and a Toggle view:
    Section{
       Toggle(isOn: $showText){
          Text("Show Text")
       }
       if showText {
          Text("The Text toggle is on")
       }
    }
    
  4. Add a Section view and a Slider view:
    Section{
       Slider(value: $sliderVal, in: 0...10, step: 0.001)
       Text("Slider current value \(sliderVal, specifier: "%.1f")")
    }
    
  5. Add a Section view and a Stepper view:
    Section {
       Stepper("Stepper", value: $stepVal, in: 0...5)
       Text("Stepper current value \(stepVal)")
    }
    
  6. Add a Section view and a DatePicker view:
    Section {
       DatePicker("Please select a date", selection: $gameTime)
    }
    
  7. Add a Section view and a slightly modified DatePicker view that only accepts future dates:
    Section {
       DatePicker("Please select a date", selection: $gameTime, in: Date()...)
    }
    
  8. The result should be a beautiful form, like what is shown here:

Figure 1.14: SwiftUI Form with Pickers

How it works…

Form views group controls that are used for data entry, and Section views create hierarchical view content. Section views can be embedded inside a Form view to display information grouped together. The default presentation style for a Form with embedded Section views is to include a gray padding area between each section for visual grouping, as shown in Figure 1.14.

Picker views are used for selecting from a set of mutually exclusive values. In the following example, a segmented picker is used to select a transit mode from our transitModes state variable:

Picker("Transit Modes", selection: $choice) {
   ForEach( 0 ..< transitModes.count, id:\.self) { index in
       Text("\(transitModes[index])")
   }
}
.pickerStyle(.segmented)

As shown in the preceding example, a Picker view takes two parameters, a string describing its function, and a state variable that holds the value selected. The state variable should be of the same type as the range of values to select from. In this case, the ForEach loop iterates through the transitModes array indices. The value selected would be an Int within the range of transitModes indices. The transit mode located in the selected index can then be displayed using Text("\(transitModes[index])"). It is also worth noting that we need to apply a .segmented style to the picker using the .pickerStyle() modifier, to use the visual segmented style everyone is used to in iOS.

Toggle views are controls that switch between “on” and “off” states. The state variable for holding the toggle selection should be of the Bool type. The section with the Toggle view also contains some text. The @State property of the Toggle reflects the current state of the toggle.

Creating a slider requires three arguments:

  • value: The @State variable to bind the user input to
  • in: The range of the slider
  • step: By how much the slider should change when the user moves it

In the sample code, our slider moves can hold values between 0 and 10, with a step of 0.001.

Steppers take three arguments too—a string for the label, value, and in. The value argument holds the @State variable that binds the user input, and the in argument holds the range of values for the stepper.

In this recipe, we also demonstrate two applications of a date picker. The first from the top shows a date picker whose first argument is the label of DatePicker, and the second argument holds the state variable that binds the user input. Use it in situations where the user is allowed to pick any date without restriction. The other date picker contains a third parameter, in. This parameter represents the date range the user can select.

Important Note

The @State variables need to be of the same type as the data to be stored. For example, the gameTime state variable is of the Date type.

Picker styles change based on its ancestor. The default appearance of a picker may be different when placed within a form or list instead of a VStack or some other container view. Styles can be overridden using the .pickerStyle() modifier.

 

Applying groups of styles using ViewModifier

SwiftUI comes with built-in modifiers such as background() and fontWeight(), among others. It also gives you the ability to create your own custom modifiers. You can use custom modifiers to combine multiple existing modifiers into one.

In this section, we will create a custom modifier that adds rounded corners and a background to a Text view.

Getting ready

Create a new SwiftUI project named UsingViewModifiers.

How to do it…

Let’s create a view modifier and use a single line of code to apply it to a Text view. The steps are given here:

  1. Replace the current body of the ContentView view with:
    Text("Perfect")
    
  2. At the end of the ContentView.swift file, create a struct that conforms to the ViewModifier protocol, accepts a parameter of type Color, and applies styles to the view’s body:
    struct BackgroundStyle: ViewModifier {
        var bgColor: Color
        func body(content: Content) -> some View{
            content
            .frame(width:UIScreen.main.bounds.width * 0.3)
            .foregroundStyle(.black)
            .padding()
            .background(bgColor)
            .cornerRadius(20)
        }
    }
    
  3. Add a custom style to the text using the modifier() modifier:
    Text("Perfect").modifier(BackgroundStyle(bgColor:
         .blue))
    
  4. To apply styles without using a modifier, create an extension to the View protocol. The extension should be created outside the struct or Xcode will issue an error:
    extension View {
        func backgroundStyle(color: Color) -> some View{
            self.modifier(BackgroundStyle(bgColor: color))
        }
    }
    
  5. Replace the modifier on the Text view with the backgroundStyle() modifier that you just created, which will add your custom styles:
        Text("Perfect")
            .backgroundStyle(color: Color.red)
    
  6. The result should look like this:

Figure 1.15: Custom view modifier

This concludes the section on view modifiers. View modifiers promote clean coding and reduce repetition.

How it works…

A view modifier creates a new view by altering the original view to which it is applied. We create a new view modifier by creating a struct that conforms to the ViewModifier protocol and apply our styles in the implementation of the required body function. You can make the ViewModifier customizable by requiring input parameters/properties that would be used when applying styles.

In the example here, the bgColor property is used in our BackGroundStyle struct, which alters the background color of the content passed to the body function.

At the end of Step 2, we have a functioning ViewModifier but decide to make it easier to use by creating a View extension and adding in a function that calls our struct:

extension View {
    func backgroundStyle(color: Color) -> some View {
        modifier(BackgroundStyle(bgColor: color))
    }
}

We are thus able to use .backgroundStyle(color: Color) directly on our views instead of .modifier(BackgroundStyle(bgColor:Color)).

See also

Apple documentation on view modifiers: https://developer.apple.com/documentation/swiftui/viewmodifier.

 

Separating presentation from content with ViewBuilder

Apple defines ViewBuilder as “a custom parameter attribute that constructs views from closures.” ViewBuilder can be used to create custom views that can be used across an application with minimal or no code duplication. In this recipe, we will create a custom SwiftUI view, BlueCircle, that applies a blue circle to the right of its content.

Getting ready

Let’s start by creating a new SwiftUI project called UsingViewBuilder.

How to do it…

We’ll create our ViewBuilder in a separate swift file and then apply it to items that we’ll create in the ContentView.swift file. The steps are given here:

  1. With our UsingViewBuilder app opened, let’s create a new SwiftUI file by going to File | New | File.
  2. Select SwiftUI view from the menu and click Next.
  3. Name the file BlueCircle and click Create.
  4. Delete the #Preview macro from the file.
  5. Modify the existing struct with the BlueCircle ViewModifier:
    struct BlueCircle<Content: View>: View {
        let content: Content
        init(@ViewBuilder content: () -> Content) {
            self.content = content()
        }
        var body: some View {
                HStack {
                   content
                    Spacer()
                    Circle()
                        .fill(Color.blue)
                        .frame(width:20, height:30)
                }
                .padding()
        }
    }
    
  6. Open the ContentView.swift file and try out the BlueCircle ViewBuilder:
        var body: some View {
            VStack {
                BlueCircle {
                    Text("some text here")
                    Rectangle()
                    .fill(Color.red)
                    .frame(width: 40, height: 40)
                }
                BlueCircle {
                    Text("Another example")
                }
            }
        }
    
  7. The resulting preview should look like this:

Figure 1.16: ViewBuilder result preview

How it works…

We use the ViewBuilder struct to create a view template that can be used anywhere in the project without duplicating code. The ViewBuilder struct must contain a body property since it extends the View protocol.

Within the body property/view, we update the content property with the components we want to use in our custom view. In this case, we use a BlueCircle. Notice the location of the content property. This determines the location where the view passed to our ViewBuilder will be placed.

See also

Apple documentation on ViewBuilder: https://developer.apple.com/documentation/swiftui/viewbuilder.

 

Simple graphics using SF Symbols

The SF Symbols 5 library provides a set of over 5,000 free consistent and highly configurable symbols. Each year, Apple adds more symbols and symbol variants to the collection.

You can download and browse through a list of SF symbols using the macOS app available for download here: https://developer.apple.com/sf-symbols/.

In this recipe, we will use SF symbols in labels and images. We’ll also apply various modifiers that will add a punch to your design.

Getting ready

Let’s start by creating a new SwiftUI project called UsingSF Symbols.

How to do it…

Let’s create an app where we use different combinations of SF Symbols and modifiers. The steps are given here:

  1. Open the ContentView.swift file and replace the entire body content with a VStack, HStack, and some SF Symbols:
    VStack {
       HStack{
           Image(systemName: "c")
           Image(systemName: "o")
           Image(systemName: "o")
           Image(systemName: "k")
       }
       .symbolVariant(.fill.circle)
       .foregroundStyle(.yellow, .blue)
       .font(.title)
    }
    
  2. We continue working on our VStack content and embed another HStack with SF Symbols for the word book:
    HStack{
        Image(systemName: "b.circle.fill")
        Image(systemName: "o.circle.fill")
            .foregroundStyle(.red)
        Image(systemName: "o.circle.fill")
            .imageScale(.large)
        Image(systemName: "k.circle.fill")
            .accessibility(identifier: "Letter K")
    }
    .foregroundStyle(.blue)
     .font(.title)
     .padding()
    
  3. Let’s add another HStack with more SF Symbols:
    HStack{
        Image(systemName: "allergens")
        Image(systemName: "ladybug")
    }
    .symbolVariant(.fill)
    .symbolRenderingMode(.multicolor)
    .font(.largeTitle)
    
  4. Finally, let’s add a Picker view with a segmented style that changes the appearance of the Wi-Fi SF Symbol based on the picker selection:
    HStack {
        Picker("Pick One", selection: $wifiSelection) {
            Text("No Wifi").tag(0)
            Text("Searching").tag(1)
            Text("Wifi On").tag(2)
        }
        .pickerStyle(.segmented)
        .frame(width: 240)
        .padding(.horizontal)
        Group {
            switch wifiSelection {
            case 0:
               Image(systemName: "wifi")
                   .symbolVariant(.slash)
            case 1:
               Image(systemName: "wifi")
                   .symbolEffect(.variableColor.iterative.reversing)
            default:
               Image(systemName: "wifi")
                   .foregroundStyle(.blue)
            }
        }
        .foregroundStyle(.secondary)
        .font(.title)
    }
    .padding()
    
  5. Let’s add the @State property to fix the Xcode error. Immediately below the declaration of the ContentView struct, add the wifiSelection property:
        @State private var wifiSelection = 0
    
  6. The resulting preview should look like this:

Figure 1.17: SF Symbols in action

How it works…

SF Symbols defines several design variants such as enclosed, fill, and slash. These different variants can be used to convey different information—for example, a slash variant on a Wi-Fi symbol lets the user know if the Wi-Fi is unavailable.

In our first HStack, we use the .symbolVariant(.fill.circle) modifier to apply the .fill and .circle variants to all the items in the HStack. This could also be accomplished using the following code:

HStack{         
     Image(systemName: "c.circle.fill")
     Image(systemName: "o.circle.fill ")
     Image(systemName: "o.circle.fill ")
     Image(systemName: "k.circle.fill ")     
}

However, the preceding code is too verbose and would require too many changes if we decided that we didn’t need either the .circle or .fill variant, or both.

We also notice something new in our first HStack —the .foregroundStyle(...) modifier. The .foregroundStyle modifier can accept one, two, or three parameters corresponding to the primary, secondary, and tertiary colors. Some symbols may have all three levels of colors, or only primary and secondary, or primary and tertiary. For symbols without all three levels, only the ones that pertain to them are applied to the symbol. For example, a tertiary color applied to an SF Symbol with only primary and secondary levels will have no effect on the symbol.

The second HStack also uses the .symbolVariant modifier with one variant. It also introduces a new modifier, .symbolRenderingMode(). Rendering modes can be used to control how color is applied to symbols. The multicolor rendering mode renders symbols as multiple layers with their inherited styles. Adding the .multicolor rendering mode is enough to present a symbol with its default layer colors. Other rendering modes include hierarchical, monochrome, and palette.

Finally, we create another HStack with a segmented picker for a Wi-Fi system image where we change the appearance based on the status of the wifiSelection state variable. The picker reads the state variable and changes the wifi symbol appearance from a slashed symbol when “No Wifi” is selected to a variable color animated symbol when “Searching” is selected to a solid blue symbol when “Wifi On” is selected. Here, we used the new Symbols framework introduced in iOS 17, and the .symbolEffect view modifier to add an animation to a symbol. When we want to add animations to a symbol, the SF Symbols Mac app allows us to configure all the animations and preview the result. We can even export the animation configuration to add it in Xcode.

See also

 

Integrating UIKit into SwiftUI: the best of both worlds

SwiftUI was announced at WWDC 2019 and is only available on devices running iOS 13 and above. Many improvements and new APIs have been added to SwiftUI since its introduction, to the point that, at the time of this writing, we can create an app in SwiftUI without using any UIKit components.

However, if you’re dealing with legacy code written in UIKit, and have the need to integrate the code in your SwiftUI app, Apple provides a way to do this. UIViews and UIViewControllers can be seamlessly placed inside SwiftUI views and vice versa.

In this recipe, we’ll look at how to integrate UIKit APIs in SwiftUI. We will create a project that wraps instances of UIActivityIndicatorView to display an indicator in SwiftUI.

Getting ready

Open Xcode and create a SwiftUI project named UIKitToSwiftUI.

How to do it…

We can display UIKit views in SwiftUI by using the UIViewRepresentable protocol. Follow these steps to implement the UIActivityIndicatorView in SwiftUI:

  1. Within the Xcode menu, click File | New | File and select Swift File. Name the view ActivityIndicator.
  2. Replace the import Foundation statement with import SwiftUI:
    import SwiftUI
    
  3. Modify the code in ActivityIndicator to use the UIViewRepresentable protocol:
    struct ActivityIndicator: UIViewRepresentable {
         var animating: Bool
        
        func makeUIView(context: Context) ->
         UIActivityIndicatorView {
            return UIActivityIndicatorView()
        }
        
        func updateUIView(_ activityIndicator:
         UIActivityIndicatorView, context: Context) {
            if animating {
                activityIndicator.startAnimating()
            } else {
                activityIndicator.stopAnimating()
            }
        }
    }
    
  4. Let’s open the ContentView.swift file and replace the struct with the following code to make use of the ActivityIndicator instance that we just created. Let’s also add a Toggle control to turn the indicator on or off:
    struct ContentView: View {
        @State private var animate = true
        var body: some View {
            VStack{
                ActivityIndicator(animating:  animate)
                HStack{
                    Toggle(isOn: $animate){
                        Text("Toggle Activity")
                    }
                }.padding()
            }
        }
    }
    
  5. The resulting ContentView preview should look like this:

Figure 1.18: UIKit UIActivityIndicatorView inside our SwiftUI view

How it works…

UIKit views can be implemented in SwiftUI by using the UIViewRepresentable protocol to wrap the UIKit views. In this recipe, we make use of a UIActivityIndicatorView by first wrapping it with a UIViewRepresentable.

In our ActivityIndicator.swift file, we implement a struct that conforms to the UIViewRepresentable protocol. This requires us to implement both the makeUIView and updateUIView functions. The makeUIView function creates and prepares the view, while the updateUIView function updates the UIView when the animation changes.

Important Note

You can implement the preceding features in iOS 14+ apps by using SwiftUI’s ProgressView. The purpose of the recipe was to show how to integrate a UIKit view with SwiftUI.

See also

Check out the Exploring more views and controls recipe at the end of this chapter for more information on ProgressView.

Apple’s tutorial on how to integrate UIKit and SwiftUI:

https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

 

Adding SwiftUI to a legacy UIKit app

In this recipe, we will learn how to navigate from a UIKit view to a SwiftUI view while passing a secret text to our SwiftUI view. This recipe assumes prior knowledge of UIKit and it is most useful to developers who want to integrate SwiftUI into a legacy UIKit app. If this is not your case, feel free to skip to the next recipe.

We’ll be making use of a UIKit storyboard, a visual representation of the UI in UIKit. The Main.storyboard file is to UIKit what the ContentView.swift file is to SwiftUI. They are both the default home views that are created when you start a new project.

We start off this project with a simple UIKit project that contains a button.

Getting ready

Get the following ready before starting out with this recipe:

  1. Clone or download the code for this book from GitHub: https://github.com/PacktPublishing/SwiftUI-Cookbook-3rd-Edition/tree/main/Chapter01-Using-the-basic-SwiftUI-Views-and-Controls/10-Adding-SwiftUI-to-UIKit.
  2. Open the StartingPoint folder and double-click on AddSwiftUIToUIKit.xcodeproj to open the project in Xcode.

How to do it…

We will add a NavigationController to the UIKit ViewController that allows the app to switch from the UIKit to the SwiftUI view when the button is clicked:

  1. Open the Main.storyboard file in Xcode by clicking on it. The Main.storyboard looks like this:

Figure 1.19: UIKit View Controller

  1. Click anywhere in the ViewController to select it.
  2. In the Xcode menu, click Editor | Embed in | Navigation Controller.
  3. Add a new ViewController to the project:
    1. Click the + button at the top right of the Xcode window.
    2. In the new window, select the Objects library, type hosting in the search bar, select Hosting View Controller, and drag it out to the storyboard:

Figure 1.20: Creating a UIKit Hosting View Controller

  1. Hold down the Ctrl key, and then click and drag from the ViewController button to the new Hosting View Controller that we added.
  2. In the pop-up menu, for the Action Segue option, select Show.
  3. Click the Adjust Editor Options button:
Figure 1.21 – Adjust Editor Options button

Figure 1.21: Adjust Editor Options button

  1. Click Assistant. This splits the view into two panes, as shown here:

Figure 1.22: Xcode with the Assistant editor open

  1. To create a segue action, hold the Ctrl key, then click and drag from the segue button (item in the middle of the blue arrow in Figure 1.22) to the space after the viewDidLoad function in the ViewController.swift file.
  2. In the pop-up menu, enter the name goToSwiftUI and click Connect. The following code will be added to the ViewController.swift file:
        @IBSegueAction func goToSwiftUI(_ coder: NSCoder) -> UIViewController? {
            return <#UIHostingController(coder: coder, rootView: ...)#>
        }
    
  3. Add a statement to import SwiftUI at the top of the ViewController page, below import UIKit:
    import SwiftUI
    
  4. Within the goToSwiftUI function, create a text that will be passed to our SwiftUI view. Also, create a rootView variable that specifies the SwiftUI view that you would like to reach. Finally, return the UIHostingController, which is a special ViewController used to display the SwiftUI view. The resulting code should look like this:
        @IBSegueAction func goToSwiftUI(_ coder: NSCoder) -> UIViewController? {
            let greetings = "Hello From UIKit"
            let rootView = Greetings(textFromUIKit: greetings)
            return UIHostingController(coder: coder, rootView: rootView)
        }
    
  5. At this point, the code will not compile because we have not yet implemented a Greetings view. Let’s resolve that now.
  6. Create a SwiftUI view to display a message:
    1. Click File | New | File and select SwiftUI View.
    2. Name the view Greetings.swift.
  7. Add a View component that displays some text passed to it:
    struct Greetings: View {
        var textFromUIKit: String
        var body: some View {
            Text(textFromUIKit)
        }
    }
    #Preview {
        Greetings(textFromUIKit: "Hello, World!")
    }
    

Run the project in the simulator, click on the UIKit button, and watch the SwiftUI page get displayed.

How it works…

To host SwiftUI views in an existing app, you need to wrap the SwiftUI hierarchy in a ViewController or InterfaceController.

We start by performing core UIKit concepts, such as adding a Navigation View Controller to the storyboard and adding a Hosting View Controller as a placeholder for our SwiftUI view.

Lastly, we create an IBSegueAction to present our SwiftUI view upon clicking the UIKit button.

 

Exploring more views and controls

In this section, we introduce some views and controls that did not clearly fit in any of the earlier created recipes. We’ll look at the ProgressView, ColorPicker, Link, and Menu views.

ProgressView is used to show the degree of completion of a task. There are two types of ProgressView: indeterminate progress views show a spinning circle till a task is completed, while determinate progress views show a bar that gets filled up to show the degree of completion of a task.

ColorPicker views allow users to select from a wide range of colors, while Menu views present a list of items that users can choose from to perform a specific action.

Getting ready

Let’s start by creating a new SwiftUI project called MoreViewsAndControls.

How to do it…

Let’s implement some views and controls in the ContentView.swift file. We will group the controls in Section instances in a List view. Section allows us to include an optional header. The steps are given here:

  1. Just below the ContentView struct declaration, add the state variables that we’ll be using for various components:
        @State private var progress = 0.5
        @State private var color  = Color.red
        @State private var secondColor  = Color.yellow
        @State private var someText = "Initial value" 
    
  2. Replace the body contents with a List view with a Section view, two ProgressView views, and a Button view:
            List {
                Section(header: Text("ProgressViews")) {
                    ProgressView("Indeterminate progress view")
                    ProgressView("Downloading",value: progress, total:2)
                    Button("More") {
                        if (progress < 2) {
                            progress += 0.5
                        }
                    }
                } 
    }
    
  3. Let’s add another section that implements two labels:
                Section(header: Text("Labels")) {
                    Label("Slow ", systemImage: "tortoise.fill")
                    Label {
                        Text ("Fast")
                            .font(.title)
                    } icon: {
                        Circle()
                            .fill(Color.orange)
                            .frame(width: 40, height: 20, alignment: .center)
                            .overlay(Text("F"))
                    }
                }
    
  4. Now, add a new section that implements a ColorPicker:
                Section(header: Text("ColorPicker")) {
                    ColorPicker(selection: $color ) {
                        Text("Pick my background")
                            .background(color)
                            .padding()
                    }
                    ColorPicker("Picker", selection: $secondColor )
                }
    
  5. Next, add a Link:
                Section(header: Text("Link")) {
                    Link("Packt Publishing", destination: URL(string: "https://www.packtpub.com/")!)
                }
    
  6. Next, add a TextEditor:
                Section(header: Text("TextEditor")) {
                    TextEditor(text: $someText)
                    Text("current editor text:\n\(someText)")
                }
    
  7. Then, add a Menu:
                
                Section(header: Text("Menu")) {
                    Menu("Actions") {
                        Button("Set TextEditor text to 'magic'"){
                            someText = "magic"
                        }
                        Button("Turn first picker green") {
                            color = Color.green
                        }
                        Menu("Actions") {
                            Button("Set TextEditor text to 'real magic'"){
                                someText = "real magic"
                            }
                            Button("Turn first picker gray") {
                                color = Color.gray
                            }
                            
                        }
                    }
                }
    
  8. Finally, let’s improve the style of all the content by applying a listStyle modifier on the List:
    List {
    ...
    }
    .listStyle(.grouped)
    
  9. The resulting view app preview should look like this:

Figure 1.23: More Views and Controls app preview

How it works…

We’ve implemented multiple views in this recipe. Let’s look at each one and discuss how they work.

Indeterminate ProgressView requires no parameters:

ProgressView("Indeterminate progress view")
ProgressView()

Determinate ProgressView components, on the other hand, require a value parameter that takes a state variable and displays the level of completion:

ProgressView("Downloading",value: progress, total:2)

The total parameter in the ProgressView component is optional and defaults to 1.0 if not specified.

Label views were mentioned earlier in the Simple graphics using SF Symbols recipe. Here, we introduce a second option for implementing labels where we customize the design of the label text and icon:

Label {
                    Text ("Fast")
                        .font(.title)
                } icon: {
                    Circle()
                        .fill(Color.orange)
                        .frame(width: 40, height: 20, alignment: .center)
                        .overlay(Text("F"))
                }

Let’s move on to the ColorPicker view. Color pickers let you display a palette for users to pick colors from. We create a two-way binding using the color state variable so that we can store the color selected by the user:

                ColorPicker(selection: $color ) {
                    Text("Pick my background")
                        .background(color)
                        .padding()
                }

Link views are used to display clickable links:

                Link("Packt Publishing", destination: URL(string: "https://www.packtpub.com/")!) 

Finally, the Menu view provides a convenient way of presenting a user with a list of actions to choose from and can also be nested, as seen here:

                Menu("Actions") {
                    Button("Set TextEditor text to 'magic'"){
                        someText = "magic"
                    }
                    Button("Turn first picker green") {
                        color = Color.green
                    }
                    Menu("Actions") {
                        Button("Set TextEditor text to 'real magic'"){
                            someText = "real magic"
                        }
                        Button("Turn first picker gray") {
                            color = Color.gray
                        } 
                    }
                } 

You can add one or more buttons to a menu, each performing a specific action. Although menus can be nested, this should be done sparingly as too much nesting may decrease usability.

After learning the basics of SwiftUI, we concluded the chapter with this recipe, where we used several SwiftUI view components that we could incorporate into our apps.

Learn more on Discord

To join the Discord community for this book – where you can share feedback, ask questions to the author, and learn about new releases – follow the QR code below:

https://packt.link/swiftUI

About the Author
  • Juan C. Catalan

    Juan C. Catalan is a software engineer with more than 18 years of professional experience. He started mobile development back in the days of iOS 3. Juan has worked as a professional iOS developer in many industries, including medical devices, financial services, real estate, document management, fleet tracking and industrial automation. He has contributed to more than 30 published apps in the App Store, some of them with millions of users. Juan gives back to the iOS development community with technical talks, mentoring developers, reviewing technical books and now as a book author. He lives in Austin, Texas, with his wife Donna, where they spend time with their kids.

    Browse publications by this author
Latest Reviews (1 reviews total)
Livro muito bom e completo com muitos elementos para aprender sobre SwiftUI e todos os seus componentes.
SwiftUI Cookbook - Third Edition
Unlock this book and the full library FREE for 7 days
Start now