Daily coverage of WWDC20.
A Swift by Sundell spin-off.

The UIKit views that now have built-in SwiftUI counterparts in iOS 14

Published at 10:00 GMT, 24 Jun 2020
Written by: John Sundell

SwiftUI’s overall coverage of commonly used views has been greatly expanded this year, which is fantastic news both for developers who already have existing apps built with SwiftUI, and for people who are now getting started with the framework for the very first time.

In this article, let’s take a look at a few examples of UIKit views that now have SwiftUI-native counterparts, which (at least for many common use cases) can serve as great alternatives to bringing those views in from UIKit using UIViewRepresentable.

UITextView  →  TextEditor

Let’s start by taking a look at the new TextEditor type, which offers a built-in way to add a larger text editing area to a UI, which is what makes it different from the TextField type that SwiftUI has been shipping with since its introduction.

While this could previously be done by wrapping an instance of UIKit’s UITextView, that’s no longer needed, at least when we’re looking to add a more basic set of text editing functionality to an app. To use TextEditor, we simply initialize an instance of it with a String binding that it should use to keep track of its current text content — like this:

struct EditorView: View {
    @State var text = ""

    var body: some View {
        TextEditor(text: $text)
    }
}

While TextEditor does (at the time of writing) not offer nearly the same amount of power and customization options as UITextView does — especially when it comes to custom text storage and formatting options — it should prove to be a nice alternative for a wide range of apps that are not text editors per se.

UIActivityIndicator  →  ProgressView

I wonder, is there a single existing iOS SwiftUI app out there that hasn’t been wrapping UIActivityIndicator in order to display a system-provided loading spinner? At least as far as I can tell, being able to natively add an activity indicator within a SwiftUI view must’ve been one of the most commonly requested features throughout this past year — and now, we can!

The new ProgressView type will show an appropriate default loading spinner when initialized without any specific arguments. Combine that with the fact that SwiftUI’s function builders now support switch statements, and we could for example display a loading spinner within one of our views like this:

struct ProfileView: View {
    @ObservedObject var viewModel: ProfileViewModel

    var body: some View {
        VStack {
            switch viewModel.state {
            case .loading:
                ProgressView()
            case .loaded(let user):
                Text(user.name)
                Text(user.emailAddress)
            }
        }
    }
}

While being able to do the above is already incredibly useful, ProgressView is a lot more powerful than its UIKit counterpart — and will adapt itself beautifully across all of Apple’s platforms with very little code required on our part.

For example, here’s how easy it is to create a progress view variant for tracking a known quantity of progress, for example to show the user the state of a download or some other asynchronous operation:

struct DownloadView: View {
    @ObservedObject var download: Download

    var body: some View {
        ProgressView(value: download.completionRatio, total: 1) {
            Text("Downloading...")
        }
    }
}

UICollectionView  →  LazyHGrid + LazyVGrid

Finally, let’s take a look at how many usages of UICollectionView can now be replaced with SwiftUI’s new LazyHGrid and LazyVGrid types.

The concept of laziness is a big trend when it comes to this year’s SwiftUI changes. In general, implementations that are lazy don’t perform their work up-front, but rather at the time when it’s first needed. This can often lead to a ton of performance improvements, especially when dealing with larger collections and other heavy data.

For example, here we’re building a PhotoGallery view, which displays a collection of photos in a grid. If we want our grid to mimic the layout behavior of UICollectionViewFlowLayout, then we can define it as a LazyVGrid that uses a single adaptive column — like this:

struct PhotoGallery: View {
    var photos: [Photo]

    var body: some View {
        ScrollView {
            // In a vertical grid, columns are defined using grid
            // items, which determine the layout of the grid's
            // cells. Here we're using an adaptive layout with
            // 25 points worth of spacing between each cell:
            let column = GridItem(.adaptive(minimum: 100), spacing: 25)

            LazyVGrid(columns: [column]) {
                ForEach(photos) { photo in
                    VStack {
                        photo.image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                        Text(photo.name)
                    }
                }
            }
        }
    }
}

We could also give our grid multiple explicit columns, by passing a number of GridItem instances when creating our LazyVGrid. When doing that, we can choose whether we want each column to have a fixed or flexible width, which gives us a lot of control over how we want our grid to behave in terms of layout. Here we’re giving our grid two columns, each with flexible width:

struct PhotoGallery: View {
    var photos: [Photo]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: makeColumns()) {
                ForEach(photos) { photo in
                    VStack {
                        photo.image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                        Text(photo.name)
                    }
                }
            }
        }
    }

    private func makeColumns() -> [GridItem] {
        let item = GridItem(.flexible(), spacing: 25)
        return Array(repeating: item, count: 2)
    }
}

Really cool! Of course, the above two examples just scratch the surface of what SwiftUI’s new grid APIs are capable of — so we’ll dive a lot deeper into them, with many practical, real-life examples, in upcoming weekly articles on Swift by Sundell.

Conclusion

While we focused on taking a look at the SwiftUI views that now offer built-in implementations of commonly used UIKit views in this article, there’s also a ton of other new views and other APIs that have been added to SwiftUI this year. So stay tuned for more coverage on those new features, which is coming soon.

Also, I really recommend watching yesterday’s What’s new in SwiftUI session, which was truly excellent.

What do you think? Are you excited about these new view types, and are you going to adopt some of them within one of your apps? Let me know on Twitter @johnsundell.

Thanks for reading! 🚀

Written by: John Sundell