WCSession sendmessage from iPhone to Watch is working (Beta 1 to 3), but I've not been able to get a message from the Watch to the Phone. Watch to phone doesnt work in the sample app (Potloc) either, although under current betas (XCode, iOS and Watch) Potloc will not install on the watch and I haven't bothered to find out why.I can't get debug to work with the Watch app, despite workarounds suggested elsewhere, so it's hard to tell what's going on with WCSession.Has anyone got WCSession sending messages from watch to phone?Cheers.
Post
Replies
Boosts
Views
Activity
I'm devloping an iOS App for the Polar H7 and H10 Heart Rate sensors and it's crucial that the App monitors the attached device's battery level.For ayone who needs to get the battery level from a BLE device (if it supports such), the Battery Service UUID is 0x180F and the battery level characterisitc UUID is 0x2A19, reporting a one byte value with the battery level as a percentage (UInt8). Details are at https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.battery_service.xmlIn my Swift 3 code, having discovered the Service and the Characteristic (2A19), I do a peripheral.readValue(for: battChar) then a peripheral.setNotifyValue(true, for: battChar). The value is then provided in the didupdatevalue for characterisitc function. Not surprisingly, my Polar devices do not constantly update the battery status but must be interrogated from time to time using the peripheral.readvalue(for: ...) function.I hope this helps someone, somewhere, sometime. 🙂
I often have need for multi-segment (hierarchical) pickers with dynamic data: in UIKit I achieve this by reloading a subsidiary component after the user picks a value from the higher level component, e.g. for Country then State.I've been trying to achieve the same sort of effect with a SwiftUI Form containing multiple (two to start with) Pickers, but without success. The ForEach(0 ..< elements.count) pattern doesn't work with second and subsequent levels in the hierarchy because the number of elements in these levels changes depending on the chosen top level, e.g. countries have different numbers of states. The compiler throws a warning that the ForEach(0 ..< ) pattern can only be used with constants and to use ForEach(array, id: ): the results are unpredictable anyway with the 0..< approach, with subscript out of range crashes.I can get correct display of second level choices based on the selected first level, but the Selection binding of the Picker doesn't work, i.e. no check marks and no display of selected element - only the available choices: Picker(selection: $selection, label: Text("SubGroup")) { ForEach(subGroups, id: \.id) { subGroup in Text(subGroup.element) } }With the ForEach(0 ..< pattern, the $selection refers to an Int, whereas using an Identifiable array the selection is (presumably) based on the id. But how to specify the binding for an id-based selection? Indeed, is this possible at all? I've seen a few posts on the Web querying this, with no answer.UPDATE: OK, fixed it 🙂 I'd been using UUID as the id for each group and each subgroup within a group. When I use a unique integer id (or double, e.g. 1.1, 1.2 etc for two-level hierarchy) in an identifiable model, everything is fine. However, my code needs to provide the id of the initial selected (first?) row of the lower-level Picker when the higher level has changed.Regards,Michaela
I'm working on a cross-platform app that displays time-dependent information via SwiftUI Views, updated automatically or by user interaction. For the tvOS version (which is now working as a full-screen app on tvOS13.4) I'd like the ability to show the app's data in PIP when the user navigates to a different app (such as a TV program) or, better still, as a rolling banner at the bottom of the screen.
However, my understanding of the tvOS 14 beta implementation of Picture-in-Picture is that an app invoking PIP must play video content, not the non-media content of updating SwiftUI Views. Is this correct? Is there any way of keeping an app active, but minimised, when the user invokes another, unrelated, app?
An event created in Apple's Calendar app can have a user-supplied address ("Location"), which gets geocoded into the EKEvent record. These coordinates are then accessible via EventKIt in EKEvent.structuredLocation, but the original (or derived) address is not - even though a debug console print of a structuredLocation shows the address.
Is here any way of Swift accessing the stored address? Obviously, an address can be created by reverse geocoding, but this consumes resources unnecessarily and might not return the "correct" address.
Regards, Michaela
When an iOS 14 beta device is connected to my MacBook running Big Sur 11 beta 10, files can be transferred to the iOS device via Finder, but not from iOS to the MacBook. No error is given and the transfer drag-and-drop appears to have worked as normal, except the file doesn't get transferred. It worked in the previous beta of Big Sur, although was problematic in earlier betas.
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
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?
The TabularData DataFrame writeCSV method formats Double (and possibly Integer) numbers that are >= 1000 with a comma thousands separator and surrounds the output in double quotes. Numbers less than 1000 are not double-quoted. If the resulting CSV file is then read by DataFrame contentsOfCSVFile an error is thrown, i.e. a type mismatch. This happens irrespective of whether the types option is set in the CSV read, or not.
There is no option in DataFrame writeCSV to specify no thousands separator, nor any other obvious way of preventing the double-quoting.
Currently, I "fix" the problem by reading the CSV in Numbers, setting the faulty column(s) to Numeric then exporting back to CSV.
Any thoughts on preventing the issue in the first place?
It seems that a DataFrame (TabularData framework) can be used in CreateML, instead of an MLDataTable - which makes sense, given the description of the TabularData API. However, there are differences.
One is that when using a DataFrame, the randomSplit method creates a tuple of DataFrame slices, which cannot then be used in MLLinearRegressor without first converting back to DataFrame (i.e. initialising a new DataFrame with the required slice). Using an MLDataTable as the source data, the output from randomSplit can be used directly in MLLinearRegressor.
I'm interested to hear of any other differences and whether the behaviour described above is a feature or a bug.
TabularData seems to have more features for data manipulation, although I haven't done any systematic comparison. I'm a bit puzzled as to why there are 2 similar, but separate, frameworks.
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
DataFrame(contentsOfJSONFile:url) (and it's MLDataTable equivalent) assumes that the rows to be imported are at the root level of the JSON structure. However, I'm downloading a fairly large dataset that has "header" information, such as date created and validity period, with the targeted array at a subsidiary level. The DataFrame initialiser has JSONReadingOptions, but these don't apply to this situation (i.e. there's nothing that helps).
It seems that I'm faced with the options of 1) stripping the extraneous data from the JSON file, to leave just the array or 2) decoding the JSON file into a bespoke struct then converting its array into a DataFrame - which removes a lot of the benefits of using DataFrame in the first place.
Any thoughts?
Cheers, Michaela
SwiftUI Map() doesn't provide a way of changing mapType from .standard. Some older (1yr+) StackOverflow posts claim being able to set mapType by using a custom initialiser or an extension, but neither approach seems to work for my needs and set up (Swift 5, iOS 15 beta, Xcode 13).
When I use .onAppear() { MKMapView.appearance().mapType = mapStyle}, where mapStyle is a var of the View, the style is set (e.g. to .hybrid) successfully on the first invocation of the view (the map) but not subsequent ones, which are as .standard. I can't think of any way of solving this.
I could use MapKit with UIViewRepresentable, which I've done a few times before with other projects, but it'd be nice to use "pure" SwiftUI.
Any ideas?
Cheers, Michaela
PS - the reason for wanting satellite or hybrid is that I often need to return to a location in a forest area with poor, or non existent, trail mapping.
I had a Big Sur MacMini update (non Beta) get stuck and nothing would fix it, so I then decided to go to Monterey (beta). After some initial problems, related to a Big Sur update waiting in the wings, the Monterey installation started - and then got stuck. The same problem: got so far then no further, time after time. I tried Safe Mode start-ups, but to no avail.
During a shutdown, which also seemed to be blocked, I became aware of HDD noise from the networked backup drive. A backup was in progress! When I turned off automatic backups in TimeMachine (and disconnected the drive), the Monterey install worked - and I suspect the Big Sur update would have done also.
I'm currently rewriting an app developed over many years in Objective-C, Swift, SwiftUI, and an SQLIte database. Until now, I'd avoided CoreData because I'd been using SQL databases for decades (so was comfortable) and had concerns with CoreData's use of NS types. However, the new app will be multi-platform and share/sync data via CloudKit - which will be a pain in the derriere with SQLite (though not insurmountable). So, I'm going to do a test import of two related entities, 1,000 records and 4,500 records, to see how things go. There are more tables, but this will be just a test.
My intended process is:
Export the SQLite tables, "main" and "details", separately, to CSV. There are linking IDs.
In the new app, load each CSV file into DataFrames using the TabularData framework.
For each row in the "main" DataFrame, filter the "details" DataFrame rows on the "main"'s ID, then create Core Data's "main" entity along with the associated "detail" entities.
Do a context.save() - after each "main" or in batches?
Is there a better (less coding?) way?
My understanding is that I can't just populate each set of entities separately and then expect an automatic creation of the relationships. I've seen reference on the Web of Core Data "linking attributes", rather like joins in SQL, but I see no mention of such in the Xcode DataModel builder, nor class definitions. Am I correct in this understanding?
Regards, Michaela