Home > Net >  Publishing array from a manager class to ViewModel and then to View
Publishing array from a manager class to ViewModel and then to View

Time:03-03

I am trying to establish a MVVM pattern to fetch values from a BLEManager to ViewModel and then to my view. But somehow I am not able to emit the values from the BLEManager class to View Model. Following is my data flow:

View(Main View with some contents) <- View(one content with a list) <- ViewModel(viewmodel that feeds data as array to list in view) <- BleManager(ble scanner to fetch available ble devices)

BleManager:

    class BLEManager: NSObject, ObservableObject {
    var centralManager: CBCentralManager!
    @Published var peripherals = [CBPeripheral]()
    @Published var isSwitchedOn = false
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    func startScanning() {
        centralManager.scanForPeripherals(withServices: nil, options: nil)
    }
    
    func stopScanning() {
        centralManager.stopScan()
    }
}

extension BLEManager: CBCentralManagerDelegate, CBPeripheralDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            isSwitchedOn = true
        }
        else {
            isSwitchedOn = false
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        guard let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String else {
            return
        }
        if !peripherals.contains(where: { $0.name == peripheralName }), let name = peripheral.name {
            peripherals.append(peripheral)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("didFailToConnect")
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("didDisconnectPeripheral")
    }
}

I am not able to figure out how to code my view model following is its current state: ViewModel:

    class NearbyBLEListViewModel: ObservableObject {
    @Published var authenticationResult: Bool?
    @Published var bleDevices = [BLEInfo]()
    @Published var blutoothOff: Bool?
    
    var bleManager = BLEManager()
    
    
    func startScanning() {
        bleManager.startScanning()
        bleDevices = bleManager.peripherals.map { BLEInfo(bleId: $0.name ?? "NA", hash: "") }
//        if bleManager.isSwitchedOn {
//            blutoothOff = false
//            bleDevices = bleManager.peripherals.map { bleInfo(bleId: $0.name ?? "NA", hash: "") }
//            print(bleDevices)
//        } else {
//            blutoothOff = true
//        }
    }
    
    func getBLEDevices() {
        bleDevices = bleManager.peripherals.map { BLEInfo(bleId: $0.name ?? "NA", hash: "") }
        print(bleDevices)
    }
}

Using it in the view as following:

    struct NearbyBLEListView: View {
    @ObservedObject var bleManager = BLEManager()
    @StateObject private var viewModel = NearbyBLEListViewModel()
    
    var body: some View {
        VStack {
            Text("BLEs nearby:")
                .font(.system(size: 22, weight: .bold, design: .default))
                .padding()
                .foregroundColor(.black)           
            List(viewModel.bleDevices) { ble in
                HStack {
                    Text(ble.bleId)
                }
                    }
                    .navigationTitle("News")
                    .onAppear(perform: viewModel.startScanning)
            
        }
    }
}

and lastly my top most view:

var body: some View {
    NavigationView {
        VStack(alignment: .center) {
            buttonView
            NearbyBLEListView()
        }
        .navigationTitle("")
        .toolbar {
            moreButtonView
        }
    }
}

I have just started using SwiftUI so I may be off the best practices for SwiftUI. Can someone please help me here?

CodePudding user response:

Get rid of the view model objects, we don't use MVVM in SwiftUI. The View data struct is already the model for the actual views on screen e.g. UILabels, UITables etc. that SwiftUI updates for us. We can move related vars and logic into a custom @State struct to make the code more testable however in your case it just looks like this:

struct NearbyBLEListView: View {
    @StateObject var bleManager = BLEManager()

    var body: some View {
        VStack {
            Text("BLEs nearby:")
            .font(.system(size: 22, weight: .bold, design: .default))
            .padding()
            .foregroundColor(.black)           
            List(bleManager.bleDevices) { ble in
                HStack {
                    Text(ble.bleId)
                }
            }
            .navigationTitle("News")
        }
    }
}

@StateObjects are init when the view appears so you can start scanning inside BLEManager's init. Also you need the BLEManager to hold your BLEDevice array of structs, e.g.

class BLEManager: NSObject, ObservableObject {
    @Published var bleDevices = [BleDevice]()
  • Related