Model objects that are not developer-defined?

193 views Asked by At

Every SwiftUI tutorial/example uses model objects that are defined by me, the guy writing the app. However, what is the best practice when the model objects are not under my direct control? For example, the HomeKit framework has an API to get all the rooms in a given home. It returns an array of HMRoom objects:

open class HMRoom: NSObject 
{
    open var name: String { get }
    open var accessories: [HMAccessory] { get }
    open var uniqueIdentifier: UUID { get }

    // Update the room's name in a HomeKit-compliant way that notifies all other HomeKit apps
    open func updateName(_ name: String) async throws
}

When I receive an array of HMRoom objects from the HomeKit API, what should I do with them to power SwiftUI? Should I create my own class that looks like this:

final class Room: ObservableObject
{
    @Published var name: String
    @Published var accessories: [Accessory]
    @Published var uniqueIdentifier: UUID

    private var representedRoom: HMRoom

    init(homekitRoom: HMRoom)
    {
        // Copy properties from 'homekitRoom' to self, then set `representedRoom` to `homekitRoom` so we can use it to call the updateName(:) function
    }
}

Is there, instead, a way for me to extend the HMRoom class directly to inform SwiftUI that name, accessories, and uniqueIdentifier are the properties we must watch for changes in order to reload views appropriately?

It's unclear to me what the best approach is to integrate @Published/@ObservableObject when I don't write the model classes/structs myself.

2

There are 2 answers

4
Yrb On

Why not a class to hold all of your rooms with a published variable like this:

class Rooms: ObservableObject {

    @Published rooms: [HMRoom]

}

Isn't that your view model? You don't need the room parameters to be published; you need to know when anything changes and evaluate it from there.

0
malhal On

If you read Apple's document Choosing Between Structures and Classes the Use Classes When You Need to Control Identity section makes you think use a class, however if you read the Use Structures When You Don't Control Identity section and notice that every HomeKit object contains a uniqueIdentifier it turns out we can use structs yay!

struct Room: Identifiable {
    let id: UUID
    var name: String
}

class HomeDelegate: NSObject, HMHomeDelegate {
    weak var homeManagerDelegate: HomeManagerDelegate?

    // HomeManagerDelegate sets the primaryHome.
    var home: HMHome? {
        didSet {
            oldValue?.delegate = nil
            home?.delegate = self
            reload()
        }
    }
    
    func reload() {
        let rooms: [Room]
        if let home = home {
            rooms = home.rooms.map { r in
                Room(id: r.uniqueIdentifier, name: r.name)
            }
        }else {
            rooms = []
        }
        homeManagerDelegate?.rooms = rooms
    }

    func home(_ home: HMHome, didUpdateNameFor room: HMRoom) {
        if let index = homeManagerDelegate?.rooms.firstIndex(where: { $0.id == room.uniqueIdentifier } ) {
            homeManagerDelegate?.rooms[index].name = room.name
        }
     }

Note: I'd recommend not using structs that mirror the home classes and instead create a struct that contains what you want to show in your UI. And in your model ObservableObject you could have several @Published arrays of different custom structs for different HomeKit related things. Apple's Fruta and Scrumdinger sample projects can help with that.