What’s New in SwiftUI 3.0?
Markdown support, new button styling, customizable lists, and more
SwiftUI is Apple’s declarative UI framework. At WWDC 2021, it was once again greeted with exciting new enhancements, features, and some deprecations for our own good.
SwiftUI 3.0 should be available on iOS 15, iPadOS 15, macOS 12, and watchOS 12.
Before we start, it’s worth noting that the Info.plist
file is no longer visible by default in the Xcode 13 project structure. Instead, you’ll have to access it from the Project Navigator tab.
In the next few sections, we’ll look at the new features of SwiftUI for iOS 15 (though most of them will work in some form on the other platforms as well).
Markdown Support and New AttributedString API
Markdown is a common language for writing formatted texts. You’ve likely seen a lot of it in GitHub Readme docs.
Starting with iOS 15, Apple is bringing Markdown support to the Foundation
framework and SwiftUI. So, you can add strings with Markdown syntax in SwiftUI Text
, as shown below:
Text("**Connect** on [Twitter](url_here)!")
Here’s an app in action:

If you’d like to customize a range of characters in the string above, there’s an all-new AttributeString
API for SwiftUI and an enhanced NSAttributedString
for UIKit.
You can pass the AttributeString
above in a SwiftUI Text or customize it using the new AttributeScope
and SwiftUIAttributes
.
Check out Zheng’s article on how to spice up your Attribute Strings in SwiftUI.
New Button Styles
SwiftUI Buttons have gotten a lot more powerful now. We have new roles and styling modifiers.
ButtonRole
lets you describe the kind of button. It accepts:
cancel
destructive
none
some()
Publisher.
Similarly, we now have the ability to style SwiftUI Buttons. By using the buttonStyle
view modifier, you can apply BorderedButtonStyle
, BorderlessButtonStyle
, PlainButtonStyle
, or DefaultButtonStyle
.
Note: These styles can also be replaced with their enum counterparts.
For BorderedButtons
, there’s a new BorderedShape to describe whether you want it as a capsule
, roundedRectangle
, or automatic
.
Here’s a quick look at different styling and role types you can apply to SwiftUI Buttons in iOS 15:

From the code above, it’s evident that a borderless button doesn’t obey tint
color and automatic
style adheres to the current OS system.
Note: Setting a style modifier to the group container (VStack
in our case) would apply it on all the Button controls.
There are two more attributes we didn’t discuss:
controlSize
to choose the Button’s size from a few standard available options.controlProminence
— This defines opacity. No wonder the bottom-most buttons stand out.
Besides a slew of changes in SwiftUI Button control, Apple also exposed a CoreLocationUI
button for SwiftUI known as LocationButton
. It provides a standardized current location UI and helps to quickly fetch location coordinates:
LocationButton(.sendMyCurrentLocation) {// Fetch location with Core Location.}.labelStyle(.titleAndIcon)
SwiftUI AsyncImage for Loading Images From URL
Previously, displaying remote URLs in images required setting up a loader and performing asynchronous tasks. Dealing with different loading states only added to the ever-increasing boilerplate code.
Thankfully, SwiftUI 3.0 brings AsyncImage
to abstract the whole process.
Here’s how it works:
AsyncImage(url: URL(string: <url_here>)!)
You can also customize the output image by accessing the content
argument. Furthermore, you can use the placeholder
argument to set a loading view too.

You can also handle the different states of the result using the AsyncImagePhase
enum from the content
block.
AsyncImage
lets you specify transcation
wherein you can pass an animation.
There’s another scale
parameter that not only lets you control the size but is also handy for zooming in and out. Take a look:

Since AsyncImage
uses URLSession
, it provides out-of-box support for caching (though you cannot write your custom cache as of right now).
SwiftUI TextField Gets Better Keyboard Management
With iOS 15, we have a new property wrapper (@FocusState
) to manage the current active fields programmatically. This is going to be so useful in login and registration forms, as developers can now highlight a specific text field.
To implement programmatic focus on TextField
in SwiftUI, we need to bind FocusState
in the focusedStated
modifier. It does a boolean check to determine if the FocusState
is bound to the same view.
Here’s an example:

Notice the submitLabel
view modifier. This lets us set the keyboard return button with a bunch of other options.
Currently, SwiftUI TextField FocusState
doesn’t work inside Forms
as intended in the first iOS 15 beta.
New onSubmit view modifier and deprecation of onCommit
Since we got better focus management for TextField, it’s no surprise we got a new way of handling results as well. The onCommit
callback for the return key is soft-deprecated and there’s an onSubmit
view modifier. This modifier is meant to handle text field results in a view hierarchy.
Here’s an example:
Note: You can optionally specify the view type using .onSubmit(of:)
. Also, setting a submitScope
to true
ensures that the results of that hierarchy are not returned.
SwiftUI Lists Are Now Searchable
A search feature had been lacking from SwiftUI lists in the previous two iterations. With iOS 15, Apple brings a searchable
modifier to lists.
The key thing to remember is this: To mark a list as searchable
, you need to wrap it inside a NavigationView
.
Here’s a first look at SwiftUI lists with a search view:
There are a few things to consider:
searchable
lets you specify a bunch of search suggestions. Tapping on them will display thesearchCompletion
text in the search bar.- Automatic
placement
decides the location of the search bar based on the Apple platform. - If you haven’t noticed yet, we can now pass
Binding
arrays directly in Lists and get a Binding value for each row. A Binding value is required in SwiftUITextField
. Previously, setting aTextField
in List would often lead to reloads of the entire view hierarchy. Thankfully, the support ofBinding
arrays inLists
eliminates that issue.

To display search results, we can use the onSubmit
modifier we discussed above or set up a computed property that filters in real-time, as shown below:

There’s also an Environment value, isSearching
, to track if the user is interacting with the search view. You can use it to display/hide search results in an overlay or another view.
It’ll be interesting to use the onSubmit
modifier with nested SwiftUI Lists.
SwiftUI List Pull To Refresh
Pull to refresh was another missing feature. This forced us to use UIViewRepresentable
workarounds. With iOS 15, you can integrate it in a few lines of code with the refreshable
modifier, as shown below:

You can fetch network requests, like with async/await
within the refreshable modifier, and display the results.
Currently, the native SwiftUI Pull to Refresh doesn’t work on SwiftUI Grids or Scroll View. However, it does bring some customizability. There’s the RefreshAction
environment value to manually trigger a refresh.
SwiftUI Lists Swipe Actions
Swipe to perform actions was another requested feature, and we’ve got it this year with the new swipeActions
modifier.
Here’s a look at swipe actions with the all-new Button style:

By default, swipe actions will show up from the trailing edge of the screen. However, we can configure them to work from the leading side too. Simply use this:
.swipeActions(edge: .leading)
We can also support swipe actions on both sides by chaining multiple modifiers.
By default, the first swipe action gets triggered on a full swipe. But you can change that by setting allowsFullSwipe
to false
.
Currently, swipe actions don’t work with Binding Array Lists.
More Modifiers for Lists, Tabs, Alerts, and Sheets
SwiftUI List has undoubtedly gotten the biggest upgrade this year. If search view, refresh, and swipe actions weren’t enough, there are more ways to customize your lists:
listRowSeparator
andlistSectionSeparator
to show or hide separator lines.listRowSeparatorTint
andlistSectionSeparatorTint
to add color tint to each row and section, respectively..insetStyle(alternatesRowBackgrounds:)
for macOS.badges
are set on the right-hand side of the SwiftUI List row. This is also available in SwiftUI TabViews with iOS 15.
SwiftUI Alert views are now deprecated. Instead, we’ve got new .alert
modifiers. .alert
also brings a variant for error
dialogs.
There’s an interactiveDismissDisabled
function where the developer can choose to prevent the dismissable of forms and sheets. Here’s a quick illustration:

A Task Modifier for Concurrency
Previously, when fetching data for views, many SwiftUI developers would resort to onAppear
.
With iOS 15, Apple brings an all-new task
modifier to let your data load asynchronously. The ability to automatically cancel tasks when the attached SwiftUI view gets destroyed is what makes the task modifier a very handy tool.
Creating an endless scrolling list by loading more data on demand is a perfect use case for the task
modifier. It’ll also be handy for doing the heavy lifting of NavigationLink
destination views (which sadly got no improvements).
Brand New SwiftUI Material Struct
The Material
struct is used to blend foreground elements with the background by adding translucency and vibrancy to the views. We use it in the following ways:
.background(.regularMaterial)
.background(.thinMaterial)
.background(.ultraThinMaterial)
.background(.thickMaterial)
.background(.ultraThicknMaterial)
Optionally, you can set a shape in the Material, as shown below:

Basically, the material type indicates the degree to which the background type passes the foreground element. It’s a good alternative to visually blurring your SwiftUI views.
New Canvas and Timeline Views
A drawRect
in SwiftUI was missing for two years. Finally, Apple is bringing it in the form of Canvas
API and a lot more.
The Canvas API provides out-of-the-box support for drawing paths and shapes. So you can create rich graphics by setting masks and transforming and applying filters in your SwiftUI app. The best part? It’s GPU-accelerated.
TimelineView
, on the other hand, introduces the ability to construct dynamic views that are updated in real-time. We saw a glimpse with the Timer publisher in SwiftUI Text last year.
But TimelineView
takes it to another level by letting you wrap any SwiftUI views and set a schedule that can be periodic or configured at certain times through TimelineSchedule
. TimelineViews are like widgets inside your app.
At WWDC 2021, Apple showcased a bunch of stock iOS apps built using SwiftUI. I’m pretty sure TimelineViews played a huge role in some of them.
Debugging
SwiftUI now provides built-in support for debugging view hierarchy.
Calling the following function inside the view body would print the SwiftUI views that were modified on the last reload:
let _ = Self._printChanges()
Conclusion
In this article, we saw some of the major SwiftUI features announced at WWDC 2021. But there’s still so much to look forward to, like the use of SwiftUI Table
in macOS or the SectionedFetchRequest
property wrapper for CoreData
to add more concurrency to SwiftUI apps.
That’s it for now. Thanks for reading.