Making a Simple Reddit App With SwiftUI
My experience creating a classic beginner iOS app: a Reddit client
Note that this article was written using the macOS Catalina GM release, Xcode 11.0, and Swift 5.1. Therefore, some material is subject to change.
Now that Xcode 11 is finally here, and SwiftUI appears to have stabilized, I felt compelled to create a simple app, write about my experience doing so, and maybe teach a few things to those who haven’t used the new framework very much.
To do this, I decided to make a classic beginner iOS app: a Reddit client.
What We’re Making
As you can see in the above GIF, this Reddit app simply allows users to view a single page of posts, tap on a row to load that post in a web view, and search for a specific subreddit to see the first page of posts in that subreddit.
That’s it! Sounds easy right?
Well, if you’ve ever made an app before, it is easy! But, take a step back and think about everything you would need to do to implement this app with UIKit.
You would need to create a view controller with a
UITableViewDelegate, add a
UITextFieldDelegate, and then segue to either an SFSafariViewController or a view controller with an embedded
On top of all that, we would need to create all the views either in storyboard(s) or in code. Clearly, the amount of view and controller code we need to write to create this simple app quickly explodes.
With SwiftUI, however, much of the view and controller code simply disappears. With SwiftUI, all we need to do is create three views:
- One row to represent each Reddit post.
- Another to show the entire list of posts, as well as the search text field at the top.
- And a third that displays the web content.
Additionally, almost all of the controller code goes away or moves out into the view model.
Before we dive into the SwiftUI view layer, however, let’s first create our model and view model code as that would be almost entirely the same for both a UIKit and a SwiftUI implementation.
Creating the Models
The post model
To create our models, we first have to know what the data is going to look like when it comes from Reddit’s API. To do this, open a new tab and go to this URL. That’s what the data powering Reddit looks like.
Unfortunately, it probably looks pretty ugly so I recommend copying and pasting it into a program like JSON Formatter, that can format the JSON in a more programmer-readable way.
For this simple app, we’re going to strip away all the things we don’t need and create just two models. One to represent a
Post and one to represent our list of posts which we will call
The post model
Everything here should look pretty familiar to you if you’ve ever worked on an iOS app before, except for one thing: the conformance to the
Identifiable protocol forces us to add the ID property with the assumption that the value of this ID is unique to each instance of this type. We do this so that SwiftUI can efficiently layout our list of
The listing model
Listing type is very straightforward. It basically just holds onto the list of
Creating the Service Layer
Like the models, this part of the code works for both a UIKit and a SwiftUI version of this app.
This class is pretty straightforward as well. It takes in a
URLSession to perform the network request, a
JSONDecoder to parse the response, and it uses both of these in just one function that performs the fetch, parses the data, and sends the data or an error to its completion handler.
Creating the View Model
This is where things might start to look more strange. You can see at the top that we import Apple’s brand new
Combine library, which is essentially their take on functional reactive programming.
Combine allows us to conform our view model to the
ObservableObject protocol which, in turn, allows us to set up reactive bindings between our view model and our view.
You’ll also notice the
@Published property wrapper. This property wrapper provides a shorthand way for its associated property to automatically notify its observers whenever its value changes.
In addition to having observable properties, this view model also provides an interface for the view to fetch the posts from Reddit’s servers. This method, like the models and the service layer is the same for both a UIKit and a SwiftUI version of this app.
Now, the SwiftUI fun beings…
Creating the Post Row
If you run this in the Preview window, you’ll notice the following:
Let’s break this down.
PostRow conforms to the
View protocol, which means we have to implement
var body: some View.
I’m not going to go into the weeds of what exactly all that means, but to sum it up as succinctly as possible, what we put in that computed property is what the view renders.
Within the body, the outermost view is an
HStack (horizontal stack) which horizontally aligns the views inside it from left to right. Inside the
HStack, we have an
Image and a
VStack (vertical stack).
Image is one of Apple’s brand new SF symbols, which is an extensive library of simple, easy to use, everyday icons. I decided to use one in this project just for the sake of playing with the new SF symbols, and, as you can see, it’s as easy as if the image was already in your asset catalog.
VStack, we have two
Text objects that are, you guessed it, stacked vertically. The top label renders our
Post’s title using the headline font, and the bottom label renders our
Post’s subreddit name using the subheadline font and the text color set to gray.
As you can see, the SwiftUI syntax is extremely expressive and easy to read, even if you’ve never worked with SwiftUI before.
Creating the Posts List
Finally, here is the view that renders the list of posts. This is obviously a bit more complicated than the
PostRow, so let’s break it down, starting from the top.
At the top of this struct, we have the view model we created earlier and it has the property wrapper
This property wrapper goes hand in hand with
ObservableObject and is what allows our view and our view model to be bound together so that they always stay in sync.
Then we have two more properties in the view:
subredditTitle. These both have the
@State property wrapper.
This property wrapper works in much the same way as
@ObservedObject, in that it keeps the view and the value of the associated property in sync, but it’s typically used for properties that our view model doesn’t care about.
Next, the outermost view in our body is a
NavigationView which creates a standard iOS navigation bar.
NavigationView, we have a
List. Lists give us the ability to create vertically scrollable lists of any combination of
At the top of the
List is the search
TextField. The initializer sets the placeholder text to “Search Subreddit”, binds the value in the text field to the
query property, and when the user taps return, it executes the trailing closure. In this case, updating the
subredditTitle property and performing the fetch.
TextField, we iterate through the array of posts in our view model, and create a
PostRow for each one and wrap it inside a
Wrapping each row in a
NavigationLink adds the disclosure indicator to the trailing side of each row and automatically adds pushing and popping functionality to and from the destination
Lastly, we need two modifiers to finish polishing off this
View. First, the
List needs the modifier
.navigationBarTitle(Text(subredditTitle)) to set the title in the navigation view to the value of the
NavigationView needs the modifier
.onAppear(perform: fetchListing) so that the list of posts are fetched when this view appears on screen.
Creating the Web View
Unfortunately, SwiftUI does not provide us with its own version of a web view (yet), but it does provide us with a great protocol for making normal
UIView’s act like SwiftUI views:
This protocol requires us to implement both
makeUIView is a function that requires us to define the type of
UIView that we are creating and then to create it.
updateUIView is called whenever our view needs to update itself in response to changes in the data it presents. In this case, however, it just gets called once, when the view is created, and all it does is load the URL in the
Updating the Scene Delegate
Lastly, to get the whole app working when we press play, we need to update the
SceneDelegate to create all the initial data we need and to pass it into the first
View of the app: the
And that’s it! That is all it takes to create a simple app with SwiftUI and Combine.
It should be clear how much SwiftUI simplifies and reduces the code we need to write for our view and controller layers.
When you utilize the Combine framework as well, the entire codebase becomes even more declarative, succinct, and self-documenting. However, it definitely feels like SwiftUI is still very much in its infancy as there are random bugs that show up the more you use it and try to color outside the lines.
On the bright side, the fact that we can already make real-world working apps with a SwiftUI-first approach makes me incredibly hopeful for future updates to come. I’m hopeful that, in a year or two from now, SwiftUI will be ready for primetime.
To see the entire project, check out the repo on my GitHub.