Post

Replies

Boosts

Views

Activity

SwiftUI dynamic number of columns with LazyVGrid
As a septuagenarian my memory is prone to leaks and I therefore rely on this forum and Stack Overflow for discovering (rediscovering?) solutions to problems. I like to give back when I can, so here goes..... 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
2
0
3.4k
Mar ’22
Creating an MLFeatureProvider class in iOS for an MLModel
Most examples, including within documentation, of using CoreML with iOS involve the creation of the Model under Xcode on a Mac and then inclusion of the Xcode generated MLFeatureProvider class into the iOS app and (re)compiling the app.  However, it’s also possible to download an uncompiled model directly into an iOS app  and then compile it (background tasks) - but there’s no MLFeatureProvider class.  The same applies when using CreateML in an iOS app (iOS 15 beta) - there’s no automatically generated MLFeatureProvider.  So how do you get one?  I’ve seen a few queries on here and elsewhere related to this problem, but couldn’t find any clear examples of a solution.  So after some experimentation, here’s my take on how to go about it: Firstly, if you don’t know what features the Model uses, print the model description e.g. print("Model: ",mlModel!.modelDescription). Which gives Model:   inputs: (     "course : String",     "lapDistance : Double",     "cumTime : Double",     "distance : Double",     "lapNumber : Double",     "cumDistance : Double",     "lapTime : Double" ) outputs: (     "duration : Double" ) predictedFeatureName: duration ............ A prediction is created by guard **let durationOutput = try? mlModel!.prediction(from: runFeatures) ** …… where runFeatures is an instance of a class that provides a set of feature names and the value of each feature to be used in making a prediction.  So, for my model that predicts run duration from course, lap number, lap time etc the RunFeatures class is: class RunFeatures : MLFeatureProvider {     var featureNames: Set = ["course","distance","lapNumber","lapDistance","cumDistance","lapTime","cumTime","duration"]     var course : String = "n/a"     var distance : Double = -0.0     var lapNumber : Double = -0.0     var lapDistance : Double = -0.0     var cumDistance : Double = -0.0     var lapTime : Double = -0.0     var cumTime : Double = -0.0          func featureValue(for featureName: String) -> MLFeatureValue? {         switch featureName {         case "distance":             return MLFeatureValue(double: distance)         case "lapNumber":             return MLFeatureValue(double: lapNumber)         case "lapDistance":             return MLFeatureValue(double: lapDistance)         case "cumDistance":             return MLFeatureValue(double: cumDistance)         case "lapTime":             return MLFeatureValue(double: lapTime)         case "cumTime":             return MLFeatureValue(double: cumTime)         case "course":             return MLFeatureValue(string: course)         default:             return MLFeatureValue(double: -0.0)         }     } } Then in my DataModel, prior to prediction, I create an instance of RunFeatures with the input values on which I want to base the prediction: var runFeatures = RunFeatures() runFeatures.distance = 3566.0 runFeatures.lapNumber = 1.0 runFeatures.lapDistance = 1001.0  runFeatures.lapTime = 468.0  runFeatures.cumTime = 468.0  runFeatures.cumDistance = 1001.0  runFeatures.course = "Wishing Well Loop" NOTE there’s no need to provide the output feature (“duration”) here, nor in the featureValue method above but it is required in featureNames. Then get the prediction with guard let durationOutput = try? mlModel!.prediction(from: runFeatures)  Regards, Michaela
1
1
2.0k
Mar ’22
TabularData Framework: DataFrame as a List in SwiftUI
The new TabularData framework in iOS 15, MacOS 12 and watchOS 8 opens up opportunities for easier, more efficient ingestion of data (and for ML possibilities). However, it does not appear to be possible to directly use a DataFrame's rows for a List or ForEach in SwiftUI: the compiler gives an error that the rows do not conform to RandomAccessCollection Protocol. The documentation for Rows does not state compliance with such, even through there are methods which inherit from the protocol. Using a separate iteration to extract each Row (as a DataFrame.Row) from the DataFrame into a new array works. I make this array identifiable by using the row.index as the id. However, if the DataFrame is large this adds considerably to storage and processing overheads. Any thoughts on directly using the DataFrame's Rows in SwiftUI?
2
0
3.3k
Mar ’22
Changing attribute type in a PersistentCloudKitContainer Model
I have a Multiplatform app using a PersistentCloudKitContainer Model that has a complex object graph and which is still in Development mode (including in CloudKit). An integer attribute of one of the entities needs to be changed to double, for reasons that were not obvious on first analysis of data. According to the Core Data Migration documentation , which is 10 years old, this case would not be Lightweight Migration and so I would need to create a Migration Policy and Process. Although I've only recently imported a few legacy records that set the Int attribute (now required to be Double), there are several hundred entities (imported legacy data) with a default Int value (0). Tempting though it is to just change the attribute type from Int to Double and see what happens, recreating the model and hundreds (thousands?) of records if things go wrong would be a real pain in the derriere. So, given the complexity of my model and the use of CloudKit for multi-device synching, I'm thinking I'll create a new Double attribute and reimport data into that, then when all's fine delete the original Int attribute. This way both sets of changes are Lightweight. Advice? Thoughts? Regards, Michaela
0
0
925
Jan ’22
Calendar Component Quarter always returns zero
I'm doing some summarising of dated data from Coredata using @SectionedFetchRequest and SwiftUI List with Section headers. Summary options are by week, month, quarter and year, with processing by Calendar Component. All works fine (with a bit of extra processing for the week period description) except for quarter, which always returns zero instead of 1 to 4. When I finally decided to look at the documentation https://developer.apple.com/documentation/foundation/calendar/component/quarter there's an Important note saying "The quarter unit is largely unimplemented, and is not recommended for use.". So why have that enumeration if it's known not to work? OK, so I now get the month and then determine the quarter with a switch statement.......
2
0
1.3k
Oct ’21
DateComponentsFormatter UnitsStyle .abbreviated in MacOS
According to the documentation for DateComponentsFormatter UnitsStyle .abbreviated https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle, this style (abbreviated) should create a string like “9h 41m 30s”. In some circumstances, eg MacOS target with import Foundation, the result is "9h 41min. 30s.", which is a sort-of mix between .brief and .abbreviated. In my SwiftUI multi-platform app (MacOS & iOS) the incorrect (mixed) style is generated for both platforms. Using Playground, the mixed formatting occurs when the platform is set to MacOS, but not iOS with either import UIKit or import Foundation. Is this a bug, or a "featured" difference between MacOS and iOS? My multi-platform function is import Foundation public func strDuration(_ duration: TimeInterval, style: DateComponentsFormatter.UnitsStyle = .abbreviated) -> String {         let formatter = DateComponentsFormatter()         formatter.allowedUnits = [.hour, .minute, .second]         formatter.unitsStyle = style         formatter.maximumUnitCount = 3         return formatter.string(from: duration) ?? "n/a"     } Regards, Michaela
6
0
1.6k
Sep ’21
Multi-platform CoreData synchronisation: deleting data from all devices except one?
I'm currently developing a multi-platform app where a standalone Watch app collects data, summarises it, then stores the summary in CoreData. The summary gets synchronised across all devices by CloudKit. However, the raw data (e.g. RR interval data from a Polar H10 heart rate monitor) are important from an historical perspective: forming the basis of further analysis, perhaps via AI. Given the limited storage, battery and processing resources on the Watch, it would be inappropriate to persistently store, or perform complex analysis of, the raw data on the Watch. My preferred solution would be to transfer the raw data to a Mac and, once successfully received, delete it from the Watch (also from CloudKit?). The Mac would then perform further analysis and propagate an additional summary to all other devices. The problem I foresee is that, if using CoreData/CloudKit synchronisation, deleting from the Watch would delete from all other devices. With my currently limited understanding of such synchronisation, I don't see a way of not then automatically deleting the Mac's CoreData records. Perhaps the solution is to have a separate CoreData container on the watch for the raw data, a separate CloudKit raw data container, and a separate Mac raw data container - then perform the deletes upon receipt of CloudKit update notifications (i.e. not via automatic syncs). The raw data are immutable, so there's no need to deal with updates. I'd appreciate advice/suggestions on a workable solution. Regards, Michaela
1
0
1.1k
Sep ’21
SwiftUI dynamic number of columns with LazyVGrid
As a septuagenarian my memory is prone to leaks and I therefore rely on this forum and Stack Overflow for discovering (rediscovering?) solutions to problems. I like to give back when I can, so here goes..... 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
Replies
2
Boosts
0
Views
3.4k
Activity
Mar ’22
Creating an MLFeatureProvider class in iOS for an MLModel
Most examples, including within documentation, of using CoreML with iOS involve the creation of the Model under Xcode on a Mac and then inclusion of the Xcode generated MLFeatureProvider class into the iOS app and (re)compiling the app.  However, it’s also possible to download an uncompiled model directly into an iOS app  and then compile it (background tasks) - but there’s no MLFeatureProvider class.  The same applies when using CreateML in an iOS app (iOS 15 beta) - there’s no automatically generated MLFeatureProvider.  So how do you get one?  I’ve seen a few queries on here and elsewhere related to this problem, but couldn’t find any clear examples of a solution.  So after some experimentation, here’s my take on how to go about it: Firstly, if you don’t know what features the Model uses, print the model description e.g. print("Model: ",mlModel!.modelDescription). Which gives Model:   inputs: (     "course : String",     "lapDistance : Double",     "cumTime : Double",     "distance : Double",     "lapNumber : Double",     "cumDistance : Double",     "lapTime : Double" ) outputs: (     "duration : Double" ) predictedFeatureName: duration ............ A prediction is created by guard **let durationOutput = try? mlModel!.prediction(from: runFeatures) ** …… where runFeatures is an instance of a class that provides a set of feature names and the value of each feature to be used in making a prediction.  So, for my model that predicts run duration from course, lap number, lap time etc the RunFeatures class is: class RunFeatures : MLFeatureProvider {     var featureNames: Set = ["course","distance","lapNumber","lapDistance","cumDistance","lapTime","cumTime","duration"]     var course : String = "n/a"     var distance : Double = -0.0     var lapNumber : Double = -0.0     var lapDistance : Double = -0.0     var cumDistance : Double = -0.0     var lapTime : Double = -0.0     var cumTime : Double = -0.0          func featureValue(for featureName: String) -> MLFeatureValue? {         switch featureName {         case "distance":             return MLFeatureValue(double: distance)         case "lapNumber":             return MLFeatureValue(double: lapNumber)         case "lapDistance":             return MLFeatureValue(double: lapDistance)         case "cumDistance":             return MLFeatureValue(double: cumDistance)         case "lapTime":             return MLFeatureValue(double: lapTime)         case "cumTime":             return MLFeatureValue(double: cumTime)         case "course":             return MLFeatureValue(string: course)         default:             return MLFeatureValue(double: -0.0)         }     } } Then in my DataModel, prior to prediction, I create an instance of RunFeatures with the input values on which I want to base the prediction: var runFeatures = RunFeatures() runFeatures.distance = 3566.0 runFeatures.lapNumber = 1.0 runFeatures.lapDistance = 1001.0  runFeatures.lapTime = 468.0  runFeatures.cumTime = 468.0  runFeatures.cumDistance = 1001.0  runFeatures.course = "Wishing Well Loop" NOTE there’s no need to provide the output feature (“duration”) here, nor in the featureValue method above but it is required in featureNames. Then get the prediction with guard let durationOutput = try? mlModel!.prediction(from: runFeatures)  Regards, Michaela
Replies
1
Boosts
1
Views
2.0k
Activity
Mar ’22
TabularData Framework: DataFrame as a List in SwiftUI
The new TabularData framework in iOS 15, MacOS 12 and watchOS 8 opens up opportunities for easier, more efficient ingestion of data (and for ML possibilities). However, it does not appear to be possible to directly use a DataFrame's rows for a List or ForEach in SwiftUI: the compiler gives an error that the rows do not conform to RandomAccessCollection Protocol. The documentation for Rows does not state compliance with such, even through there are methods which inherit from the protocol. Using a separate iteration to extract each Row (as a DataFrame.Row) from the DataFrame into a new array works. I make this array identifiable by using the row.index as the id. However, if the DataFrame is large this adds considerably to storage and processing overheads. Any thoughts on directly using the DataFrame's Rows in SwiftUI?
Replies
2
Boosts
0
Views
3.3k
Activity
Mar ’22
Changing attribute type in a PersistentCloudKitContainer Model
I have a Multiplatform app using a PersistentCloudKitContainer Model that has a complex object graph and which is still in Development mode (including in CloudKit). An integer attribute of one of the entities needs to be changed to double, for reasons that were not obvious on first analysis of data. According to the Core Data Migration documentation , which is 10 years old, this case would not be Lightweight Migration and so I would need to create a Migration Policy and Process. Although I've only recently imported a few legacy records that set the Int attribute (now required to be Double), there are several hundred entities (imported legacy data) with a default Int value (0). Tempting though it is to just change the attribute type from Int to Double and see what happens, recreating the model and hundreds (thousands?) of records if things go wrong would be a real pain in the derriere. So, given the complexity of my model and the use of CloudKit for multi-device synching, I'm thinking I'll create a new Double attribute and reimport data into that, then when all's fine delete the original Int attribute. This way both sets of changes are Lightweight. Advice? Thoughts? Regards, Michaela
Replies
0
Boosts
0
Views
925
Activity
Jan ’22
Calendar Component Quarter always returns zero
I'm doing some summarising of dated data from Coredata using @SectionedFetchRequest and SwiftUI List with Section headers. Summary options are by week, month, quarter and year, with processing by Calendar Component. All works fine (with a bit of extra processing for the week period description) except for quarter, which always returns zero instead of 1 to 4. When I finally decided to look at the documentation https://developer.apple.com/documentation/foundation/calendar/component/quarter there's an Important note saying "The quarter unit is largely unimplemented, and is not recommended for use.". So why have that enumeration if it's known not to work? OK, so I now get the month and then determine the quarter with a switch statement.......
Replies
2
Boosts
0
Views
1.3k
Activity
Oct ’21
DateComponentsFormatter UnitsStyle .abbreviated in MacOS
According to the documentation for DateComponentsFormatter UnitsStyle .abbreviated https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle, this style (abbreviated) should create a string like “9h 41m 30s”. In some circumstances, eg MacOS target with import Foundation, the result is "9h 41min. 30s.", which is a sort-of mix between .brief and .abbreviated. In my SwiftUI multi-platform app (MacOS & iOS) the incorrect (mixed) style is generated for both platforms. Using Playground, the mixed formatting occurs when the platform is set to MacOS, but not iOS with either import UIKit or import Foundation. Is this a bug, or a "featured" difference between MacOS and iOS? My multi-platform function is import Foundation public func strDuration(_ duration: TimeInterval, style: DateComponentsFormatter.UnitsStyle = .abbreviated) -> String {         let formatter = DateComponentsFormatter()         formatter.allowedUnits = [.hour, .minute, .second]         formatter.unitsStyle = style         formatter.maximumUnitCount = 3         return formatter.string(from: duration) ?? "n/a"     } Regards, Michaela
Replies
6
Boosts
0
Views
1.6k
Activity
Sep ’21
Multi-platform CoreData synchronisation: deleting data from all devices except one?
I'm currently developing a multi-platform app where a standalone Watch app collects data, summarises it, then stores the summary in CoreData. The summary gets synchronised across all devices by CloudKit. However, the raw data (e.g. RR interval data from a Polar H10 heart rate monitor) are important from an historical perspective: forming the basis of further analysis, perhaps via AI. Given the limited storage, battery and processing resources on the Watch, it would be inappropriate to persistently store, or perform complex analysis of, the raw data on the Watch. My preferred solution would be to transfer the raw data to a Mac and, once successfully received, delete it from the Watch (also from CloudKit?). The Mac would then perform further analysis and propagate an additional summary to all other devices. The problem I foresee is that, if using CoreData/CloudKit synchronisation, deleting from the Watch would delete from all other devices. With my currently limited understanding of such synchronisation, I don't see a way of not then automatically deleting the Mac's CoreData records. Perhaps the solution is to have a separate CoreData container on the watch for the raw data, a separate CloudKit raw data container, and a separate Mac raw data container - then perform the deletes upon receipt of CloudKit update notifications (i.e. not via automatic syncs). The raw data are immutable, so there's no need to deal with updates. I'd appreciate advice/suggestions on a workable solution. Regards, Michaela
Replies
1
Boosts
0
Views
1.1k
Activity
Sep ’21