SwiftUI: Meme Creator

Brandon Baars
Oct 27 · 7 min read

Using SwiftUI, create memes from a blank template

This is what we will accomplish in this tutorial

Using a public API from https://imgflip.com/api, we will pull popular meme templates and give the user the ability to enter in their desired text and adjust as necessary.

We will use Combine to fetch the results, SwiftUI to display the information, and a few custom views for the user to edit it accordingly.

We won’t cover saving the image in this tutorial, but you could still just screenshot and crop it to fit your needs.

Getting Started

Create a new SwiftUI Project in Xcode. Make sure you’re running macOS Catalina and have Xcode 12 installed as we will be using parts of the iOS API that are only available in iOS 14.

Open Xcode → File → New → Project

I called mine MemeGenerator, but feel free to name it whatever you’d like.

Make sure you have User Interface set to SwiftUI.

Image for post
Image for post

Implementation

Let’s get started with creating our Models

For this, let’s look to the API and see what endpoint we need to request from, as well as what data is returned.

We will model our Swift objects after what is returned by the API.

Create a new Group and call it Models . Right-click on the newly created group and add a new Swift File and call it Meme.swift .

Looking at the API Documentation, the following JSON is returned from the GET request:

URL: https://api.imgflip.com/get_memes

{
"success": true,
"data": {
"memes": [
{
"id": "61579",
"name": "One Does Not Simply",
"url": "https://i.imgflip.com/1bij.jpg",
"width": 568,
"height": 335,
"box_count": 2
},
{
"id": "101470",
"name": "Ancient Aliens",
"url": "https://i.imgflip.com/26am.jpg",
"width": 500,
"height": 437,
"box_count": 2
}
// probably a lot more memes here..
]
}
}

Our Meme object will hold success and a data object.

Go ahead and add the following code to the Meme.swift file.

We are recreating the JSON structure with our Swift Objects.

The Meme struct holds the data and the success variables that we can see in the JSON object. The Data object holds the array of return information about each Meme. The reason the variables are all optional in the struct Info is that the API states that that data is subject to change. If a variable was removed from the API in the future, it could break our implementation when it’s trying to decode the JSON into our Swift objects (if they weren’t optional, because it would be trying to find the non optional data in the JSON response)

Another thing to note with the Meme.Infois we are adding the Hashable protocol here because we will be looping over these Memes later on to create a List and it makes it easier to do that.

Now that we have that out of the way, let’s work on our network request.

We will be using Combine for it as it’s pretty minimal code to do so and handles the Fetching, Decoding, and Errors all in one. (I say that loosely)

We will be adding another extension to Meme, but for this let’s create a new file and call it Meme+Fetcher.swift

This will house our Combine fetching code but still be contained within the Meme object to provide some context.

Add the following code to the newly created file:

We create a Fetcher class. This will house all our logic for fetching the memes. We also have a custom Error class that will be for any errors occurred while requesting the Meme stuff.

Our Fetcher class has the one function that will just return a publisher that we can use in another class to pull the response data off of.

Let’s create our MemeListView now that will fetch and display the memes.

Create a new Group and name it MemeView . Drag the ContentView.swift file into that newly created group and rename the ContentView.swiftto be MemeListView.swift . Go ahead and update all the usages of ContentView to be MemeListView.

At this point, we have created our Models and our Fetching class. Now we will create our View that will utilize what we have created thus far.

In MemeListView.swift , add the following code:

Very basic, but we create a view model (we will create this in just a second)

We have a navigation view that we will use later on to push to a new view when the user selects a row.

We use List(viewModel.memes, id:\.self) to loop over each ‘meme’ in our list of memes. The id: \.self is something we can use because we inherited from our Hashable protocol in Meme.Info, thus allowing each object to be separately hashed and able to be used this way. (Kind of a unique identifier)

SideNote: We could have inherited fromIdentifiable instead of Hashable and changed our List to render like this: List(viewModel.memes, id: \.id) and it would have worked all the same.

Let’s create our View Model now:

Create a new file MemeListView+ViewModel.self , and add the following code:

Our view model handles fetching the memes, and holds the data needed for the View to render / update the changes.

At this point, the only thing that will happen is the app will fetch our memes when the app launches.

Let’s take care of creating the individual list item views that will display each meme.

First, let’s create some fake static data, add the following code to the Meme.swift

extension Meme.Info {static var fakeMeme = Meme.Info(
id: "1",
name: "one does not simply",
url: "https://i.imgflip.com/1bij.jpg",
width: 200,
height: 300,
boxCount: 2
)
}

We will use this to construct our UI for the list item view.

Go ahead and create a new file and call it MemeView.swift . This will be what each row in our List view will look like.

Add the following code to the newly created file.

Don’t worry, we’ll create the AsyncImage next.

This essentially is just an image overlaid with text that displays the name of the meme.

Create a new file called ImageLoader.swift and add the following code:

The above code has been taken from the following link:

It’s a very informative article on SwiftUI image loading, as well as how to cache the image.

With the brevity of this tutorial, we only utilized the async loading features.

Now that we can download images, our MemeView should look something like this now:

Image for post
Image for post

Back in, MemeListView.swift , add the following code to update our List to display the MemeView for each item in our list.

var body: some View {NavigationView {
List(viewModel.memes, id: \.self) { meme in
MemeView(meme: meme) -- Newly added line
}
}

If you build and run, you should now see something like the following:

Image for post
Image for post

Handling navigation events and showing the new view

Now that we have the ability to view all the popular memes, let’s work on tapping on one to take us to the next view that allows us to edit the meme template.

To start, we will create our new view. Create a new file and call this one MemeEditorView.swift .

In this view, we will display the image, text fields (based on the number of boxes in our Meme.Info object), and the labels as the user types.

This will just display our meme image again, text fields, and the labels of whatever is typed in the text fields. Those ‘labels’ will just render in the middle of our view whenever the input in our TextFields change.

To actually show this screen now, we need to update our MemeListView one more time and say that for each row in our List, we want to add a navigation link to open this new view with the proper data.

Add the following code back in MemeListView

// Wrap our MemeView in a Navigation Link, where the destination
// is the MemeEditorView
NavigationLink(destination: MemeEditorView(from: meme)) {
MemeView(meme: meme)
}

If you build and run you should see something like the following now when you click into a meme.

Image for post
Image for post

Creating a scaleable & draggable, Text View

Instead of rendering a Text(label), we will create our own Text component that will be able to be scaled, draggable, and tappable.

Create a new SwiftUI view and name it MemeEditorDraggableLabel.swift

This view will essentially just be a Text() , where we will attach gestures that allow it to scale (with a pinch gesture), rotate (with two fingers), as well as move, change color by tapping on it, and change the ‘width’ of the frame with a double tap (for the times you want your text to wrap)

Add the following code to the newly created file.

At this point, this newly created draggable label is not being used in our code.

Back in MemeEditorView.swift , change the line within our ForEach(addedLabels, id: \.self) to be MemeEditorDraggableLabelView(text: label)

ForEach(addedLabels, id: \.self) { label in
MemeEditorDraggableLabel(text: label)
}

Now, if you build and run you should be able to do the following:

Image for post
Image for post

That’s it!

A good look at how you can use Combine for URL session tasks, creating a ListView from our ViewModel, showing a custom view and attaching gestures to make our own memes!

Here the link to the full source code:

Enjoy!

The Startup

Medium's largest active publication, followed by +730K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store