Building the SwiftUI Grid view

Preparing the photo grid ūüĖľ

If the user has dropped a pin at a specific location and then taps on it, we want to overlay the map with a modal view. In this view, we want to display the images that we’ll download by using the Flickr API. 

To do this, we declare a new State property inside our ContentView we use to control whether the modal view should be opened. 

@State var showPhotoGrid = false

For the modal view itself, we create a new SwiftUI file and name it “PhotoGrid.swift”. We will replace the default Text view later on.

Next, we add a .sheet modifier to the ZStack in our ContentView that shows the PhotoGrid view if the showPhotoGrid State is true. 

ZStack(alignment: .bottomTrailing) {
    //...
}
    //...
    .sheet(isPresented: $showPhotoGrid, content: {
        PhotoGrid()
    })

We want to show the PhotoGrid when the user taps on the dropped pin. To achieve this, we need a Binding in our MyMap to the showPhotoGrid State of our ContentView.

@Binding var showPhotoGrid: Bool

We initialize this Binding in our ContentView again.

MyMap(showPhotoGrid: $showPhotoGrid, currentRegion: $locationManager.currentRegion, currentAnnotation: $currentAnnotation)

But how do we toggle the showPhotoGrid State over the corresponding Binding of our MyMap? Fortunately, this is quite simple. Just add the following method to the Coordinator subclass. 

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    parent.showPhotoGrid = true
}

This method is part of the¬†MKMapViewDelegate¬†protocol and gets called when the user taps on an annotation. Our Coordinator is already conforming to the¬†MKMapViewDelegate¬†protocol. But we have not yet specified that the¬†coordinator¬†should also be the map’s delegate. We define this by writing into the¬†makeUIView¬†function:

func makeUIView(context: Context) -> MKMapView {
    let map = MKMapView()
    //...
    
    map.delegate = context.coordinator
    
    return map
}

If we now run our app, drop a pin, and then tap on it, the PhotoGrid opens as a modal view!

Grids in SwiftUI

Our PhotoGrid will display a collection of downloaded photos. The UIKit framework offers a UICollectionView for this purpose. However, since Xcode 12 and iOS 14, we have a proper alternative in SwiftUI: Grid views. Those Grids views are super easy to implement. We just need to embed a LazyVGrid or LazyHGrid into a ScrollView. Inside this Grid view, we generate the cells by using a ForEach loop. Finally, we customize the appearance of our Grid by using the “columns” and “spacing” arguments.¬†

Let’s start with replacing the pre-generated Text view of the PhotoGrid with a ScrollView. Because we want to know the size of the whole¬†PhotoGrid, we embed the ScrollView into a GeometryReader.

var body: some View {
    GeometryReader { geometry in
        ScrollView {
            
        }
    }
}

Inside this ScrollView, we insert a LazyVGrid view. 

GeometryReader { geometry in
    ScrollView {
        LazyVGrid {
            
        }
    }
}

Note: The difference between an HStack and VStack compared to a LazyHStack and LazyVStack is that a LazyVStack or LazyHStack only loads its as far as it is needed. This means that only that lazy stack view doesn’t create items until it needs to render them onscreen. This can save considerable system and energy resources.

The LazyVGrid should always have two columns with no spacing. To achieve this, we pass an array of two¬†GridItem instances¬†representing two fixed columns with no spacing to “columns” argument. By using 0 for the “spacing” argument, we make sure that there are no gaps between the rows of our LazyVGrid.

LazyVGrid(columns: [GridItem(spacing: 0), GridItem(spacing: 0)], spacing: 0) {
    
}

Now we can generate the image cells by using a ForEach loop. For now, we want to create 20 dummy cells. We will pass the images fetched using the Flickr API to our ForEach loop later on.

LazyVGrid(columns: [GridItem(spacing: 0), GridItem(spacing: 0)], spacing: 0) {
    ForEach(0 ..< 20) { fetchedImage in
        Image("samplePhoto")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: geometry.size.width/2, height: geometry.size.width/2)
            .clipped()
    }
}

Make sure you have a “samplePhoto” image file inside your Assets.xcassets folder. Use this one for example:

Let’s run a Live preview of our ContentView and tap on a dropped pin. The presented PhotoGrid shows a nicely arranged collection of dummy cells!

Recap 

Okay, we’ve done a lot so far. We learned how to work with maps in SwiftUI and query and display the user’s location.

Then we managed to allow the user to mark any location by dropping a pin on it. After the user taps on the pin, a modal view opens that shows a photo collection by using a SwiftUI LazyVGrid. At the moment, this collection contains only sample images. Next, we will learn how to use the Flickr API to download images shot near the selected location, which we will present in our PhotoGrid.

2 replies on “Building the SwiftUI Grid view”

Another fun chapter! Lots of info packed in.

A couple of typos.

1] In the section on ‘Grid Views in SwiftUI’ the file to edit is the just created PhotoGrid.swift not the ContentView.

2] To make the layout in the video match with what’s run in the simulator the height of the image should be set to geometry.size.height / 4 not geometry.size.height / 2.

Leave a Reply

Your email address will not be published. Required fields are marked *