Home > Mobile >  How to listen for data change with @Published variable then reload tableView
How to listen for data change with @Published variable then reload tableView

Time:12-06

The most difficult task I face is to know the correct terminology to search for. I'm used to SwiftUI for an easy way to build an app in the fastest time possible. With this project I have to use UIKit and for this specific task.

Inside a view controller I created a tableView:

private let tableView: UITableView = {
    let table = UITableView()
    table.register(ProfileCell.self, forCellReuseIdentifier: ProfileCell.identifier)
    return table
}()

Later I reload the data inside viewDidLoad


override func viewDidLoad() {
    super.viewDidLoad()
    
    Task {
        do {
            try await viewModel.getProfiles()

            // Here I reload the table when data comes in
            self.tableView.reloadData()
        } catch {
            print(error)
        }
    }
    
    view.addSubview(tableView)
    tableView.delegate = self
    tableView.dataSource = self
}

So what is viewModel? In SwiftUI I'm used to having this inside a view struct:


@ObservedObject var viewModel = ProfilesViewModel()

..and that's what I have inside my view controller. I've searched for:

  • observedobject in uitableview
  • uitableview reload data on data change

..and more but noting useful for me to "pick up the pieces" with.

In same controller, I'm showMyViewControllerInACustomizedSheet which now uses UIHostingController:


private func showMyViewControllerInACustomizedSheet() {
    // A SwiftUI view along with viewModel being passed in
    let view = ProfilesMenu(viewModel: viewModel)
    let viewControllerToPresent = UIHostingController(rootView: view)
    if let sheet = viewControllerToPresent.sheetPresentationController {
        sheet.detents = [.medium(), .large()]
        sheet.largestUndimmedDetentIdentifier = .medium
        sheet.prefersScrollingExpandsWhenScrolledToEdge = false
        sheet.prefersEdgeAttachedInCompactHeight = true
        sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
    }
    present(viewControllerToPresent, animated: true, completion: nil)
}

For the ProfilesViewModel:


class ProfilesViewModel: ObservableObject {

  // ProfilesResponse is omitted
  @Published var profiles = [ProfilesResponse]()

  public func getProfiles(endpoint: String? = nil) async throws -> Void {
    
   // After getting the data, I set the profiles variable
   self.profiles = [..]
  }
}

Whenever I call try await viewModel.getProfiles(endpoint: "..."), from ProfileMenu, I'd like to reload the tableView. What additional setup is required?

CodePudding user response:

In the comments, Vadian mentioned "Combine" where I did a Google search and found this. What works, for a basic demonstaration:


[..]
import Combine

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
  private let viewModel = ProfilesViewModel()
  private var cancellable: AnyCancellable?

  override func viewDidLoad() {
      super.viewDidLoad()
      
      Task {
          do {
              try await viewModel.getProfiles()

              // Remove this
              // self.tableView.reloadData()
          } catch {
              print(error)
          }


      }
      
      view.addSubview(tableView)
      tableView.delegate = self
      tableView.dataSource = self

      // Add this
      cancellable = viewModel.objectWillChange.sink(receiveValue: { [weak self] in
        self?.render()
      })
  }

  // Also add this
  private func render() {
    // TODO: Implement failures...
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
  }

  ...
}

objectWillChange was the key to my problem.

  • Related