How to Create a Neumorphic Design With SwiftUI
In this neumorphic design tutorial, you’ll learn how to use SwiftUI’s powerful modifiers to craft beautiful custom elements. By Yono Mittlefehldt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Create a Neumorphic Design With SwiftUI
35 mins
- Getting Started
- Introducing Linear Gradient
- Customizing Your First Element
- Including the Image Gradient
- Adding Highlight and Shadow
- Changing the Text Gradient
- Rounding the Corners
- Making That Border Pop
- Changing the Border Shape
- Troubleshooting AccessoryView
- Introducing Built-in Modifiers
- Discovering Inverse Masks
- Tackling Tab Bar Buttons
- Designing the Unselected Tab Bar Button
- Adding Unselected Tab Bar Button Effects
- Designing the Select Tab Bar Button
- Incorporating the Button Symbol
- Tidying up the Details
- Crafting the Progress Bar
- Modifying the Second Capsule
- Deepening the Progress Bar
- Navigating the Navigation Bar
- Where to Go From Here?
Don’t deny it. You feel the longing, that absence to make hearts grow fonder. You’re heart’s been fond to near bursting since the release of iOS 7. You miss your dear skeuomorphism.
When you look at your screens today, they look so flat and boring. Then again, when you look at those pre-iOS 7 designs, they’re old and outdated. If only there was a skeuomorphic design language that looked new and fresh.
You’ve come to the right place!
In late 2019, a tweet made the rounds that included designs from Alexandar Plyuto. And they are fabulous:
In this tutorial, you’ll learn how to recreate some of these design elements in SwiftUI. You will discover how to:
- Use linear gradients to give depth to your views.
- Create complex effects by combining simple ones such as shadows.
- Master the art of the inverse mask, an effect that does not exist natively in SwiftUI.
Get ready for a whole lot of design fun!
Getting Started
Smart homes are all the rage these days. Not to be outdone, super villains and mad scientists are now super into smart lairs. Hey, you don’t build your HQ in an active caldera without wiring some mad tech throughout.
Here’s the little-known secret: The app super villains use to control their lairs — called SmartLair — is as flat and boring as the Home app on your iPhone. Maybe more so.
In this exercise, you’ve retrieved a copy of SmartLair’s source code through illicit back channels. You’ve been threatened, erm, hired to snazz it up.
To get started, click the Download Materials button at the top or bottom of this tutorial. Open the begin project and explore its contents.
Remember: This is a big opportunity for you. If the villains like your work, it could lead to more contracts in the future. But if they don’t, well, laser sharks aren’t known to be picky eaters.
Introducing Linear Gradient
Before you begin, you need to familiarize yourself with LinearGradient
. Skeuomorphic design leans heavily on linear gradients. They’re kind of a big deal.
In SwiftUI, you define a linear gradient like this:
LinearGradient(
gradient: Gradient(colors: [.white, .lairLightGray]),
startPoint: UnitPoint(x: 0.2, y: 0.2),
endPoint: .bottomTrailing
)
If used as a view to cover the whole screen, it looks like this:
Here, you define that the gradient will go from white
to lairLightGray
, a Color
you will add to the project later. You can have more than two colors if you want the gradient to pass through several:
startPoint
and endPoint
are coordinates relative to the unit square, which has a coordinate of (0, 0) in the top left and (1, 1) in the bottom right. However, they aren’t required to be within this range. For instance, a negative coordinate value would start the gradient outside of the view.
In the code above, there are also some predefined constants for typical start and end points, such as .leading
, .trailing
, .top
, .bottom
and combinations of those.
Customizing Your First Element
Start by attacking the boring looking AccessoryView
. It represents the large rectangles in the middle of the screen with labels such as Control Room Lights and Dungeon.
Drag the Extensions folder from the downloaded materials to your Xcode project. Place it above the Views group. Make sure Copy items if needed and Create groups are selected, then click Finish.
These three files define some UIColor
constants, the SwiftUI Color
equivalents, and some LinearGradient
definitions. You already saw how to create a LinearGradient
, but skeuomorphic design uses a lot of gradients. It takes too long to go through them one by one, and super villains aren’t the patient type, so you’ll get straight to it.
Including the Image Gradient
In AccessoryView.swift under the definition for body
, find the line that begins with image
in the VStack
. Replace that line with the code below, but don’t remove the modifiers frame
, padding
and font
that are there:
LinearGradient.lairHorizontalDark
.mask(image.resizable().scaledToFit())
You just turned the SFSymbol image into a mask for the gradient. The layer with the gradient will be cut out in the shape of the opaque pixels from the image. Cool! You can build and run to see the changes, or turn on the previewing canvas in Xcode to see changes immediately:
Adding Highlight and Shadow
Add this code after font
:
// 1
.shadow(color: .white, radius: 2, x: -3, y: -3)
// 2
.shadow(color: .lairShadowGray, radius: 2, x: 3, y: 3)
With these two lines, you have:
- Added a white shadow that is offset relative to the top left of the image.
- Added a dark shadow that is offset relative to the bottom right.
This contrast provides the illusion of depth in all directions. You can use this shadow trick to get a raised effect.
The code for the element with all modifiers should now look like this:
LinearGradient.lairHorizontalDark
.mask(image.resizable().scaledToFit())
.frame(width: 150, height: 236)
.padding(40)
.font(.system(size: 150, weight: .thin))
.shadow(color: .white, radius: 2, x: -3, y: -3)
.shadow(color: .lairShadowGray, radius: 2, x: 3, y: 3)
Changing the Text Gradient
The text clearly can’t stay black. Add the following modifier to Text
within the HStack
:
.foregroundColor(.lairDarkGray)
Now, your text is an attractive shade of lair dark gray.
The complete HStack
should look like this now:
HStack {
Text(title)
.foregroundColor(.lairDarkGray)
.bold()
.padding(.leading)
.padding(.bottom)
Spacer()
}
Note that if you put foregroundColor
in a different location among Text
, it still works. You can order some modifiers any which way, but as you’ll see, the order matters for others.
Rounding the Corners
Your next step is to round off the border corners. It’s not as straightforward as it sounds, though.
For example, try something like this:
.border(Color.gray, width: 1)
.cornerRadius(15)
And you see that the corners are cut off.
Swap the two modifiers like so:
.cornerRadius(15)
.border(Color.gray, width: 1)
And you see that the borders maintain sharp corners.
Fortunately, there is a workaround. You can use an overlay to obtain those sweet, sweet curved borders.
Delete border
and, if you added it, cornerRadius
. Replace them with:
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(LinearGradient.lairDiagonalDarkBorder, lineWidth: 2)
)
This code lays another view over your view — i.e., an overlay — that draws a rounded rectangle with the desired corner radius.
For structs conforming to Shape
, such as RoundedRectangle
or Path
, use stroke
instead of border
to draw a line around it.
With this change, you also add a gradient to the border by stroking it with LinearGradient.lairDiagonalDarkBorder
instead of Color.gray
. This addition gives the border a bright highlight in the top left of the element and a darker shadow in the bottom right. Simultaneously, you make the border heavier by increasing the width of the border/stroke to “2.”
You may notice the top and bottom borders are thinner than the left and right. That’s because the view has no vertical padding and clips half of the stroke. No worries. You’ll fix this in a bit.