Activity Indicator with SwiftUI

An Tran • June 10, 2019

ios swiftui

SwiftUI is amazing

It is a declarative way to build up user interfaces for iOS apps. So that developers don't need to decide if they want to build user interfaces using code or storyboard/xib. In combination with the great SwiftUI tools supported in XCode 11, the process to develope apps for Apple's platforms has completely changed.

You can find a lot more information about SwiftUI in many WWDC 2019 sessions dedicated to SwiftUI: https://developer.apple.com/videos/all-videos/?q=swiftui

You can also take a look here https://www.hackingwithswift.com/quick-start/swiftui/migrating-from-uikit-to-swiftui for list of SwiftUI Views corresponding to their UIKit counterparts.

As a new framework and the documentation is not fully completed, it is sometimes hard to do some simple tasks. One of such common tasks is showing a loading indicator while the app is doing some heavy asynchronous operations. If you follow all WWDC Videos about SwiftUI, you can't find any sample code for showing loading indicators while for example loading images from remote sources/internet.

Wrapping UIControls

Since SwiftUI doesn't have all equivalent Views for all UIControls from UIKit, the SwiftUI team has created great APIs so that developers can wrap any UIKit components in SwiftUI's views. Basically the wrapper must only adopt UIViewRepesentable protocol.

The following code will wrap UIActivityIndicator into a ActivityIndicator View which can be use as a SwiftUI View.

struct ActivityIndicator: UIViewRepresentable {

    typealias UIViewType = UIActivityIndicatorView

    let style: UIActivityIndicatorView.Style

    func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> ActivityIndicator.UIViewType {
        return UIActivityIndicatorView(style: style)
    }

    func updateUIView(_ uiView: ActivityIndicator.UIViewType, context: UIViewRepresentableContext<ActivityIndicator>) {
        uiView.startAnimating()
    }
}

Normally, developers don't use UIActivityIndicator alone but also adding an UILabel to describe the context of the asynchronous operation such as: Loading, Calculating, Processing .....

Since we now have ActivityIndicator as a SwiftUI View, we can easily use it and combine with other views to create a full-blown HUD (Head up Display) View

struct ActivityIndicatorView<Content>: View where Content: View {
    @Binding var isShowing: Bool
    var content: () -> Content

    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .center) {
                if (!self.isShowing) {
                    self.content()
                } else {
                    self.content()
                        .disabled(true)
                        .blur(radius: 3)

                    VStack {
                        Text("Loading ...")
                        ActivityIndicator(style: .large)
                    }
                    .frame(width: geometry.size.width / 2.0, height: 200.0)
                    .background(Color.secondary.colorInvert())
                    .foregroundColor(Color.primary)
                    .cornerRadius(20)
                }
            }
        }
    }
}

To preview it, you can use this example code:

#if DEBUG
struct ActivityIndicatorView_Previews: PreviewProvider {

    static var previews: some View {
        ActivityIndicatorView(isShowing: .constant(true)) {
            NavigationView {
                Text("Hello World")
                    .navigationBarTitle(Text("List"), displayMode: .large)
            }
        }
    }
}
#endif

Open Source

The source code is avaialbe as a swift package here https://github.com/peacemoon/SwiftyUIView.

The result is quite promising:

Result