top of page
Writer's pictureHui Wang

36. SwiftUI: Hide UIActivityIndicatorView through AnyCancellable

Today I will show you how to use AnyCancellable to hide UIActivityIndicatorView.


Steps:

  • Continue the source code of the previous blog (blog 35).

  • First, import the Combine framework.

  • Then declare a class. This class conforms to the ObservableObject protocol. @ObservableObject and @State property wrappers are very similar, the difference is that ObservableObject is for objects.

import Combine

class GlobalData: ObservableObject {
}

  • Add a @Published property wrapper, which is often used with ObservableObject. It allows properties in the observable to be listened to, thus serving a similar function to @State.

@Published var isActive = false

  • Add an AnyCancellable object. AnyCancellable is a cancelable object of type erasure that executes the provided closure when canceled. For example, we are using this property today to time hide UIActivityIndicatorView.

var cancellable: AnyCancellable? = nil

  • Add an init() method to initialize the property.

  • Pause for 2 seconds, then perform the following actions. It is published by an upstream publisher on the main thread, which publishes the latest or first element.

  • After 2 seconds, send a message with the content false. Transforms all elements of the upstream publisher using the AnyCancellable closure.

  • Finally, the false processed by the map is published to the isActive property of self, thereby modifying the value of this property to false to hide UIActivityIndicatorView.

init() {
    cancellable = $isActive
        .delay(for: 3, scheduler: RunLoop.main)
        .map {val in false}
        .assign(to: \.isActive, on: self)
}

  • Declare a property to receive the passed data. This property has a @EnvironmentObject property wrapper.

  • Display and hide UIActivityIndicatorView according to the value of the Bool property of EnvironmentObject.

@EnvironmentObject var globalData: GlobalData

var body: some View {
    LoadingView(isActive: $globalData.isActive)
}

  • To use the ObservableObject in the preview window, we need to initialize the ObservableObject object in the PreviewProvider.

  • Then pass the ObservableObject object as EnvironmentObject to the instance of ContentView.

let globalData = GlobalData()
globalData.isActive = true

return ContentView().environmentObject(globalData)



Source Code:

import SwiftUI
import UIKit
import Combine

class GlobalData: ObservableObject {
    @Published var isActive = false
    var cancellable: AnyCancellable? = nil
    
    init() {
        cancellable = $isActive
            .delay(for: 3, scheduler: RunLoop.main)
            .map {val in false}
            .assign(to: \.isActive, on: self)
    }
}

struct ContentView: View {
    @EnvironmentObject var globalData: GlobalData
    
    var body: some View {
        LoadingView(isActive: $globalData.isActive)
    }
}

struct ActivityIndicator: UIViewRepresentable {
    @Binding var isActive: Bool
    
    func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
        UIActivityIndicatorView(style: .large)
    }
    
    func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
        isActive ? uiView.startAnimating() : uiView.stopAnimating()
    }
}

struct LoadingView: View {
    @Binding var isActive: Bool
    var body: some View {
        VStack {
            Text("Loading...")
            ActivityIndicator(isActive: $isActive)
        }
        .frame(width: 180, height: 180)
        .background(.teal)
        .foregroundColor(.primary)
        .cornerRadius(20)
        .opacity(isActive ? 1 : 0)
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        
        let globalData = GlobalData()
        globalData.isActive = true
        
        return ContentView().environmentObject(globalData)
    }
}
#endif

 

Follow me on:

Comentarios


bottom of page