What’s New in SwiftUI 3.0?

Markdown support, new button styling, customizable lists, and more

Anupam Chugh
Better Programming

--

Purple flower
Photo by Frédéricke Boies on Unsplash.

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:

SwiftUI Text Markdown example
Screengrab by the author — SwiftUI Text Markdown

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:

SwiftUI New Button Styles: Roles, Prominence, Control Size
Gist

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.

SwiftUI AsyncImage loading Unsplash API

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:

SwiftUI AsyncImage loading Unsplash API

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:

SwiftUI TextField FocusState
Gist

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 the searchCompletion 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 SwiftUI TextField. Previously, setting a TextField in List would often lead to reloads of the entire view hierarchy. Thankfully, the support of Binding arrays in Lists eliminates that issue.
SwiftUI searchable list with suggestion and completions

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:

SwiftUI searchable results
Gist

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:

SwiftUI refreshable — pull to refresh lists
Gist

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:

SwiftUI List swipeActions modifier — swipe to delete

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 and listSectionSeparator to show or hide separator lines.
  • listRowSeparatorTint and listSectionSeparatorTint 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:

SwiftUI popover sheet don’t dismiss with interactiveDismissDisabled

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:

SwiftUI Material — background blur

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.

--

--

iOS and Android Developer. Online Writer. Editor @BttrProgramming. Marketer. Wannabe Filmmaker, and a Funny Human bot!