Creating Instagram-like place selection with SwiftUI

Alexey Antonov
5 min readOct 27, 2020

Let’s pretend that you’re writing an app with SwiftUI that will need to provide nearby places with filtering option sorted by distance. Just like one in your Instagram. You think it’s hard? Let’s get our hands dirty and ruin this myth.

Create a new iOS App that uses SwiftUI and open ContentView.swift:

Get rid of the Text component and add the TextField component. TextField requires two parameters: title (that in other words is a placeholder) and text (that will contain entered text). As text parameter has type of Binding<String>, we should create a variable to store our value first. So, your code should look like the one below:

To be sure that it works, put your TextField component into a VStack and insert after this text field a Text component with a value of searchText variable. Now, when you type into the text field, you will see the result immediately appearing below.

Also you can add some padding to a VStack to make it more visible

Then, when we made this text field work, let’s dive into some adding. So, first we need to create a model. Go and create a new .swift file (not SwiftUI view!). I named it Place.swift, but it doesn’t matter how you will call it.

In a new file import CoreLocation framework right under import Foundation line. That will be needed for giving coordinates to our places. Let’s begin and create a struct called Place. It will have only two properties: the title of the place (that has String type) and the location of the place (that has CLLocation type). Then, let’s create an array right under the struct and fill it with some places:

Moving back to ContentView.swift file: creating a list of places. For that reason, get rid of the Text component and insert a List component that will contain our places array:

This list contains titles of places, but at the moment something is wrong

The code was quite correct, but something went wrong. The error occurred because the List component requires the array items needed to confirm to Identifiable protocol. It’s necessary to differ items from each other. But we can make a small ‘hack’ as it’s not our final solution. We will add id: \.title parameter to make the items’ titles identify it. Woo-hoo, now it works:

But honestly we won’t need to show a coordinate, so now we will go back and create another struct called Location below the Place struct. This struct is going to confirm to Identifiable protocol and have title and distance fields:

Thus, when the model is ready, let’s go and create another .swift file. Now it’s a ViewModel that will contain all our CoreLocation stuff. So, we also import CoreLocation under Foundation and create a class called ViewModel. It will be a subclass of NSObject and confirm to protocol ObservableObject.

Next, it’s time to add an instance of CLLocationManager, that will do all the geolocation work. As a result, it will be a private constant as we won’t use it outside our class. And right below it we add an initialiser of the class, when we set up the geolocation stuff:

As our class is a subclass of NSObject, we need to override a parent init() method and call super.init()

Here, we ask for permission to use the geolocation when app works in a foreground, tell that our class will be a delegate for CLLocationManager and finally start listening for location updates. As we promised that this class will be a delegate for our location manager, let’s write an extension for our class:

I know that I could chain two last lines of method, but I decided to make it the more understandable way

We need to implement only one method (just for this small purpose), that is called when the phone updates geolocation. As you could notice before, the distance of our struct Location is Optional, because sometimes the GPS module can’t receive current geolocation (when the user is in subway area or somewhere the location is unavailable). That’s why it’s reasonable to leave it optional. Hence, if we can’t get new geolocation we just exit the function. In case we have location, we clean the array with old distances, sort places according new values and fill the locations array with necessary data. We also want to show the distance in kilometres, so divide the value by 1000.

Now we can see it in our app. Just add our viewModel as an ObservedObject and change the List component to accept viewModel.locations array without any id parameters.

Nevertheless we still can’t filter it by name, so let’s apply filter function to our parameter of List component:

List(viewModel.locations.filter { $0.title.hasPrefix(searchText) })

Now if you build and run the app on Simulator or real device, you will see something like that:

Yes, I am obsessed with new iPhone 12 mini, so let it be at least on a Simulator 😀

In the end it’ll be nice to have a fancy row with a distance to the location, so let’s create another SwiftUI view just under the ContentView.

The specifier allows us to cut long numbers in a string

Now we can replace Text component in our list with PlaceRaw filling it with place parameter and see the result:

Hooray! We did it! 🎊🎉

Now you’re done! Thanks for reading! Let’s go and have some pizza nearby 🍕!

P.S. All code is available on GitHub: https://github.com/antonoff/Places

P.P.S. I want to thank the Apple Foundation students team NAPPoletan for their amazing work and inspiring me to write this tutorial!

--

--