Day 15: SwiftUI Custom Modal

100 Days of SwiftUI

Brady Murphy
7 min readNov 29, 2019

Today is going to be short and sweet because it’s Thanksgiving and I have a lot of food to eat and family to be with. So, sit back, pull out an Xcode project, and let’s get to coding

Why custom modal’s Brady?

So you’re probably reading this and wondering:

Why do we need custom modals if we have sheets?

The reason being is that in UX/UI design, we don’t always want to have the sheet coming over the rest of the view and blocking everything. Sometimes we want elegance and control over the transition and how large the modal is. With a sheet, we can’t control the size to a great extent nor the transition of the sheet. It kinda just slides on into the UI and slides right out like people in their DM’s…

Alright, alright, all jokes aside sheets are great but it’s always great to know how to do something custom for the design of your project. Not only will it help your app stand-out, but it could also give you some claps on your app like you’re going to do for me at the end of this article 😉. Also, talk about the satisfaction of knowing that you can build custom components! All of our friends are out here living in 2020 when we’re out here in 3000.

Creating THE Modal

Final Modal Design

First things first, let’s think of how we’re going to be building this modal. We need to think in terms of stacks (HStack, ZStack, and VStack).

To create the depth that we need for the background and the content we will be using a ZStack. Then, we need to place the content of the modal above the background. Since the content could be very long, we want to place the content in a ScrollView to allow the user to scroll through the information on the modal. Lastly, we want to place the close button above that. By conforming to that design, we’re able to get the modal depicted to the left!

Generic Modal

Since we’re creating a custom modal, we want to be able to reuse this component again with different content than before. This means that we need to create the modal using generics to ensure that the content being placed in the scroll view can adapt to any view being placed in the modal. This is just like creating a generic data structure except for the data type that always implements View.

The reason we ensure that the data type always implements the View protocol is to ensure that it can be rendered in our modal. We don’t want someone using this component and blindly placing a String instead of a view as the content for the modal. Consequently, the content is restricted to types that implement the View protocol.

Background

To create the background of the modal, we simply start with a ZStack. This will give us the depth of the view that we talked about previously. Next, our background layer will start with a RoundedRectangle with corner radius of 30 (this value can be changed at your discretion) and modifying it to have a white foreground.

We then want to make the modal stand out from the actual background of our application. To do this, we will start by giving the modal an edge. To give the modal an “edge”, we want to overlay this with another RoundedRectangle using the stroke modifier this time. Since we are using a stroke, it will “paint” the outside of the view with the defined color and the given line width. Lastly, to give it some depth and to make it 💥POP💥 we will add a shadow to the original rectangle.

The code in the ZStack will then look something like this:

ZStack {RoundedRectangle(cornderRadius: 30)
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornderRadius: 30)
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
)
.shadow(color: Color.gray.opacity(0.4), radius: 4)
}
.padding(50) // this padding makes the modal come off the edges

Content

This is by far the easiest part of this whole thing. Following the background of the modal in the ZStack, add a ScrollView. This component will allow our views to scroll and give you all the landscape you could ask for. Now within that scroll view, we want to place the modals’ content.

Wait, is this where we make the view generic?

YES. You hit it right on the head. Go to the struct definition for the Modal struct, and update the code to the following:

struct Modal<Content: View> : View

What we’re doing here is saying, within this closure, we’re going to use the generic called Content to represent a type that implements the protocol View. This will then allow us to place any view within the content of the modal!

Next, make one of the Modal’s attributes be a constant called content by placing this code within the definition of Modal:

let content: Content

This will then make an init parameter called content for the Modal struct. When we create an instance of the Modal, we will then have to provide the content as well through the init parameter:

Modal(content: Text("Brady has the greatest content"))

Since we made content be a generic type, any view can be placed in the initializer!

Lastly, add a vertical padding modifier to the ScrollView. This will give the content some room to breathe and will make the view look much more professional. All in all, we simply added this to the ZStack:

ScrollView {
content
}.padding(.vertical, 40)

Close Button

We need to provide the user some way to close the modal. For simplicity of this article, I’m going to just add a button that will update the state of my view and tell the modal to close.

To do this, we need to do two critical steps:

  1. Create a Binding boolean variable within our modal
  2. Have the button update the variable when clicked

Creating the Button’s View

To create a simple button, we first create the generic Button View:

Button(action: {
// action here
}) {
// actual view of button here
}

I want my button to have text, so I will be using a Text view within the view of the button. I then modify the text to have a white color and headline font style. To get the text to have a background, we first want to add padding to the Text view. This will allow the view to take up space within the main view which will give us a playground for styling the body of the button.

Next, we’re going to add the background to the button. To do this, use the background modifier and then specify the color of your button. I chose mine to be blue 🌊 but you can choose some other weird color like unicorn pink if you want.

Last but not least, we want to make the button curvy. I’m sure you’re looking at your screen right now thinking this rectangle looks absolutely AWFUL. Well, I’m right here with ya and I’m here to save the day. Simply add the cornerRadius modifier to the view and specify how curvy you want your button to be!

Button(action: {
// action here
}) {
Text("Close Modal")
.foregroundColor(.white)
.font(.headline)
.padding()
.background(Color.blue)
.cornerRadius(26)
.padding(50) // extra padding from the bottom
.shadow(color: Color.gray.opacity(0.4), radius: 8) // pop
}

Buttons’ Action

Like we said before, we need to add a binding variable to the modal. To do this, add the following code to bellow the content constant we created earlier.

@Binding var showModal: Bool

This article isn’t on the Binding property wrapper, but I will give a brief overview of what it does. Essentially, it acquires a reference to the parameter being passed in for showModal when the Modal view is created. Since we’re acquiring a reference and not a copy of the variable, changes to the value of the variable will be seen all throughout the program. This is useful for variables that update the state because it will allow us to update the state within another view! (i.e. @State variables)

Next, we will simply update the value of showModal when the button is clicked:

Button(action: {
self.showModal.toggle()
}...

Voila! The button is complete!

Placing the button in the Modal

Lastly, we need to add the button to the Modal’s view. Since I want the button to be hovering at the bottom of the Modal, I’m going to place the button is a VStack and then use a spacer to fill in the rest of the vertical space and force the button to the bottom of the view.

Nous avons fini!

Great! We’ve completed the custom modal that was shown to you at the beginning of this article. We’ve gone over design decisions, UX/UI, and many other topics that interweave between those! I hoped you enjoyed this article and learned a little bit about SwiftUI today!

Cheers 🍺

Brady

ps. the gist is attached below 🍺

--

--

Brady Murphy

Man of smiles and knowledge | iOS enthusiast bringing my ideas to life | Brady = { favTeam: “Capitals”, school: “VT” } MY BLOG: www.unwrappedbytes.com