EventGet 50% off your ticket to MongoDB.local NYC on May 2. Use code Web50!Learn more >>
MongoDB Developer
Realm
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Productschevron-right
Realmchevron-right

Most Useful iOS 15 SwiftUI Features

Andrew Morgan13 min read • Published Sep 27, 2021 • Updated Sep 01, 2022
iOSRealmSwift
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty

Introduction

I'm all-in on using SwiftUI to build iOS apps. I find it so much simpler than wrangling with storyboards and UIKit. Unfortunately, there are still occasions when SwiftUI doesn't let you do what you need—forcing you to break out into UIKit.
That's why I always focus on Apple's SwiftUI enhancements at each year's WWDC. And, each year I'm rewarded with a few more enhancements that make SwiftUI more powerful and easy to work with. For example, iOS14 made it much easier to work with Apple Maps.
WWDC 2021 was no exception, introducing a raft of SwiftUI enhancements that were coming in iOS 15/ SwiftUI 3 / Xcode 13. As iOS 15 has now been released, it feels like a good time to cover the features that I've found the most useful.
I've revisited some of my existing iOS apps to see how I could exploit the new iOS 15 SwiftUI features to improve the user experience and/or simplify my code base. This article steps through the features I found most interesting/useful, and how I tested them out on my apps. These are the apps/branches that I worked with:

Prerequisites

  • Xcode 13
  • iOS 15
  • Realm-Cocoa (varies by app, but 10.13.0+ is safe for them all)

Lists

SwiftUI Lists are pretty critical to data-based apps. I use Lists in almost every iOS app I build, typically to represent objects stored in Realm. That's why I always go there first when seeing what's new.

Custom Swipe Options

We've all used mobile apps where you swipe an item to the left for one action, and to the right for another. SwiftUI had a glaring omission—the only supported action was to swipe left to delete an item.
This was a massive pain.
This limitation meant that my task-tracker-swiftui app had a cumbersome UI. You had to click on a task to expose a sheet that let you click on your preferred action.
With iOS 15, I can replace that popup sheet with swipe actions:
iOS app showing that action buttons are revealed when swiping a list item to the left or right
The swipe actions are implemented in TasksView:
The role of the delete button is set to .destructive which automatically sets the color to red.
For the other actions, I created custom buttons. For example, this is the code for CompleteButton:

Searchable Lists

When you're presented with a long list of options, it helps the user if you offer a way to filter the results.
RCurrency lets the user choose between 150 different currencies. Forcing the user to scroll through the whole list wouldn't make for a good experience. A search bar lets them quickly jump to the items they care about:
Animation showing currencies being filtered as a user types into the search box
The selection of the currency is implemented in the SymbolPickerView view.
The view includes a state variable to store the searchText (the characters that the user has typed) and a searchResults computed value that uses it to filter the full list of symbols:
The List then loops over those searchResults. We add the .searchable modifier to add the search bar, and bind it to the searchText state variable:
This is the full view:

Pull to Refresh

We've all used this feature in iOS apps. You're impatiently waiting on an important email, and so you drag your thumb down the page to get the app to check the server.
This feature isn't always helpful for apps that use Realm and Atlas Device Sync. When the Atlas cloud data changes, the local realm is updated, and your SwiftUI view automatically refreshes to show the new data.
However, the feature is useful for the RCurrency app. I can use it to refresh all of the locally-stored exchange rates with fresh data from the API:
Animation showing currencies being refreshed when the screen is dragged dowm
We allow the user to trigger the refresh by adding a .refreshable modifier and action (refreshAll) to the list of currencies in CurrencyListContainerView:
In that code snippet, you can see that I added the .listRowSeparator(.hidden) modifier to the List. This is another iOS 15 feature that hides the line that would otherwise be displayed between each List item. Not a big feature, but every little bit helps in letting us use native SwiftUI to get the exact design we want.

Text

Markdown

I'm a big fan of Markdown. Markdown lets you write formatted text (including tables, links, and images) without taking your hands off the keyboard. I added this post to our CMS in markdown.
iOS 15 allows you to render markdown text within a Text view. If you pass a literal link to a Text view, then it's automatically rendered correctly:
Text formatted. Included bold, italics and a link
But, it doesn't work out of the box for string constants or variables (e.g., data read from Realm):
Raw Markdown source code, rather than rendered text
The issue is that the version of Text that renders markdown expects to be passed an AttributedString. I created this simple Markdown view to handle this for us:
I updated the ChatBubbleView in RChat to use the Markdown view:
RChat now supports markdown in user messages:
Animation showing that Markdown source is converted to formated text in the RChat app

Dates

We all know that working with dates can be a pain. At least in iOS 15 we get some nice new functionality to control how we display dates and times. We use the new Date.formatted syntax.
In RChat, I want the date/time information included in a chat bubble to depend on how recently the message was sent. If a message was sent less than a minute ago, then I care about the time to the nearest second. If it were sent a day ago, then I want to see the day of the week plus the hour and minutes. And so on.
I created a TextDate view to perform this conditional formatting:
This preview code lets me test it's working in the Xcode Canvas preview:
Screen capture of dates rendered in various formatt
We can then use TextDate in RChat's ChatBubbleView to add context-sensitive date and time information:
Screen capture of properly formatted dates against each chat message in the RChat app

Keyboards

Customizing keyboards and form input was a real pain in the early days of SwiftUI—take a look at the work we did for the WildAid O-FISH app if you don't believe me. Thankfully, iOS 15 has shown some love in this area. There are a couple of features that I could see an immediate use for...

Submit Labels

It's now trivial to rename the on-screen keyboard's "return" key. It sounds trivial, but it can give the user a big hint about what will happen if they press it.
To rename the return key, add a .submitLabel modifier to the input field. You pass the modifier one of these values:
  • done
  • go
  • send
  • join
  • route
  • search
  • return
  • next
  • continue
I decided to use these labels to improve the login flow for the LiveTutorial2021 app. In LoginView, I added a submitLabel to both the "email address" and "password" TextFields:
Screen capture showing that the "return" key is replaced with "next" when editing the email/username field
Screen capture showing that the "return" key is replaced with "go" when editing the password field
Note the .onSubmit(userAction) modifier on the password field. If the user taps "go" (or hits return on an external keyboard), then the userAction function is called. userAction either registers or logs in the user, depending on whether "Register new user” is checked.

Focus

It can be tedious to have to click between different fields on a form. iOS 15 makes it simple to automate that shifting focus.
Sticking with LiveTutorial2021, I want the "email address" field to be selected when the view opens. When the user types their address and hits "next", focus should move to the "password" field. When the user taps "go," the app logs them in.
You can use the new FocusState SwiftUI property wrapper to create variables to represent the placement of focus in the view. It can be a boolean to flag whether the associated field is in focus. In our login view, we have two fields that we need to switch focus between and so we use the enum option instead.
In LoginView, I define the Field enumeration type to represent whether the username (email address) or password is in focus. I then create the focussedField @FocusState variable to store the value using the Field type:
I use the .focussed modifier to bind focussedField to the two fields:
It's a two-way binding. If the user selects the email field, then focussedField is set to .username. If the code sets focussedField to .password, then focus switches to the password field.
This next step feels like a hack, but I've not found a better solution yet. When the view is loaded, the code waits half a second before setting focus to the username field. Without the delay, the focus isn't set:
The final step is to shift focus to the password field when the user hits the "next" key in the username field:
This is the complete body from LoginView:

Buttons

Formatting

Previously, I've created custom SwiftUI views to make buttons look like…. buttons.
Things get simpler in iOS 15.
In LoginView, I added two new modifiers to my register/login button:
Before making this change, I experimented with other button styles:
Xcode. Showing button source code and the associated previews

Confirmation

It's very easy to accidentally tap the "Logout" button, and so I wanted to add this confirmation dialog:
Dialog for the user to confirm that they wish to log out
Again, iOS 15 makes this simple.
This is the modified version of the LogoutButton view:
These are the changes I made:
  • Added a new state variable (isConfirming)
  • Changed the logout button's action from calling the logout function to setting isConfirming to true
  • Added the confirmationDialog modifier to the button, providing three things:
    • The dialog title (I didn't override the titleVisibility option and so the system decides whether this should be shown)
    • A binding to isConfirming that controls whether the dialog is shown or not
    • A view containing the contents of the dialog:
      • A button to logout the user
      • A cancel button

Material

I'm no designer, and this is blurring the edges of what changes I consider worth adding.
The RChat app may have to wait a moment while the backend MongoDB Atlas App Services application confirms that the user has been authenticated and logged in. I superimpose a progress view while that's happening:
A semi-transparrent overlay to indicate that the apps is working on something
To make it look a bit more professional, I can update OpaqueProgressView to use Material to blur the content that's behind the overlay. To get this effect, I update the background modifier for the VStack:
The result looks like this:
A semi-transparrent overlay, with the background blurred, to indicate that the apps is working on something

Developer Tools

Finally, there are a couple of enhancements that are helpful during your development phase.

Landscape Previews

I'm a big fan of Xcode's "Canvas" previews. Previews let you see what your view will look like. Previews update in more or less real time as you make code changes. You can even display multiple previews at once for example:
  • For different devices: .previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Max"))
  • For dark mode: .preferredColorScheme(.dark)
A glaring omission was that there was no way to preview landscape mode. That's fixed in iOS 15 with the addition of the .previewInterfaceOrientation modifier.
For example, this code will show two devices in the preview. The first will be in portrait mode. The second will be in landscape and dark mode:
Animation of Xcode preview. Shows that the preview updates in real time as the code is changed. There are previews for both landscape and portrait modes

Self._printChanges

SwiftUI is very smart at automatically refreshing views when associated state changes. But sometimes, it can be hard to figure out exactly why a view is or isn't being updated.
iOS 15 adds a way to print out what pieces of state data have triggered each refresh for a view. Simply call Self._printChanges() from the body of your view. For example, I updated ContentView for the LiveChat app:
If I log in and check the Xcode console, I can see that it's the update to username that triggered the refresh (rather than app.currentUser):
There can be a lot of these messages, and so remember to turn them off before going into production.

Conclusion

SwiftUI is developing at pace. With each iOS release, there is less and less reason to not use it for all/some of your mobile app.
This post describes how to use some of the iOS 15 SwiftUI features that caught my attention. I focussed on the features that I could see would instantly benefit my most recent mobile apps. In this article, I've shown how those apps could be updated to use these features.
There are lots of features that I didn't include here. A couple of notable omissions are:
  • AsyncImage is going to make it far easier to work with images that are stored in the cloud. I didn't need it for any of my current apps, but I've no doubt that I'll be using it in a project soon.
  • The task view modifier is going to have a significant effect on how people run asynchronous code when a view is loaded. I plan to cover this in a future article that takes a more general look at how to handle concurrency with Realm.
  • Adding a toolbar to your keyboards (e.g., to let the user switch between input fields).
If you have any questions or comments on this post (or anything else Realm-related), then please raise them on our community forum. To keep up with the latest Realm news, follow @realm on Twitter.

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Article

Lessons Learned from Building a Game with MongoDB and Unity


May 13, 2022 | 4 min read
Article

Advanced Data Modeling with Realm .NET


Oct 19, 2022 | 7 min read
Tutorial

Create a Custom Data Enabled API in MongoDB Atlas in 10 Minutes or Less


Sep 23, 2022 | 8 min read
Article

SwiftUI Best Practices with Realm


Oct 19, 2022 | 33 min read
Table of Contents