What’s New in SwiftUI 2.0?

iOS 14 brings Lazy Stacks, ProgressView, ColorPickers, labels, grids, and more SwiftUI controls

WWDC 2020 commences this week, and the developer community was eagerly looking forward to SwiftUI 2.0. Unsurprisingly, Apple dropped some brand new API updates for SwiftUI at the end of Keynote.

The new improvements in SwiftUI are additive in nature. This means that there are no deprecations or changes that’ll break your old 13 SwiftUI codebases.

In the next few sections, we’ll take a sneak peek at the new SwiftUI controls released with iOS 14. You’ll need an Xcode 12 beta (which requires a minimum macOS version of 10.15.4) to run them. Let’s get started.


A New SwiftUI App Starting Point

Up until now, we had to use AppDelegates and SceneDelegates to set our first SwiftUI view. Swift 5.3 introduced a type-based program entry point that can be set using the @main attribute and the latest SwiftUI iteration smartly leverages that.

SwiftUI now provides the following struct that’s called upon app launch:

@main
struct WhatsNewiOS14SwiftUIApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

WindowGroup is a scene property inside so that we can define the starting view hierarchy. We can set out TabView, NavigationViews, or App Clips inside the WindowGroup computed property.

SwiftUI LazyVStack and LazyHStack

Previously, SwiftUI views used to load immediately, which led to performance and memory issues when populating huge amounts of data.

Funnily, the NavigationLink destination view used to load contents upfront too in SwiftUI’s first iteration. This time, Apple has introduced new lazy horizontal and vertical stacks that load content as and when it’s needed, thereby aiding in the performance optimization of SwiftUI. Lazy loading is introduced in SwiftUI Lists as well now.

Here’s an example of a SwiftUI LazyHStack in action:

struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
LazyHStack(spacing: 10) {
ForEach(0..<1000) { index in
Text("\(index)")
.frame(width: 100, height: 200)
.border(Color.gray.opacity(0.5), width: 0.5)
.background(Color.blue)
.cornerRadius(6)
}
}
.padding(.leading, 10)
}
}
}

SwiftUI Scroll View Position

The first version of SwiftUI suffered a lot at the ScrollView front due to its limited capabilities. iOS 14’s SwiftUI brings the much-needed ScrollViewReader and ScrollViewProxy to capture scroll view offset positions and move onto them programmatically.

In order to do so, we embed our views inside a ScrollViewReader and use the scrollTo method in either of the following ways:

scrollView.scrollTo(viewId)

//or

scrollView.scrollTo(viewId, anchor: .center)

By default, the scroll view position gets set to the leading or top of the view. We can refine that by using the anchor property. For example, in the following piece of code, when setting the anchor property to the center, the scroll position looks much better than when it was set to leading.

SwiftUI ProgressView

Previously, we had to leverage SwiftUI Shapes to replicate linear ProgressView and UIViewRepresentable for creating ActivityIndicator in SwiftUI.

Now, in iOS 14’s SwiftUI, ProgressView has native support.

A default ProgressView() creates an indeterminate UIActivityIndicator like progress loader whereas the following creates a linear ProgressView in SwiftUI:

ProgressView("Text", value: 10, total: 100)

We can further customize the ProgressView by using progressViewStyle, which accepts built-in CircularProgressViewStyle , DefaultProgressViewStyle, and lets you create custom modifiers as well.

accentColor is used to set the text color in the ProgressView, while foregroundColor acts as the tint.

SwiftUI Labels, Links, and ColorPickers

Labels are a much-needed addition in the latest SwiftUI iteration. They let you set icons alongside text with the following line of code:

Label("SwiftUI 2.0", systemImage: "checkmark.icloud")

Inside the icons property, you can set SF Symbols, image assets, or custom SwiftUI Shapes.

Note: At the time of writing, the icon is aligned with the top of the text. Hopefully, this will get fixed soon.

SwiftUI Link is another cool UI control that provides built-in support for navigating to a URL:

Link("Click me",destination: URL(string: "your_url")!)

The link is redirected to either the web browser or the associated application in case it’s a universal link.

Another big addition to the SwiftUI arsenal is the inclusion of a native ColorPicker UI control. You can use a State property wrapper to update the color picked by the user.

ColorPicker("Sample Picker", selection: $myColor)

SwiftUI TextEditor, MapKit, Sign In With Apple

Multi-line scrollable UITextViews that were omitted last time are now included natively in SwiftUI and known as TextEditor.

TextEditor(text: $stateProperty)

MapKit, which had to be embedded in SwiftUI by wrapping in UIViewRepresentable, now gets added natively. We can pass a MKCoordinateRegion , show user location, and do a lot of other MapKit stuff directly from SwiftUI’s view interface itself.

Map(mapRect:interactionModes:showsUserLocation: userTrackingMode:

SignInWithAppleButton now makes its way into SwiftUI’s built-in control. To set up the button, we simply instantiate the struct and set the label argument as either .signUp or .signIn to indicate the type of authorization. For more details on its syntax, refer to the official documentation.

A New onChange Modifier to Listen for State Changes

onChange is a new view modifier that’s available across all SwiftUI views. It lets you listen to state changes and perform actions on a view accordingly.

For instance, we can toggle a Button state change and trigger the TextEditor to clear since the onChanged modifier is attached, as shown below:

import SwiftUI
struct ContentView: View {
@State var currentText: String = "Hi How are you?"
@State var clearText: Bool = false
var body: some View {
VStack{
TextEditor(text: $currentText)
.onChange(of: clearText) { value in
if clearText{
currentText = ""
}
}
Button(action: {clearText = true}, label: {
Text("Clear Text Editor")
})
}
}
}

Note: The clearText state property triggers the onChange modifier automatically for the first time when SwiftUI’s body is instantiated.

SwiftUI TabView Brings New Style for Page Control

UIPageViewController did make its way into SwiftUI the last time. In the iOS 14 iteration, TabView introduces a new style to let you embed swipeable page control in your SwiftUI views. Simply set the PageTabViewStyle() in your .tabViewStyle() modifier, as shown below:

import SwiftUI
struct ContentView: View {
let colors: [Color] = [.red, .green, .yellow, .blue]
var body: some View {
TabView {
ForEach(0..<6) { index in
Text("Tab \(index)")
.font(.title)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(colors[index % colors.count])
.cornerRadius(8)
}
}.tabViewStyle(PageTabViewStyle())
}
}

Here’s a screengrab of the code above in action on Xcode 12:

SwiftUI Grids

CollectionView and Compositional Layouts were once again missed in iOS 14 SwiftUI. But that didn’t stop Apple from introducing new containers for grid-based layouts that let you set child views in LazyHGrid or LazyVGrid.

Each element of a SwiftUI grid is a GridItem. We can set the alignments, spacing, and size of the GridItem. In the following code, we’ve created a vertical grid layout in SwiftUI that consists of three columns:

struct ContentView: View {
let colors: [Color] = [.red, .green, .yellow, .blue]
var columns: [GridItem] =
Array(repeating: .init(.flexible(), alignment: .center), count: 3)
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(0...100, id: \.self) { index in
Text("Tab \(index)")
.frame(width: 110, height: 200)
.background(colors[index % colors.count])
.cornerRadius(8)
}
}
}
}
}

With just a few lines of code, we’ve built a customizable grid layout in SwiftUI for iOS 14.

Conclusion

There’s a lot more to look forward to from SwiftUI and other iOS 14 framework updates this year. SwiftUI OutlineGroups and VideoPlayer support are a few promising new features. Most importantly, SwiftUI View builders now support if let and switch statements.

That’s it for this one. Thanks for reading.