I'm currently doing a project with a variable number of BLE sensors at varying locations and want to display a neat table (grid) of Observations and Locations (in pure SwiftUI) like this:
Temperature: Locn1_value, Locn2_value , ..... Locnx_value, obsTime
Humidity: Locn1_value, Locn2_value ..... Locnxvalue, obsTime
(optionally more sensors)
The SwiftUI View is:
struct SensorObservationsView: View {
let sensorServer = SensorServer.shared
@State var latestObservations = [ObservationSummary]()
@State var obsColumns = Array(repeating: GridItem(.flexible(),spacing: 20), count: 4)
var body: some View {
VStack{
ForEach(latestObservations,id: \.id) { latestObs in
HStack{
LazyVGrid(columns: obsColumns, content: {
Text(latestObs.id) .foregroundColor(latestObs.colour)
ForEach(latestObs.summaryRows, id:\.id) { row in
Text(row.strVal) .foregroundColor(latestObs.colour)
}
Text(latestObs.summaryRows.last!.strTime) .foregroundColor(latestObs.colour)
})
}
}
}
.onReceive(sensorServer.observationsUpdated, perform: { observationSummaries in
if observationSummaries.isEmpty { return }
latestObservations = observationSummaries
let columns = observationSummaries.last!.summaryRows.count
var newColumns = [GridItem]()
#if os(tvOS)
newColumns.append(GridItem(.fixed(230.0), spacing: 10.0, alignment: .leading))
#else
newColumns.append(GridItem(.fixed(130.0), spacing: 10.0, alignment: .leading))
#endif
for in (0..<columns) {
#if os(tvOS)
newColumns.append(GridItem(.fixed(170.0), spacing: 10.0, alignment: .trailing))
#else
newColumns.append(GridItem(.fixed(70.0), spacing: 10.0, alignment: .trailing))
#endif
}
#if os(tvOS)
newColumns.append(GridItem(.fixed(190.0), spacing: 10.0, alignment: .trailing))
#else
newColumns.append(GridItem(.fixed(90.0), spacing: 10.0, alignment: .trailing))
#endif
obsColumns = newColumns
})
}
}
SensorServer collects all required characteristics for all active sensors every few minutes, then publishes the set via SensorServer.observationsUpdated. The View's .onReceive then creates an appropriate array of GridItems based on the number of columns in latestObservations (sadly, I named these as "summaryRows" - because the raw observations are in rows). "latestObs.id" is the observation type e.g. "temperature". The observation time for all is the same and taken from the timestamp of the last item of the summary rows(columns). I also adjust the layout depending on the target platform.
PS: SensorServer ensures that there's the same number of location columns, using a default content of "n/a" if there's no valid data from the BLE sensor. The solution is dynamic in that I can add/remove locations (sensors) and not have to recode the View. Sensor data are pre-formatted to strings before sending to the view.
I hope this helps someone, somewhere, sometime. Cheers, Michaela