Adding New Homes and Rooms to HomeKit within a SwiftUI app

Adding New Homes and Rooms to HomeKit within a SwiftUI app

Understand how to add or remove Homes and Rooms to the HomeKit environment within a SwiftUI app.

Even though it is most convenient to manage the general configuration of your HomeKit setup through the native Home app on iOS, iPadOS, or macOS, you might also want to add some general capabilities to your HomeKit-enabled app.

For example, beyond interacting with smart home Accessories, you may also want to add additional Rooms or even Homes to your HomeKit environment. This article shows how to list any Home and its Rooms and how to add additional Rooms or Homes to the configuration. When adding accessories through the default HomeKit workflow, they can then be assigned to a Home and Room.

To develop a HomeKit-enabled app you need to add the HomeKit capability to your Xcode project. This can be done in the Project Settings and the Signing & Capabilities pane of your target. Also, you have to add an entitlement to your projects info.plist to ask the user for permission to access HomeKit. In your project settings Info pane, you can add a new entry for the Custom iOS Target Properties. Provide a meaningful description of why you need access to HomeKit for the NSHomeKitUsageDescription key. To learn more about these requirements, follow our tutorial "Creating a HomeKit-enabled App with SwiftUI".

⚠️
Without the usage description, your app will crash when you first try to use HomeKit, so make sure you don't forget it. 

Let's explore how to display any number of Homes available in HomeKit and any Rooms configured inside. Then, let's also understand how we can add and also remove additional Homes and Rooms to the HomeKit environment.

Finding Available Homes

In this example, we will use a HomeStore class to manage access to HomeKit and reference it from the SwiftUI views throughout the app as a reference type. It uses a private instance of HMHomeManager, which serves as the primary manager for any number of Homes of a user. To monitor changes made to any Home, we adopt the HMHomeManagerDelegate Protocol to track the state of the home network.

The HomeStore also comes with a @Published array of HMHome, which is the primary unit of living space inside HomeKit, that can be composed of Rooms organized in Zones. With this simple setup, we can start exploring the HomeKit configuration for available Homes.

import Foundation
import HomeKit
import Combine

class HomeStore: NSObject, ObservableObject, HMHomeManagerDelegate {
    
    
    @Published var homes: [HMHome] = []
    private var manager: HMHomeManager!

    override init(){
        super.init()
        load()
    }
    
    func load() {
        if manager == nil {
            manager = .init()
            manager.delegate = self
        }
    }
    

    func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
        print("DEBUG: Updated Homes!")
        self.homes = self.manager.homes
    }
}

In a HomeView SwiftUI view you can then display a List of Homes like this:

import SwiftUI
import HomeKit

struct HomeView: View {
    
    @ObservedObject var model: HomeStore
    
    var body: some View {
        List {
            Section(header: HStack {
                Text("My Homes")
            }) {
                ForEach(model.homes, id: \.uniqueIdentifier) { home in
                        Text("\(home.name)")
                }
            }
        }
    }
}

Adding and Removing Homes

Now, if you want to add an additional Home to the configuration, you can extend the HomeStore with a simple function addHome(). In this example, we give it a randomized name with a UUID, in a real app this could be provided by the user. To delete a home, a similarly simple function removeHome(home: HMHome) can be added that takes a HMHome as a parameter.

class HomeStore: NSObject, ObservableObject, HMHomeManagerDelegate {
    
    [...]
    
    func addHome() {
        manager.addHome(withName: "NewHome \(UUID())") { [weak self] (home, error) in
            guard let self = self else {return}
            self.homeManagerDidUpdateHomes(self.manager)

        }
    }
    
    func removeHome(home: HMHome) {
        manager.removeHome(home) { [weak self] error in
            guard let self = self else {return}
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
}

Then, within our SwiftUI view, we could add a simple Button that calls an addhome() function that accesses the newly created function in the HomeStore to add a new Home to the List.

struct HomeView: View {
    
    @State private var path = NavigationPath()
    @ObservedObject var model: HomeStore
    
    var body: some View {
        NavigationStack(path: $path) {
            List {
                Section(header: HStack {
                    Text("My Homes")
                }) {
                    ForEach(model.homes, id: \.uniqueIdentifier) { home in
                        NavigationLink(value: home){
                            Text("\(home.name)")
                        }.navigationDestination(for: HMHome.self){
                            RoomsView(homeId: $0.uniqueIdentifier, model: model)
                        }
                    }
                }
                Button("Add another Home"){
                    addHome()
                }
            }
        }
    }
    
    func addHome(){
        model.addHome()
    }
    
}

While we are at it, let's add a detailed view for the List, so we can display a Button to remove the Home from HomeKit. The RoomView could look like this:

struct RoomsView: View {
    
    var homeId: UUID
    @ObservedObject var model: HomeStore
    
    var body: some View {
    	Button("Remove this Home"){
        	removeHome()
    	}
	}
    
	func removeHome(){
        guard let thisHome = model.homes.first(where: {$0.uniqueIdentifier == homeId}) else {
            print("ERROR: No Home was found!")
            return
        }
        model.removeHome(home: thisHome)
    }
}

Now that we established how to work with Homes, let's explore how we can find, add, and remove Rooms in HomeKit.

Finding Rooms

To now explore the Home and find any available Rooms, we can further extend the functionality of the HomeStore with a simple findRooms(homeId: UUID) function. It takes the unique identifier of a Home as a parameter and then filters the array of homes for the Rooms. All identified Rooms are then made available using another @Published array rooms of the HMRoom type.

class HomeStore: NSObject, ObservableObject, HMHomeManagerDelegate {
    
    @Published var homes: [HMHome] = []
    @Published var rooms: [HMRoom] = []
    
    [...]
    
    func findRooms(homeId: UUID) {
        guard let foundRooms = homes.first(where: {$0.uniqueIdentifier == homeId})?.rooms else {
            print("ERROR: No Roome was found!")
            return
        }
        rooms = foundRooms
    }
}

With this information, we can then extend the RoomView to show a List of all available Rooms. For matters of simplicity, there is a .onAppear() modifier that calls the findRooms(homeId: homeId) of the HomeStore to load the available Rooms in the selected Home.

struct RoomsView: View {
    
    var homeId: UUID
    @ObservedObject var model: HomeStore

    var body: some View {
        List {
            Section(header: HStack {
                Text("My Rooms")
            }) {
                ForEach(model.rooms, id: \.uniqueIdentifier) { room in
                    Text(room.name)
                }
            }
            Button("Remove this Home"){
                removeHome()
            }
        }.onAppear(){
            model.findRooms(homeId: homeId)
        }
    }
    func removeHome(){
        guard let thisHome = model.homes.first(where: {$0.uniqueIdentifier == homeId}) else {
            print("ERROR: No Home was found!")
            return
        }
        model.removeHome(home: thisHome)
    }
}

Adding and Removing Rooms

Now, if you want to add an additional Room to the configuration, you can extend the HomeStore with a simple function addRoom(homeId: UUID). Through the unique identifier of the Home, we can add the new Room with a randomized name with a UUID, even though in a real app this could be provided by the user. To delete a room, a similarly simple function removeRoom(homeId: UUID, room: HMRoom) can be added that takes the unique identifier of a Home and an HMHome as a parameter.

class HomeStore: NSObject, ObservableObject, HMHomeManagerDelegate {
    
    [...]
    
    func addRoom(homeId: UUID) {
        guard let home = homes.first(where: {$0.uniqueIdentifier == homeId}) else {
            print("ERROR: No Home was found!")
            return
        }
        home.addRoom(withName: "NewRoom \(UUID())") { [weak self] (room, error) in
            guard let self = self else {return}
            self.findRooms(homeId: homeId)
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
    
    func removeRoom(homeId: UUID, room: HMRoom){
        manager.homes.first(where: {$0.uniqueIdentifier == homeId})?.removeRoom(room){ [weak self] error in
            guard let self = self else {return}
            self.findRooms(homeId: homeId)
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
}

Then, within our SwiftUI view, we could add a simple Button that calls an addRoom() function that accesses the newly created function in the HomeStore to add a new Room to the List. Let's also navigate to a detail view for the List, so we can display a Button to remove the Room from HomeKit. The RoomView could look like this:

struct RoomsView: View {
    var homeId: UUID
    @ObservedObject var model: HomeStore

    var body: some View {
        List {
            Section(header: HStack {
                Text("My Rooms")
            }) {
                ForEach(model.rooms, id: \.uniqueIdentifier) { room in
                    NavigationLink(value: room){
                        Text("\(room.name)")
                    }.navigationDestination(for: HMRoom.self) {
                        AccessoriesView(homeId: homeId, roomId: $0.uniqueIdentifier ,model: model)
                    }
                }
                Button("Add another room"){
                    addRoom()
                }
            }
            Button("Remove this Home"){
                removeHome()
            }
        }.onAppear(){
            model.findRooms(homeId: homeId)
        }
    }
    func addRoom(){
        model.addRoom(homeId: homeId)
    }
    func removeHome(){
        guard let thisHome = model.homes.first(where: {$0.uniqueIdentifier == homeId}) else {
            print("ERROR: No Home was found!")
            return
        }
        model.removeHome(home: thisHome)
    }
}

Then, the RoomDetailView could look like this, showing just the Button to delete the Room by calling the function in the HomeStore.

struct RoomDetailView: View {
    
    var homeId: UUID
    var roomId: UUID
    @ObservedObject var model: HomeStore

    var body: some View {
        List {
            Button("Remove this Room"){
                removeRoom()
            }
        }
    }
    func removeRoom(){
        guard let thisRoom = model.rooms.first(where: {$0.uniqueIdentifier == roomId}) else {
            print("ERROR: No Home was found!")
            return
        }
        model.removeRoom(homeId: homeId, room: thisRoom)
    }
}

That's basically how this works. Here you find the complete code of the HomeStore that contains all functions to find all Homes and Rooms inside HomeKit and then to add and remove both Homes and Rooms.

class HomeStore: NSObject, ObservableObject, HMHomeManagerDelegate {
    
    
    @Published var homes: [HMHome] = []
    @Published var rooms: [HMRoom] = []

    private var manager: HMHomeManager!
    
    override init(){
        super.init()
        load()
    }
    
    func load() {
        if manager == nil {
            manager = .init()
            manager.delegate = self
        }
    }
    
    func findRooms(homeId: UUID) {
        guard let foundRooms = homes.first(where: {$0.uniqueIdentifier == homeId})?.rooms else {
            print("ERROR: No Roome was found!")
            return
        }
        rooms = foundRooms
    }
    
    func addRoom(homeId: UUID) {
        guard let home = homes.first(where: {$0.uniqueIdentifier == homeId}) else {
            print("ERROR: No Home was found!")
            return
        }
        home.addRoom(withName: "NewRoom \(UUID())") { [weak self] (room, error) in
            guard let self = self else {return}
            self.findRooms(homeId: homeId)
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
    
    func removeRoom(homeId: UUID, room: HMRoom){
        manager.homes.first(where: {$0.uniqueIdentifier == homeId})?.removeRoom(room){ [weak self] error in
            guard let self = self else {return}
            self.findRooms(homeId: homeId)
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
    
    func addHome() {
        manager.addHome(withName: "NewHome \(UUID())") { [weak self] (home, error) in
            guard let self = self else {return}
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
    
    func removeHome(home: HMHome) {
        manager.removeHome(home) { [weak self] error in
            guard let self = self else {return}
            self.homeManagerDidUpdateHomes(self.manager)
        }
    }
    
    func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
        print("DEBUG: Updated Homes!")
        self.homes = self.manager.homes
    }
}

Combined, the app should behave like this:

Working with Zones

HomeKit also allows you to group any number of Rooms in Zones. Similar to the Rooms, you could look up the Zones in any Home and make them available in the HomeStore as a @Published array of HMZone. Within any HMHome you use the addZone(named zoneName: String) async throws -> HMZone and the removeZone(_ zone: HMZone, completionHandler completion: @escaping (Error?) -> Void) functions to manage the Zones. Then, similarly to Homes, Zones have any number of Rooms, which then contain Accessories and their Services and Characteristics.


If you want to learn more about developing HomeKit-enabled apps, check out our other articles: