Let’s continue with WWDC2019’s Introducing SwiftUI: Building Your First App to learn how to use SwiftUI.

Download the project

The initial code is in the same state as at the end of (2). If you don’t have the original code, you can download it as follows:

git clone https://github.com/swiftui-from-zero/wwdc2019_building_your_first_app.git
cd wwdc2019_building_your_first_app
git checkout before-new-view
Copy the code

Open wwdc2019_building_your_first_app/Room/ room.xcodeproj with Xcode.

Again, it’s important to note that writing code together with the tutorial is more efficient!

Creating a new view

Create a new SWIFT file in Xcode that displays the details of the conference room. In the Xcode navigation bar, go to File -> New -> File… , select SwiftUI View in User Interface as the template

And I’ll call it RoomDetail. Then Xcode creates a file named RoomDetail.swift that contains the SwiftUI view and the initial template for the preview:

import SwiftUI

struct RoomDetail: View {
    var body: some View {
        Text("Hello, World!")}}struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        RoomDetail()}}Copy the code

Build RoomDetail UI

Like the RoomCell extracted at the end of the last lecture, the RoomDetail needs a member variable of type Room in order to present the conference Room. After adding this member, we also need to modify the code in the preview section to initialize RoomDetail with testData[0] so that the preview view is generated correctly.

import SwiftUI

struct RoomDetail: View {
    let room: Room  // Add the room variable
    
    var body: some View {
        Text("Hello, World!")}}struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        RoomDetail(room: testData[0])   // Don't forget to modify preview}}Copy the code

Next, let’s start designing the UI for this new view.

We are going to show a picture of the conference room in this view, so insert the picture first.

struct RoomDetail: View {
    let room: Room
    
    var body: some View {
        Image(room.imageName)
    }
}
Copy the code

You can see that the image range in the preview is beyond the screen range. SwiftUI uses the original size of the image by default. If you want to change the size of the image, use the modifier.resizable().

After adding.resizable(), you can see that the image will be resized according to the screen size. But such a rigid deformation is obviously not very beautiful, if you want to keep the picture of the original aspectRatio, you need aspectRatio this modifier. AspectRatio has a contentMode parameter that can be passed either.fill or.fit, which will fill the entire space with the image, or.fit, which will fill the entire image, depending on its scale. For a better view of the images, let’s temporarily use.fit.

Now, we can go back to our ContentView and change the View that the navigation bar points to from Text to RoomDetail. So if you click on the grid in the preview of the ContentView, you can jump to the RoomDetail page that we made.

Next we add a navigation title to the RoomDetail page. To keep the title from getting too big, we set the displayMode:.inline.

struct RoomDetail: View {
    let room: Room
    
    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)  // Add the navigation bar title}}Copy the code

However, the preview at this point will not show our new title because the RoomDetail in the preview section is not in the navigation bar. We can get a better view of our view by simply changing the preview code — wrapping the Preview RoomDetail with NavigationView:

struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView { RoomDetail(room: testData[0])}}}Copy the code

Change views based on user input

A curious request came in from our users, who were particularly concerned about the quality of the table in the conference room and wanted to be able to zoom in on the images on the page so they could see the table in the room more clearly. Our solution to this strange requirement is to allow the user to zoom in on the image by clicking on it, which in the code changes from.aspectratio (contentMode:.fit) to.aspectratio (contentMode:.fill).

So how do you do this transformation? A natural idea is to add a Zoomed variable to the RoomDetail to control whether the image is enlarged or not. Add an onTapGesture modifier outside the image, allowing the user to adjust the Zoomed value each time they click.

struct RoomDetail: View {
    let room: Room
    var zoomed: Bool = false

    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                zoomed.toggle()
            }
    }
}
Copy the code

However, the code above triggers an error:

Error: Cannot use mutating member on immutable value: ‘self’ is immutable. In other words, the RoomDetail view itself is immutable. SwiftUI’s internal data model requires views to be immutable, so if you need to change a member variable, you need to prefix it with a property wrapper called @state. That is:

struct RoomDetail: View {
    let room: Room
    @State private var zoomed: Bool = false  // Private can be omitted

    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                zoomed.toggle()
            }
    }
}
Copy the code

When you click on the image in the preview, you can see how it changes with the click.

SwiftUI’s data model eliminates the need to prioritize and interact with various callback functions, allowing you to focus on UI interactions. We’ll cover it in more detail in a later tutorial. Now you just have to remember that if you want to change the value of a member variable in your view, you just have to put at sign State in front of the variable.

Add animation

The current switch is a bit stiff, so let’s add animation to the transition. Adding animations is incredibly easy, just layer withAnimation {} around zoomed. Toggle () :

Isn’t it easy? SwiftUI will automatically add appropriate animations based on the view changes, which in our case is continuous scaling. And the animation can be broken in the middle, we can zoom in halfway and click again to zoom it out.

Superimpose new elements

Another user requirement is to show whether the conference room has video conferencing capabilities. So we’re going to add a camera icon in the RoomDetail view for the conference room where video conferencing can take place. We used ZStack to stack the different views (camera icon and conference room image) and chose to use video.fill in SF Symbol for the camera icon and change its size with.font (this is SF Symbol image, That is, the image specified by systemName, unique resizing method). Then we set ZStack to alignment:. TopLeading to move the camera icon to the top left corner. Finally, we add the padding to the icon. The adjusted RoomDetail is:

struct RoomDetail: View {
    let room: Room
    @State private var zoomed: Bool = false

    var body: some View {
        ZStack(alignment: .topLeading) {  // Align the top left corner
            Image(room.imageName)
                .

            Image(systemName: "video.fill")  // Camera icon
                .font(.title)  // Resize the icon
          	    .padding()  // padding}}}Copy the code

At this point, the location of the camera is in the upper left corner of the conference room picture. We want to put it in the upper left corner of the entire view. The trick here is to add a.frame Modifier after the Image to adjust the space occupied by the Image view. Here we set the image to take up all the space so topLeading aligned on the upper left corner of the screen.

struct RoomDetail: View {
    .

    var body: some View {
        ZStack(alignment: .topLeading) {
            Image(room.imageName)
                .
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)

            Image(systemName: "video.fill")
                .}}}Copy the code

We will cover SwiftUI’s layout in more detail in a later tutorial. Now you just have to remember this little trick.

Next, we want to display the camera icon on the page only in the conference room that supports video conferencing. Because SwiftUI is declarative programming, all you need to do is add a conditional statement to the body.

struct RoomDetail: View {
    .

    var body: some View {
        ZStack(alignment: .topLeading) {
            Image(room.imageName)
                .

            if room.hasVideo {
                Image(systemName: "video.fill")
                    .}}}}Copy the code

Create multiple previews

After adding judgment to the above page, we want to check to see if the camera icon has been removed from the room page that does not support video conferencing. An easy way to do this is to change the initialization data in the preview from RoomDetail(room: testData[0]) to RoomDetail(Room: testData[1]). In our test data, the first room supports video conferencing and the second does not, so the changed preview view should not have the camera icon. However, if we need to change the style of the camera icon later, we will have to change the preview code back again, which is very inconvenient.

Is there a way to display two preview views at the same time, one with video conferencing and one without? You can hold down Command and click on NavigationView in preview code, select Group, and a Group view wraps around NavigationView. You can set multiple preview views in the Group view. Such as:

struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            NavigationView { RoomDetail(room: testData[0])}NavigationView { RoomDetail(room: testData[1])}}}}Copy the code

Once set up, you can see two preview views on the right.

Custom animation

We continue to come back and beautify our pages. The camera icon is now displayed when zooming in or out. In order for the user to see the larger image better, we can set the icon not to show when the image is enlarged. All you need to do is add a judgment statement outside the icon image.

struct RoomDetail: View {
    .

    var body: some View {
        ZStack(alignment: .topLeading) {
            .

            if room.hasVideo && !zoomed {  // 添加了 && !zoomed
                Image(systemName: "video.fill")
                    .font(.title)
                    .padding()
            }
        }
        
    }
}
Copy the code

At this point, when you click on the image, SwiftUI also automatically animates the vanishing and reappearance of the camera. The default animations are gradual and fade out.

We would like to change this animation to use the. Transition modifier. This time we set.transition(.move(.leading)) to slide in and out of leading (default: left). To get a better look at the animation, we set the animation runtime on withAnimation. The modified code becomes:

struct RoomDetail: View {
    .

    var body: some View {
        ZStack(alignment: .topLeading) {
            Image(room.imageName)
                .
                .onTapGesture {
                    // Change the animation time to 1s, using the default linear variation
                    withAnimation(.linear(duration: 1)) { zoomed.toggle() }
                }
                .

            if room.hasVideo && !zoomed {
                Image(systemName: "video.fill")
                    .
                    .transition(.move(edge: .leading))
            }
        }
    }
}
Copy the code

The modified animation is as follows:

Now we are done with the RoomDetail page.

Due to space limitation, this article will come to an end. In this tutorial, you learned how to create new views, adjust views based on user input, create multiple preview views, and customize animations. So far, we’ve been using testData as a fixed tool. The next tutorial will show you how to add and remove conference room lists.