SwiftUI Cookbook

2 (2 reviews total)
By Giordano Scalzo , Edgar Nzokwe
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Chapter 2: Going Beyond the Single Component with Lists and Scroll Views

About this book

SwiftUI is an innovative and simple way to build beautiful user interfaces (UIs) for all Apple platforms, right from iOS and macOS through to watchOS and tvOS, using the Swift programming language. In this recipe-based book, you’ll work with SwiftUI and explore a range of essential techniques and concepts that will help you through the development process. The recipes cover the foundations of SwiftUI as well as the new SwiftUI 2.0 features introduced in iOS 14. Other recipes will help you to make some of the new SwiftUI 2.0 components backward-compatible with iOS 13, such as the Map View or the Sign in with Apple View.

The cookbook begins by explaining how to use basic SwiftUI components. Then, you’ll learn the core concepts of UI development such as Views, Controls, Lists, and ScrollViews using practical implementation in Swift. By learning drawings, built-in shapes, and adding animations and transitions, you’ll discover how to add useful features to the SwiftUI. When you’re ready, you’ll understand how to integrate SwiftUI with exciting new components in the Apple development ecosystem, such as Combine for managing events and Core Data for managing app data. Finally, you’ll write iOS, macOS, and watchOS apps while sharing the same SwiftUI codebase.

By the end of this SwiftUI book, you'll have discovered a range of simple, direct solutions to common problems found in building SwiftUI apps.

Publication date:
October 2020
Publisher
Packt
Pages
614
ISBN
9781838981860

 

Chapter 1: Using the Basic SwiftUI Views and Controls

In this chapter, we'll learn about SwiftUI's innovative way of building user interfaces across all platforms. SwiftUI does not use UIKit concepts such as Auto Layout. It has a completely new layout system designed to make it easy to write applications that work across Apple platforms.

We'll also learn about views and controls, SwiftUI's visual building blocks for app interfaces. We will look at how to use text, images, buttons, navigation, and much more.

By the end of the chapter, you'll be able to combine various views and components to create crisp and beautiful user interfaces.

In this chapter, we will cover the following recipes:

  • How to lay out components
  • Dealing with text
  • Using images
  • Adding buttons and navigating with them
  • Beyond buttons – how to use advanced pickers
  • How to apply groups of styles using ViewModifiers
  • Separating presentation from content with ViewBuilder
  • Simple graphics using SF Symbols
  • The best of both worlds – integrating UIKit into SwiftUI
  • Adding SwiftUI to an existing app
  • More views and controls (iOS 14+)
 

Technical requirements

The code in this chapter is based on Xcode 12 with iOS 13 as a minimum iOS target.

You can find the code in the book's GitHub repository at https://github.com/PacktPublishing/SwiftUI-Cookbook/tree/master/Chapter01%20-%20Using%20the%20basic%20SwiftUI%20Views%20and%20Controls

 

How to lay out components

SwiftUI uses three basic layout components – VStack, HStack, and ZStack. VStack is a view that arranges its children in a vertical line, HStack arranges its children in a horizontal line, and ZStack arranges its children by aligning them with the vertical and horizontal axes.

We will also take a look at how spacers and dividers can be used for layout.

Getting ready

Let's start by creating a new SwiftUI project with Xcode, calling it TheStacks. Use the following steps:

  1. Start the Xcode program.
  2. Click on Create a new Xcode project from the left pane.
  3. Leave the default selected application, Single View App. Click Next.
  4. In the project options menu, make sure SwiftUI is selected instead of storyboard.
  5. Enter the product name, TheStacks.
  6. Select the folder location to store the project and click Create.

How to do it…

  1. Replace Text in ContentView with a vertical stack containing three elements:
    struct ContentView: View {
        var body: some View {
    VStack{
        Text("VStack Item 1")
        Text("VStack Item 2")
        Text("VStack Item 3")
       }.background(Color.blue)
     }
    }

    This renders like the following in the canvas preview:

    Figure 1.1 – VStack with three items

    Figure 1.1 – VStack with three items

  2. Add a Spacer and a Divider between items 2 and 3:
    Spacer()
    Divider()

    This renders the following preview:

    Figure 1.2 – VStack, HStack, and ZStack

    Figure 1.2 – VStack, HStack, and ZStack

  3. Add the following HStack and ZStack to the bottom of the VStack:
                   HStack{
                        Text("Item 1")
                        Text("HStack Item 2")
                        Divider().background(Color.black)
                        Spacer()
                        Text("HStack Item 3")
                    }.background(Color.red)
                    ZStack{
                                Text("ZStack Item 1")
                                .padding()
                                .background(Color.green)
                                .opacity(0.8)
                                 Spacer()
                        Text("ZStack Item 2")
                        .padding()
                            .background(Color.green)
                            .offset(x: 80, y: -400)
                    }

    This renders the following preview:

Figure 1.3 – VStack, HStack, and ZStack

Figure 1.3 – VStack, HStack, and ZStack

That concludes our recipe on using stacks. Going forward, we'll make extensive use of VStacks and HStacks to position elements in our views.

How it works…

The SwiftUI stack layout process consists of three steps:

  • The parent proposes the size for the child.
  • The child chooses its size.
  • The parent places the child in the parent's coordinate space.

Following the preceding steps, the initial text view on a new project gets placed at the center of the screen. Adding a divider causes the stack view to extend and use the full width of the screen.

Stacks also define a layout process for items added to the stack:

  • The stack figures out its internal spacing and subtracts it from the size proposed by its parent view.
  • The stack then divides the remaining space into equal parts, processes the size of its least flexible view, and divides the remaining unclaimed space by the unallocated space, and then repeats step 2.
  • The stack then aligns its content and chooses its own size to exactly enclose its children.

A VStack adds items in a vertical way, with new views added from top to bottom. Adding the Spacer() forces the view to use the maximum amount of vertical space. Adding a Divider() draws a line that uses the maximum horizontal space allowed. By default, the divider line does not have a color. To set the divider color, we add the .background(Color.black) modifier. Items such as background(Color.black), .padding(), and .offests(…) are called ViewModifiers, or just modifiers. Modifiers can be applied to views or other modifiers, producing a different version of the original value.

The HStack also works in a similar manner. However, since items are added to an HStack in a horizontal manner, the Spacer() causes the HStack to fill the available horizontal space, while the Divider() draws a vertical line to separate items in the HStack. The Divider() thus causes the HStack to fill the maximum vertical space available.

The ZStack overlays its content on top of existing items. It uses the .offsets() modifier to position items along the X and Y axes.

 

Dealing with text

The SwiftUI text struct is a view that displays one or more lines of read-only text. Text structs come with a number of standard modifiers to format text. In this section, we will create a project that applies modifiers to 10 different texts.

We will also implement TextField, which is used to display an editable text interface. We will also look at SecureField. SecureField is almost identical to TextField, but masks its content for privacy.

Getting ready

Create a new SwiftUI project named FormattedText.

How to do it…

  1. Enclose the text in the body view with a VStack:
    struct ContentView: View {
        var body: some View {
            VStack{
                Text("Hello World")
            }
         }
    }
  2. Add the .fontWeight(.medium) modifier to the text and observe the weight change in the canvas preview:
    Text("Hello World")
    .fontWeight(.medium)
  3. Add two state variables to ContentView, password and someText. These variables will store the user input placed in SecureField and TextField, respectively:
    struct ContentView: View {
    @State var password = ""
    @State var someText = ""
    var body: someView {
    …
    }
  4. Add SecureField and a Text view to display the value entered in TextField:
         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 texts and modifiers to the list:
                Text("\(someText)")
                    .font(.largeTitle)
                    .underline()
                Text("Changing text color and make it bold")
                    .foregroundColor(Color.blue)
                    .bold()
                Text("Use kerning to change space between                    lines of 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(Color.yellow)
                    .multilineTextAlignment(.trailing)
                    .lineSpacing(10)      

    The resulting preview should look as follows:

    Figure 1.4 – FormattedText preview

    Figure 1.4 – FormattedText preview

  7. Run the code in a simulator or click the play button next to the preview. Enter some text in the SecureField and TextField.

Text entered in the SecureField will be masked, while text in the TextField will be in clear text.

How it works…

Text views have a number of modifiers for font, spacing, and other formatting requirements. When in doubt, type . after the text view and choose from the list of possible options that will appear. This is shown in the following example:

Figure 1.5 – Using Xcode autocomplete to view formatting options

Figure 1.5 – Using Xcode autocomplete to view formatting options

Unlike regular text views, TextFields and SecureFields require state variables to store the value entered by the user. State variables are declared using the keyword @State. Values entered by the user are stored using the process of binding, where the state variable is bound to the SecureField or TextField input parameter. 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 content of bound state variables is displayed without using the $ symbol because no binding is required when displaying content, as shown in the following code:

Text("\(someText)")

There's more…

Try adding an eleventh element to the VStack. You will get an error, because SwiftUI views can hold a maximum of 10 elements. To add an eleventh element, you would have to enclose the first 10 elements in a Group view. Groups are required because the SwiftUI view building system has various code designed to allow the addition of views 1 through 10 but not beyond. A Group view can be used as shown in the following code:

Group {
	Text("Item 1")
	…
	Text("Item 10")
}
Text("Item 11")

Items in a Group are considered as one view. The preceding code thus allows us to display 11 items as two views, the Group view, and the Text view at the bottom.

See also

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

 

Using images

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/. Special thanks to jf-brou, Kieran White, and Camilo Fierro.

Getting ready

Create a new SwiftUI project named ImageApp.

How to do it…

  1. Replace the initial Text view with a Vstack.
  2. Download the project images from the GitHub link at https://github.com/PacktPublishing/SwiftUI-Cookbook/tree/master/Chapter01%20-%20Using%20the%20basic%20SwiftUI%20Views%20and%20Controls/03%20-%20Using%20Images
  3. Drag and drop the downloaded images for this recipe into the project's Assets.xcassets folder, as shown in the following screenshot:
    Figure 1.6 – Assets.xcassets folder in Xcode

    Figure 1.6 – Assets.xcassets folder in Xcode

  4. Add an Image view to VStack:
    Image("dogs1")

    Observe the result in the canvas preview.

  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("dog-and-nature")
       	.resizable()
        	.aspectRatio(contentMode: .fit)
       	.frame(width:300, height:200)
        	.clipShape(Circle())
        	.overlay(Circle().stroke(Color.blue, lineWidth: 6))
         	.shadow(radius: 10)
  8. To use a UIImage class as input for images, create a function, getImageFromUIImage(image: String), that accepts an image name and returns a UIImage:
    func getImageFromUIImage(image:String) -> UIImage {
        guard let img = UIImage(named: image) else {
            fatalError("Unable to load image")
        }
        return img
    }
  9. Use getImageFromUIImage(image: String) to display a UIImage within the VStack. The resulting code should look as follows:
    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: getImageFromUIImage(image:                    "dog2"))
                .resizable()
                .frame(width: 200, height: 200)
                .aspectRatio(contentMode: .fit)
                    
            }
        }
    }
    func getImageFromUIImage(image:String) -> UIImage {
        guard let img = UIImage(named: image) else {
            fatalError("Unable to load image")
        }
        return img
    }

    The completed application should look as follows:

Figure 1.7 – ImageApp preview

Figure 1.7 – ImageApp 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 11 Pro Max screen:

Figure 1.8 – Dog-and-nature image without the resizable modifier

Figure 1.8 – 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

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

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

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

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 () modifier may lead to different results, or cause an error.

See also

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

 

Adding buttons and navigating with them

In this recipe, we will learn how to use various buttons available in SwiftUI. We will use a Button view to trigger the change of a count when clicked, implement a navigationView 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

Create a new SwiftUI project named ButtonsApp.

How to do it…

  1. Add a new SwiftUI View file called ButtonView to the project: File | New | File, or press the shortcut keys: + N.
  2. Select SwiftUI View from the User Interface 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.
  7. Open the ContentView.swift file and create a NavigationView to navigate between the SwiftUI views we added to the project:
    NavigationView {
                VStack{
                   NavigationLink(destination: ButtonView()){
                        Text("Buttons")
                    }
                    
                    NavigationLink(destination:                                  EditButtonView()) {
                        Text("EditButtons")
                            .padding()
                    }
                    NavigationLink(destination:                                  MenuButtonView()) {
                        Text("MenuButtons")
                            .padding()
                    }
                    NavigationLink(destination:                                  PasteButtonView()) {
                        Text("PasteButtons")
                            .padding()
                    }
                    NavigationLink(destination:
                        Text("Very long text that should                      not be displayed in a single                      line because it is not good design")
                        .padding()
                        .navigationBarTitle(Text("Detail"))
                    ) {
                        Text("details about text")
                            .padding()
                    }
                    
                }.navigationBarTitle(Text("Main View"),                displayMode: .inline)

    Upon completion, the ContentView preview should look like the following screenshot:

    Figure 1.13 – ButtonsApp ContentView

    Figure 1.13 – ButtonsApp ContentView

  8. Open the EditButtonView.swift file in the project navigator and add the following code that implements an EditButton:
    @State private var animals = ["Cats", "Dogs", "Goats"]
        var body: some View {
            NavigationView{
                List{
                    ForEach(animals, id: \.self){ animal in
                        Text(animal)
                    }.onDelete(perform: removeAnimal)
                }
                .navigationBarItems(trailing:EditButton())
                .navigationBarTitle(Text("EditButtonView"),               displayMode: .inline)
            }
        }
        func removeAnimal(at offsets: IndexSet){
            animals.remove(atOffsets: offsets)
        }
  9. Open the MenuButtonView.swift file and add the code for MenuButtons:
        var body: some View {
            Text("MenuButtons are currently available on                MacOS currently")
                .padding()
                .navigationBarTitle("MenuButtons",                displayMode: .inline)
            /*
            MenuButton("country +") {
                Button("USA") { print("Selected USA") }
                    .background(Color.accentColor)
                Button("India") { print("Selected India") }
            }
             */
        }
  10. Open the PasteButtonView.swift file and implement the text regarding PasteButtons:
        @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()
            }.navigationBarTitle("PasteButton", displayMode:             .inline)
        }

Go back to ContentView and 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 has to be placed in a NavigationView prior to being used. It takes 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.

NavigationLink buttons can be used to move from one SwiftUI view file to another, for example, moving from ContentView to EditButtonView. It can also be used to display text details without creating a SwiftUI view 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 .navigationBarTitle(Text("Main View"), displayMode: .inline) modifier adds a title to ContentViewPage. The first argument passed to the modifier represents the title to be displayed, and the displayMode argument controls the style for displaying the navigation bar.

The .navigationBarTitle() modifier was also added to EditButtonView and other views. Since these views do not contain NavigationView 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.

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

As of April 2020, MenuButtons and PasteButtons are only available on macOS. This recipe provides some sample code for menu buttons. Refer to the See also section of this recipe for code on how the PasteButton is implemented.

See also

The code for implementing PasteButtons can be found here: https://gist.github.com/sturdysturge/79c73600cfb683663c1d70f5c0778020#file-swiftuidocumentationpastebutton-swift

More information regarding NavigationLink buttons can be found here: https://developer.apple.com/documentation/swiftui/navigationlink

 

Beyond buttons – how to use advanced pickers

In this recipe, we will learn how to implement the pickers, namely, Picker, Toggle, Slider, Stepper, and DatePickers. Pickers are typically used to prompt the user to select from a set of mutually exclusive values; Toggles are used to switch between on/off states; and Sliders are used to select a value from a bounded linear range of values. Like Sliders, Steppers also provide the user interface for selecting from a range of values. However, Steppers use a + and sign to allow the users to increment the desired value by a certain amount. Finally, DatePickers are used to select dates.

Getting ready

Create a new SwiftUI project named PickersApp.

How to do it…

  1. In ContentView.swift, create @State variables to hold the values selected from Pickers and other controls. Place the variables between the ContentView struct and the body:
        @State var choice = 0
        @State var showText = false
        @State var transitModes = ["Bike", "Car", "Bus"]
        @State var sliderVal: Float = 0
        @State var stepVal = 0
        @State var gameTime = Date()
  2. Create a Form view within the body view of the ContentView struct. Add a Section view and a Picker to the form:
    Form{
        Section{
          Picker(selection: $choice, label:Text("Transit                Mode")){
             ForEach( 0 ..< transitModes.count) { index in
                 Text("\(self.transitModes[index])")
                 }
             }.pickerStyle(SegmentedPickerStyle())
                  Text("Current choice: \                    (transitModes[choice])")
         }
    }
  3. Now, add a 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()...)
                }

    The result should be a beautiful form, as seen in the following diagram:

Figure 1.14 – SwiftUI form with pickers

Figure 1.14 – SwiftUI form with pickers

How it works…

Form views group controls used for data entry and Section creates hierarchical view content. Enclosing Section views, our Form creates the gray area between each picker.

To use SwiftUI pickers, we first need to declare @State variables to store the values from the pickers.

Pickers are used for selecting from a set of mutually exclusive values. In the following example, Picker is used to select a transit mode from an array of strings called TransitModes:

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

As shown in the example, a Picker view takes two parameters, a state variable that holds the value selected, and a label. 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 transit mode indices. The transit mode located in the selected index can then be displayed using Text("\(self.transitModes[index])").

Toggles 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 current value of the toggle @State property stores the current value of the Toggle and can be used to show or hide the view as necessary.

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, the first being a string representing the label of the Stepper. The two other arguments, value and in, hold the @State variable that binds the user input and the range of the Stepper, respectively.

Two applications of DatePickers are demonstrated in this recipe. The first from the top shows one whose first argument is the label of DatePicker, and the second argument, selection, holds the state variable that binds the user input. Such a DatePicker would allow the user to pick any date. In the second version of DatePicker, the last contains a third parameter, in, representing the date range. The date range here only allows future dates to be selected.

Important note

The @State variables should 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 view. Styles can be overridden using the .pickerStyle(..) modifier.

 

How to apply groups of styles using ViewModifiers

SwiftUI comes with built-in modifiers such as background() and fontWeight(), among others. SwiftUI also gives programmers the ability to create their own custom modifiers to do something specific. Custom modifiers allow the programmer to combine multiple existing modifiers into a single modifier.

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 ViewModifiersApp.

How to do it…

  1. Change the Text in ContentView to "Perfect":
    Text("Perfect")
  2. In the ContentView.swift file, create a struct that conforms to the ViewModifier protocol, accepts parameter of the Color type, and applies styles to the body:
     struct BackgroundStyle: ViewModifier {
        var bgColor: Color
        func body(content: Content) -> some View{
            content
            .frame(width:UIScreen.main.bounds.width * 0.3)
            .foregroundColor(Color.black)
            .padding()
            .background(bgColor)
            .cornerRadius(CGFloat(20))
        }
    }
  3. Add the custom style to the text using the modifier() modifier:
    Text("Perfect").modifier(BackgroundStyle(bgColor: .blue))
  4. To apply styles without using the modifier() modifier, create an extension to the View protocol:
    extension View {
        func backgroundStyle(color: Color) -> some View{
            self.modifier(BackgroundStyle(bgColor: color))
        }
    }
  5. Remove the modifier on the Text view and add your custom style using the backgroundStyle() modifier you just created:
    Text("Perfect")
            .backgroundStyle(color: Color.red)

    The result should be as follows:

Figure 1.15 – Custom ViewModifier

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 ViewModifier modifies a view, thereby producing a different version of the original value. To apply ViewModifier, we first create a view that conforms to the ViewModifier protocol. In this example, we created the BackgroundStyle modifier.

The bgColor variable in BackgroundStyle means it would accept a color as a parameter and use it to change the style of the view.

The body function within the view gets the current body of the caller. Styles applied to the content parameter will be applied to the caller's view.

To make custom modifiers easier to use, we create an extension of View and add a function that calls our custom modifier. The backgroundStyle(color: Color) function in our View extension allows us to apply the custom modifier to the view using .backgroundStyle(color:Color) 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

ViewBuilder is a custom parameter attribute that constructs views from closures. ViewBuilder can be used to create custom views that can be used across the application with minimal or no code duplication. We will create a SwiftUI view, BlueCircle.swift, for declaring the ViewBuilder and implement the custom ViewBuilder. The ContentView.swift file will be used to implement the custom view.

Getting ready

Create a new SwiftUI project named ViewBuildersApp.

How to do it…

  1. Open the ViewBuildersApp and add a new SwiftUI view to the project from the menu: File | New | File.
  2. Select the SwiftUI view from the menu.
  3. Click Next.
  4. Name the file BlueCircle and click Create.
  5. Delete the BlueCircle_Previews: struct from the file (delete all five lines of code).
  6. Add BlueCircle ViewModifier to the file:
    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()
        }
    }
  7. Open the ContentView.swift file and implement BlueCircle ViewModifier:
            VStack {
                BlueCircle {
                    Text("some text here")
    			Rectangle()
    			.fill(Color.red)
    			.frame(width: 40, height: 40)
                }
                BlueCircle {
                    Text("Another example")
                }
            }

    The result should look like the following screenshot:

Figure 1.16 – ViewModifiers preview

Figure 1.16 – ViewModifiers preview

How it works…

ViewBuilder structs create view templates that can be used without the need to duplicate code.

ViewBuilder is a struct that implements the View protocol and therefore should have a body variable within it.

Once created and initialized, the UI logic added within ViewBuilder would be displayed each time the custom view is used. The location of the Content constant determines where items added to the custom view would be displayed.

See also

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

 

Simple graphics using SF Symbols

The San Francisco Symbols (also known as SF Symbols) provide a set of over 1,500 consistent and highly configurable symbols.

To browse through the list or look at symbol names, you can download the SF Symbols app from https://developer.apple.com/design/downloads/SF-Symbols.dmg.

In this recipe, we will create a project that uses various SF Symbols and applies different styles to them.

Getting ready

Create a new SwiftUI project named SFSybmolsApp.

How to do it…

Using SF Symbols is very similar to using images.

  1. Open the ContentView.swift file and delete the default Text view.
  2. Add a VStack and an HStack to the ContentView body and implement various SF symbols:
    VStack {
                HStack{
                    Image(systemName: "c.circle.fill")
                    Image(systemName: "o.circle.fill")
                    Image(systemName: "o.circle.fill")
                    Image(systemName: "k.circle.fill")
                    Image(systemName: "b.circle.fill")
                    Image(systemName: "o.circle.fill")
                    Image(systemName: "o.circle.fill")
                    Image(systemName: "k.circle.fill")
                }.foregroundColor(.blue)
                    .font(.title)
                    .padding()
                HStack{
                    Image(systemName: "clock")
                        .foregroundColor(Color.purple)
                        .font(.largeTitle)
                    Image(systemName: "wifi")
                    .foregroundColor(Color.red)
                    .font(.largeTitle)
                }     
            }

    The resulting preview should appear as shown in the following diagram:

Figure 1.17 – SF symbols in action

Figure 1.17 – SF symbols in action

How it works…

SF Symbols are displayed using the Image view and a systemName parameter. Most modifiers used on Image and Text views can then be applied to SF Symbols. In this example, fonts, frame, and background modifiers were applied to the images.

See also

Apple documentation on SF Symbols:

https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/

 

The best of both worlds – integrating UIKit into SwiftUI

SwiftUI was announced at WWDC 2019 and is only available on iOS 13 devices or later. Due to its relative newness, SwiftUI lacks broad API coverage compared to UIKit. For example, as of April 2020, UICollectionViews, UITextView, UIKit APIs for displaying grids, and multiline texts are not available in SwiftUI. There is therefore a need to implement certain UIKit APIs in SwiftUI.

This recipe goes over the required process for integrating UIKit into 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…

UIKit views are displayed in SwiftUI by using the representable protocol. Follow these steps to implement UIActivityIndicatorView in SwiftUI.

  1. Within the Xcode menu, click File | New | File and select SwiftUI view. Name the view ActivityIndicator.
  2. Import UIKit into ActivityIndicator.
  3. Modify the code to make ActivityIndicator use the representable 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. Open the ContentView.swift file and add the following code to apply ActivityIndicator. Also add a toggle control to turn the indicator on or off.
    struct ContentView: View {
        @State var animate = true
        var body: some View {
            VStack{
                ActivityIndicator(animating:  animate)
                HStack{
                    Toggle(isOn: $animate){
                        Text("Toggle Activity")
                    }
                }
            }
        }
    }

    The result should look as follows:

Figure 1.18 – UIKit ActivityIndicator in SwiftUI

Figure 1.18 – UIKit ActivityIndicator in SwiftUI

How it works…

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

To conform to the UIViewRepresentable protocol, we added a makeUIView and updateUIView function to our ActivityIndicator struct.

The makeUIView() function creates and prepares the view. In this case, it just returns the UIActivityIndicator view.

The updateUIView method updates the UIView when the animation state changes. The value of the animating variable in the struct is used to start or stop animating UIActivityIndicator.

Important note

If working on an iOS 14+ app, use ProgressView provided out of the box.

See also

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

 

Adding SwiftUI to an existing app

In this recipe, we will learn how to navigate from a UIKit view to a SwiftUI view while passing a text as argument from our UIKit storyboard to our SwiftUI view.

A storyboard is a visual representation of the user interface in UIKit. The Main.storyboard file is to UIKit as the ContentView.swift file is to SwiftUI. They both are the default home views created when you start a new project.

The recipe begins with a UIKit project that contains a button.

Getting ready

To prepare for this recipe, perform the following steps:

  1. Clone or download the GitHub project at https://github.com/PacktPublishing/SwiftUI-Cookbook.
  2. Open the StartingPoint folder located at SwiftUI-Cookbook/Chapter01 - Using the basic SwiftUI Views and Controls/10 - Adding SwiftUI to UIKit and double-click on the AddSwiftUIToUIKitApp.xcodeproj file. It opens up the UIKit project in Xcode.

How to do it…

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

  1. Open up the Main.storyboard file in Xcode by clicking on it. The display should look as follows:
    Figure 1.19 – UIKit ViewController

    Figure 1.19 – UIKit ViewController

  2. Click anywhere in ViewController to select it.
  3. In the Xcode menu, click Editor | Embed in | Navigation Controller.
  4. Add a new ViewController to the project.
  5. Click on the + button, five buttons to the left of the top-right corner of Xcode.
  6. In the pop-up menu that appears, type hosting and select the Hosting View Controller:
    Figure 1.20 – Creating a UIKit ViewController

    Figure 1.20 – Creating a UIKit ViewController

  7. Hold down the Ctrl key, and then click and drag from the button in ViewController to the new hosting ViewController recently created.
  8. In the pop-up menu, select the Show action segue.
  9. Click the Adjust Editor Options button:
    Figure 1.21 – Adjust Editor Options button

    Figure 1.21 – Adjust Editor Options button

  10. Click Assistant. This splits the view into two panes, as shown here:
    Figure 1.22 View when Assistant is opened

    Figure 1.22 View when Assistant is opened

  11. To create a segue action, hold the Ctrl key, and then click and drag from the segue button to the space after the viewDidLoad() function in ViewController.swift.
  12. In the pop-up menu, name the segue 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: ...)#>
        }
  13. Add a statement to import SwiftUI at the top of the ViewController page, next to Import UIKit:
    Import SwiftUI 
  14. Within the goToSwiftUI segue action, create the string that will be passed to the SwiftUI view. Also create a rootView variable that indicates the SwiftUI view you would like to navigate to. Finally, return the viewController used to display the SwiftUI view. The resulting code should be as follows:
        @IBSegueAction func goToSwiftUI(_ coder: NSCoder) ->       UIViewController? {
            let greetings = "Hello From UIKit"
            let rootView = Greetings(randomText: greetings)
            return UIHostingController(coder: coder,           rootView: rootView)
        }
  15. Create the Greetings SwiftUI view.
  16. Within the Xcode menu, click File | New | File and select SwiftUI View.
  17. Name the View Greetings.swift.
  18. Add the code to display some text:
    struct Greetings: View {
        var randomText = ""
        var body: some View {
            Text(randomText)
        }
    }

    Run the resulting program in a simulator or device.

How it works…

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

We started by performing core UIKit concepts, such as adding a NavigationView controller to the storyboard. We then proceeded to add HostingController as a placeholder for our SwiftUI view.

Lastly, an IBSegueAction function was created to present the SwiftUI view upon clicking a button.

 

More views and controls (iOS 14+)

Let's now take a look at some new SwiftUI views and controls introduced in iOS 14. We will look at the ProgressView, Label, ColorPicker, Link, TextEditor, and Menu views. We use ProgressViews to show the degree of completion of a task. There are two types of ProgressViews; indeterminate progress views show a spinning circle till a task is completed, and determinate progress views show a bar that gets filled up to show the progress of a task.

Labels provide an easy way to display a label and icon. ColorPickers expand when clicked and present the user with a wide range of colors to select from. TextEditor provides a multiline interface for the user to input text.

Finally, Menus present a list of items the user can chose from. Each item should perform a specific action.

Getting ready

Create a new SwiftUI app called MoreViewsAndControls.

How to do it…

Let's now implement all the new views and controls in the ContentView.swift file. To make the resulting app elegant, we will implement each item in a separate Section within a List. Let's proceed as follows:

  1. Within the ContentView struct, just above the body variable, declare some state variables that we'll use later:
        @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. Within the body view, let's add a List view and a Section view containing a ProgressView:
            List{
                Section(header: Text("ProgressViews")) {
                    ProgressView("Indeterminate progress                                 view")
                    ProgressView("Downloading",value:                   progress, total:2)
                    Button("More"){
                        if(progress < 2){
                            progress += 0.5
                        }    
                    }
                }
      }
  3. Now, let's add another section that implements two Labels in two ways:
                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. Add the Link section below the ColorPicker section:
                Section(header: Text("Link")) {
                  Link("Packt Publishing", destination:               URL(string: "https://www.packtpub.com/")!)
                }
  5. Next, add a TextEditor:
                Section(header: Text("TextEditor")) {
                    TextEditor(text: $someText)
                    Text("current editor text:\n\(someText)")
                } 
  6. Next, 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
                        }                }
                }
  7. Finally, let's improve the style by adding a .listStyle() modifier to our List:
            List{
                …
            }.listStyle(GroupedListStyle())

    Here is what the resulting app should look like:

Figure 1.23 – MoreViewsAndControls app

Figure 1.23 – MoreViewsAndControls app

Nice work! Run the app in Xcode preview and take some time to interact with the components to get a better appreciation for how they work.

How it works…

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

Indeterminate ProgressViews require no parameter, just a label:

ProgressView("Indeterminate progress view")
ProgressView()

Determinate ProgressViews, 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 is optional, but defaults to 1.0 if not specified.

Labels are used to display some text and an icon. Labels can be implemented on a single line as follows:

Label("Slow ", systemImage: "tortoise.fill")

Alternatively, they can be implemented on multiple lines. This option is used when you need to customize how the Label text or icon is displayed:

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

ColorPickers let you display a palate for users to pick colors from. We pass in an @State variable to the ColorPicker that stores the value of the selected color:

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

The Link view is used to display clickable links:

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

TextEditor provides an easy way to allow the user to provide multiline input. The value entered is stored in the @State variable, text:

TextEditor(text: $someText)

Menu provides a convenient way of presenting a user with a list of actions to choose from:

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

Menus can also be nested:

                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                          'magic'"){
                            someText = "magic"
                        }
                        Button("Turn first picker green") {
                            color = Color.green
                        }
                    }
                }

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

About the Authors

  • Giordano Scalzo

    Giordano Scalzo is a developer with 20 years of programming experience, since the days of ZX Spectrum. He has worked in Swift, Objective-C, C/C++, Java, .Net, Ruby, Python, and in a ton of other languages that he has forgotten the names of. After years of backend development, over the past 5 years, Giordano has developed extensively for iOS, releasing more than 20 appsapps that he wrote for clients, enterprise applications, or on his own. Currently, he is a contractor in London, where he delivers code for iOS through his company, Effective Code Ltd, with the objective being quality and reliability.

    Browse publications by this author
  • Edgar Nzokwe

    Edgar Nzokwe is a software engineer at Booz Allen and an adjunct computer science instructor at Howard Community College in Maryland. He has more than 5 years of experience in the field of software engineering and has spent most of that time building web and mobile applications. His areas of expertise include SwiftUI, UIKit, and Kotlin. Edgar is dedicated to advancing the knowledge base of SwiftUI because he believes it will significantly improve UI and cross-platform app development time.

    Browse publications by this author

Latest Reviews

(2 reviews total)
more infomore infomore infomore info
I ordered this book two months ago but Packt has not published it yet.

Recommended For You

Book Title
Access this book, plus 7,500 other titles for FREE
Access now