Fast app prototyping with SwiftUI

Carlos Corrêa
9 min readSep 26, 2019

I remember my first days working with iOS development. Playing around with the UI was quite simple and straightforward because, at that time, we had only one iPhone and one iPad screen sizes. Nowadays you have different screen size for the iPhone/iPad, safe areas, split-screen on the iPad, watchOS, tvOS, etc. So how are you gonna prototype apps as fast as we did in the past?

Well, luckily things have changed! We got the constraints system along with the Storyboards, making it less painful to adhere to all of the different screen sizes (and also giving people some headache).

Then we evolved again (kinda going back to the early days of iOS) to build UI fully with code, you may have read/hear somewhere the term "View Code".

My journey on iOS development comes a long way (since 2011 to be precise) and I must admit that, in all these years, I spent one working with React Native (first try it then you hate it or not) and I remember liking to develop / prototype UI for a couple of reasons:

  1. It was way faster compared to native iOS development. Mostly because of the declarative UI system and fast rendering with features like Hot Reloading.
  2. The possibility to easily break down a complex screen into small components and then shrink them even more and so on.
  3. Making these small components immutable, just presenting the data they were supposed to present, just bubbling up the user's actions for something to handle it, etc.

The day Apple announced SwiftUI I got goosebumps because I had the impression that developing UI for native iOS development was going to be exactly how I dreamed about since the React Native era:

Simple, Fast and Declarative.

One of these days, after releasing my previous article on making the 2048 game with SwiftUI and Combine, I was having dinner with my wife and I asked her for an idea of a simple app to play around with SwiftUI on the Apple Watch (because I remember it was not very flexible and quite hard to come up with a nice app interface the last time I wrote a watchOS app) and she said "Do a simple drink water tracker app, maybe you can add some cool animations as well". The challenge was accepted.

It was like 8 pm and I had until midnight (we do a time-box for our studies at home) to finish up this app.

How to read this article

It is interesting to read along with the code that you can find here:
https://github.com/caiobzen/water-reminder-swiftui

By cloning the repo, you can easily fiddle around with the various components I will mention throughout the article.

Of course, I will leave a couple of gists so you can copy and paste it in the XCode Playground to have a glimpse as well.

The "Waterminder" App ⌚💧

As I mentioned in the 2048 game article, I always break down the work I'm about to do in a simple list of things to accomplish. So in order to figure out the amount of work needed to finish up this simple app, this is the list of things that came up in my mind:

  1. How to make it look like you’re filling up the app with water (they are water-resistant, aren’t they? 😜)
  2. Figure out how does the Digital Crown works with SwiftUI
  3. Work on the UI and experimenting how to take advantage of the new SF Symbols to enrich the appearance of the app
  4. Figure out how custom Views works in-depth and create my own wrappers

With the list in hands, let's get started!

#1 Filling up the app with water!

The "Wavy" background animation

I must admit, I didn’t know exactly where to start from, so I did a quick research on "how to draw a curved shape" and I found this Stack Overflow post. All I had to do was to understand what the code does and convert it into a SwiftUI View. I named it Wave.

Code that creates a View with a wave shape at the top
This is how the wave view looks like.

Once I had the waved shape view, my plan for “how to make it feel like it has a wave motion” was: Placing two of those views one in front of another with a little animation on the Y and X-axis. Again, no rocket science here, I was just testing out how to put things together and seeing if WatchOS could handle it.

So I wrapped two of these wavy views in a ZStack so one could be in front of another, making one of the waves a bit darker like there was some distance between the two waves.

By applying an easeInOut animation repeating forever, changing just a bit of the Y and X axis, I had the basic “look and feel” done.

How to implement a "repeat forever" animation

So how did I manage to make it fill up the screen with water? That’s simple! I just wrapped everything in a VStack, added a view below the ZStack that wraps the waves, and increased its height pushing the waves to stack up according to the amount of “water” filled in. I did a super drawing to illustrate the concept:

Super complex wave system (just kidding, it is very simple)

This is how it looks like when filling it up:

The animation and the water filling up

Once I had the "Background" of my application, next step was to create a way to add the amount of water drank, so we could update the background view to dynamically increase the water level.

#2 Digital Crown and SwiftUI

Obviously, one should tell the amount of water drank, so I made the "Add Button" value customizable. By rotating the Digital Crown, the user would be able to tell how much water is being drunk.

The way Digital Crown works along with SwiftUI is quite simple. All you need to do is:

  1. Have some binding property (in this case, the “amount of water to drink")
  2. Use such property as you wish (in this case, it is the text of the "add button")
  3. Tell the parent view that it is “focusable” (just add the .focusable() modifier)
  4. Bind the property to the digital crown rotation (using the dollar sign on your property):
A silly example of how to configure the digitalCrownRotation

As you can see, the first parameter here is our binding value "$viewModel.drinkingAmount", all the other properties can be customized as well, like the range of such value passing "from" and "through" (like from 100 to 1000) ,"by" which stands for the amount that will be increased while you rotate the Digital Crown (for example: always increment by 50 would be 100 to 150 to 200 etc), and the sensitivity which goes from low to high and it represents how sensible the rotation will increase or decrease the value that you want to update.

After managing how to use the Digital Crown to update the drinking amount dynamically, I moved to the UI side of things. I wanted to see how do SF Symbols work and where to find them!

#3 SF Symbols

In case you don't know what SF Symbols are, this is the definition according to Apple:

SF Symbols provides a set of over 1,500 consistent, highly configurable symbols you can use in your app. Apple designed SF Symbols to integrate seamlessly with the San Francisco system font, so the symbols automatically ensure optical vertical alignment with text for all weights and sizes. SF Symbols are available in a wide range of weights and scales to help you create adaptable designs.

In order to use those symbols, all you need to do is to know their names!

Image(systemName: "paperplane")
One of the 1500 SF Symbols

It took me a while to figure out that there's an app for you to browse the whole catalog of symbols. There's a link to the pkg file kinda hidden in one of the new Apple layout guides and I will do this favor to you and paste it below 😄.

You can download it here (Not a virus 🦠)

This is how the SF Symbols app looks like

With the SF Symbols app in hands, all you need to do is to browse through the catalog, grab the name of the symbol you want and use it as you wish! (there are a couple of icons that you're not supposed to use outside their scope: airplay should always refer to Airplay, for example).

So, for the Waterminder app, I decided to use the plus.circle.fill in the "add button".

Using SF Fonts example

It's sad that Xcode doesn't suggest these icon names for you as you type, so that's why this app catalog helped me a lot when I had to pick one of these images and I hope it helps you as well!

Last but not least, I was playing around with contextMenus when I found something interesting called ViewBuilder.

#4 @ViewBuilder

If you look at the definition of the contextMenu function,(used to create that classic watchOS force touch menus), this is what you are going to see:

public func contextMenu<MenuItems>(@ViewBuilder menuItems: () -> MenuItems) -> some View where MenuItems : View

Now if you're asking what a @ViewBuilder can be, here's the definition:

Builds an empty view from a block containing no statements, `{ }`.

You typically use ViewBuilder as a parameter attribute for child view-producing closure parameters, allowing those closures to provide multiple child views.

What it means is that you can use arguments marked as @ViewBuilder to represent the content that you want to render inside your View.

Once I realized that, I changed the WavingBackground to pass in the constructor what I wanted to render within the view:

How to use @ViewBuilder to render custom content within your View

By doing this, I moved from wrapping the views in a ZStack:

The initial usage of the WavingBackground View

To wrapping the content I wanted to display within the WavingBackground itself:

How I ended up using the WavingBackground View: Wrapping the content I wanted to display

Quite nice huh? By using this approach, @ViewBuilder allows us to implement lots of custom wrapper views for our projects. Obviously, this is a very simple example and you can go way further if you wish!

Wrapping up

Oh, that was such a wavy reading! I really appreciate if you got this far! So let's wrap up and summarize what we learned in this article:

  1. SwiftUI works very well on the watchOS. It was as fast as developing UI using the other frameworks as I mentioned at the beginning of this article. Animations also work very smoothly and I'm impressed with how easy It was to put the pieces together and end up with a nice app prototype!
  2. It is very intuitive to build an interaction with the Digital Crown, which promotes a very common way for users to interact with the Apple Watch apps. Just make your component focusable and bind the Digital Crown to your property. Boom! Done!
  3. Take advantage of the new SF Symbols. They are beautiful and well-structured. Don't forget to download the SF Symbols app so you can browse and get the glyph names! You won't regret it!
  4. Remember that you can go even further when creating your custom views by wrapping your content using the @ViewBuilder parameter attribute. Sometimes it makes more sense comparing to wrapping stuff inside Stacks!

Once again thank you for your patience and I hope you can give me some feedback so I can always improve and learning together with y'all!

❤️ Have a good one ❤️

A special thanks to:

and for the feedback! ❤️

--

--