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 UITableView, implement UITableViewDataSource, and UITableViewDelegate, add a UITextField, implement UITextFieldDelegate, and then segue to either an SFSafariViewController or a view controller with an embedded WKWebView.

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:

  1. One row to represent each Reddit post.
  2. Another to show the entire list of posts, as well as the search text field at the top.
  3. 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 Listing.

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.

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

The listing model

The Listing type is very straightforward. It basically just holds onto the list of Posts.

Creating the Service Layer

The RedditService

Like the models, this part of the code works for both a UIKit and a SwiftUI version of this app.

The RedditService

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.

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

The 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).

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

Inside the 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 @ObservedObject.

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: query and 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.

Inside our NavigationView, we have a List. Lists give us the ability to create vertically scrollable lists of any combination of Views.

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.

Below the TextField, we iterate through the array of posts in our view model, and create a PostRow for each one and wrap it inside a NavigationLink.

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

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 subredditTitle property.

Second, 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: UIViewRepresentable.

This protocol requires us to implement both makeUIView and updateUIView.

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

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

And that’s it! That is all it takes to create a simple app with SwiftUI and Combine.

Conclusion

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.