Post

Replies

Boosts

Views

Activity

SwiftUI FormView not updating after value creation/updating in a SubView
I'm developing a SwiftUI, CoreData / CloudKit App with Xcode 16.2 & Sequoia 15.2. The main CoreData entity has 20 NSManaged vars and 5 derived vars, the latter being computed from the managed vars and dependent computed vars - this somewhat akin to a spreadsheet. The data entry/updating Form is complex for each managed var because the user needs to be able to enter data in either Imperial or Metric units, and then switch (by Button) between units for viewing equivalents. This has to happen on an individual managed var basis, because some source data are in Imperial and others Metric. Consequently, I use a generalised SubView on the Form for processing the managed var (passed as a parameter with its identity) and then updating the CoreData Entity (about 100 lines of code in total): i.e. there are 20 uses of the generalised SubView within the Main Form. However, none of the SubViews triggers an update of the managed var in the Form, nor computations of the derived vars. On initial load of the app, the CoreData entity is retrieved and the computations happen correctly: thereafter not. No technique for refreshing either View works: e.g. trigger based on NSManagedObjectContextDidSave; nor does reloading the CoreData entity after Context Save (CoreData doesn't recognise changes at the attribute level anyway). If the SubView is removed and replaced with code within the Form View itself, then it works. However, this will require about 40 @State vars, 20 onCommits, etc - so it's not something I'm keen to do. Below is a much-simplified example of the problem. Form{ Section(header: Text("Test Record")){ Text(testRec.dateString ?? "n/a") TextField("Notes",text:$text) .onSubmit{ testRec.notes = text dataStore.contextSave() } //DoubleNumberEntry(testRec: testRec) - doesn't work TextField("Double",value:$numDbl,format: .number) // This works .onSubmit{ testRec.dblNum = numDbl dataStore.contextSave() } TextField("Integer",value: $numInt,format: .number) .onSubmit{ testRec.intNum = Int16(numInt) dataStore.contextSave() } Text(String(format:"%.2f",testRec.computation)) Section(header: Text("Computations")) { Text(String(format:"%.2f",testRec.computation)) Text(String(format:"%.2f",testRec.anotherComputation)) } } } A much simplified version of my NSManaged var entry/updating. struct DoubleNumberEntry: View { let dataStore = MainController.shared.dataStore var testRec : TestRec @State private var numDbl: Double init(testRec: TestRec) { self.testRec = testRec self.numDbl = testRec.dblNum } var body: some View { TextField("Double",value:$numDbl,format: .number) .onSubmit{ testRec.dblNum = numDbl dataStore.contextSave() } } } I'd appreciate any offered solution or advice. Regards, Michaela
1
0
256
Nov ’24
Polar H10 ECG reading
There are some reliable and affordable Polar H10 ECG reader apps available on the App Store: I’ve been using one for a couple of years. However, I recently needed to incorporate an ECG capability into an app that already uses the Polar H10 for RR Interval monitoring, but the documentation online for Polar ECG is scarce and sometimes incorrect. Polar provides an SDK, but this covers many different devices and so is quite complex. Also, it’s based on RxSwift - which I prefer not to use given that my existing app uses native Swift async and concurrency approaches. I therefore offer this description of my solution in the hope that it helps someone, somewhere, sometime. The Polar H10 transmits ECG data via Bluetooth LE as a stream of frames. Each frame is length 229 bytes, with a 10 byte leader and then 73 ECG data points of 3 bytes each (microvolts as little-endian integer, two’s complement negatives). The leader’s byte 0 is 0x00, bytes 1 - 8 are a timestamp (unknown epoch) and byte 9 is 0x00. The H10’s sampling rate is 130Hz (my 2 devices are a tiny fraction higher), which means that each frame is transmitted approximately every half second (73/130). However, given the latencies of bluetooth transmission and the app’s processing, any application of a timestamp to each data point should be based on a fixed time interval between each data point, i.e. milliseconds interval = 1000 / actual sampling rate. From my testing, the time interval between successive frame timestamps is constant and so the actual sampling interval is that interval divided by 73 (the number of samples per frame). I’ve noticed, with both the 3rd party app and my own coding, that for about a second (sometimes more) the reported voltages are very high or low before settling to “normal” oscillation around the isoelectric line. This is especially true when the sensor electrode strap has only just been placed on the chest. To help overcome this, I use the Heart Rate service UUID “180D” and set notify on characteristic "2A37" to get the heart rate and RR interval data, of which the first byte contains flags including a sensor contact flag (2 bits - both set when sensor contact is OK, upon which I setNotifyValue on the ECG data characteristic to start frame delivery). Having discovered your Polar H10, connected to it and discovered its services you need to discover the PMD Control Characteristic within the PMD Service then use it to request Streaming and to request the ECG stream (there are other streams). Once the requests have been accepted (didWriteValueFor Characteristic) then you start the Stream. Thereafter, frames are delivered by the delegate callback func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) for the characteristic.uuid == pmdDataUUID The following code snippets, the key aspects of the solution, assume a working knowledge of CoreBluetooth. Also, decoding of data (code not provided) requires a knowledge of byte and bit-wise operations in Swift (or Objective-C). // CBUUIDs and command data let pmdServiceUUID = CBUUID.init( string:"FB005C80-02E7-F387-1CAD-8ACD2D8DF0C8" ) let pmdControlUUID = CBUUID.init( string:"FB005C81-02E7-F387-1CAD-8ACD2D8DF0C8" ) let pmdDataUUID = CBUUID.init( string:"FB005C82-02E7-F387-1CAD-8ACD2D8DF0C8" ) let reqStream = Data([0x01,0x02]) let reqECG = Data([0x01,0x00]) let startStream = Data([0x02, 0x00, 0x00, 0x01, 0x82, 0x00, 0x01, 0x01, 0x0E, 0x00]) // Request streaming of ECG data func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) if service.uuid == pmdServiceUUID { for pmdChar in service.characteristics! { if pmdChar.uuid == pmdControlUUID { peripheral.setNotifyValue(true, for: pmdChar) peripheral.writeValue(reqStream, for: pmdChar, type: .withResponse) peripheral.writeValue(reqECG, for: pmdChar, type: .withResponse) } } } } // Request delivery of ECG frames - actual delivery subject to setNotify value func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { // this responds to the reqStream and reqECG write values if error != nil { print("**** write error") return } if ecgStreamStarted { return } // I use a flag to prevent extraneous stream start commands guard let charVal = characteristic.value else { return } if charVal[0] == 0xF0 && charVal[1] == 0x01 { peripheral.writeValue(startStream, for: characteristic, type: .withResponse) ecgStreamStarted = true } } For “live” charting, I create an array of data points, appending each frame’s set on arrival, then provide those points to a SwiftUI View with a TimeLineView(.periodic(from: .now, by:actual sampling interval)) and using Path .addlines with the Y value scaled appropriately using GeometryReader. So far, I’ve found no way of cancelling such a TimeLineView period, so any suggestions are welcome on that one. An alternative approach is to refresh a SwiftUI Chart View on receipt and decoding of each frame, but this creates a stuttered appearance due to the approximately half-second interval between frames. Regards, Michaela
0
0
577
Aug ’24
How to fix NSCloudKitMirroringDelegate unhandled exception after faulty model change
I have a complex data model in development mode, with a large amount of data, using CoreData with CloudKit sync. All worked fine, syncing from a Mac to an iPad Pro, until I made some unwise changes to the model and a couple of relationships. I should have known, but was hurrying and not thinking clearly. The App is not on the App Store: it's for my own use. Records are now not synced to CloudKit: _error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _importFinishedWithResult:importer:]- (1371): <PFCloudKitImporter: 0x6000005d8080>: Import failed with error: Error Domain=NSCocoaErrorDomain Code=134421 "Import failed because applying the accumulated changes hit an unhandled exception." UserInfo={NSLocalizedFailureReason=Import failed because applying the accumulated changes hit an unhandled exception., NSUnderlyingException=* -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]}_** It seems that there's a queue of faulty (non-matching) updates, which I can find no way of purging. Perhaps I could disconnect the CloudKit syncing in the app, use the CloudKit console to reset the development environment, then reconnect CloudKit syncing in the app - but would that invoke a repopulation of the CloudKit data, or cause a deletion of all CoreData data? I can, with a few days' work, reimport all of the data - but that would, I assume, need a newly named app so as to create a new CloudKit container. Any thoughts/solutions would be appreciated. Regards, Michaela
4
0
1.1k
Sep ’23
Does iPadOS really exist? If so, why is there no Xcode compiler directive for it?
For some time now Apple has been referring to iPadOS (eg in https://developer.apple.com/download/ ), but there is no compiler directive in Xcode to check for iPadOS - even though Xcode now has supported destinations for iPhone, iPad and Mac (catalyst). I have a couple of use cases where I'd really prefer to include/exclude large chunks of code (frameworks) based on whether the device is an iPad or an iPhone, not just because of the different user-interface characteristics - which can be accommodated using the UIUserInterfaceIdiom. Regards, Michaela
1
0
1.1k
Jul ’22
Sub-second X AxisMarks in SwiftUI Charts time series line chart
I already have a working app, using SwiftUI Canvas, for charting sensor milliVolt data at a frequency of 130Hz, so thought I'd see how SwiftUI Charts compared for ease of implementation. Charts is much easier to implement, saving the tedium of scaling and label context placement. However, creating the AxisMarks.Values was a problem because the stride-by method with Calendar.Component has .second as the smallest unit, and I need deciSecond. I tried some other inbuillt options, e.g. desiredCount, but no joy. After a while I realised that I can create my own values array using a function, so here it is (hoping that this helps someone, somewhere, sometime): struct ChartView: View {     var chartData : [DataPoint] // An array of CoreData objects with timeStamp and milliVolt attributes let xSecStyle = StrokeStyle(lineWidth: 1.0)     let xDeciSecStyle = StrokeStyle(lineWidth: 0.5) ... // The .chartAxis code within Chart in body .chartXAxis {                 AxisMarks(values: deciSecValues) { value in                     if Double(value.index).remainder(dividingBy: 10.0)  == 0.0 { // show the second-gridline as wider and display the time label                         AxisGridLine(stroke:xSecStyle)                                 .foregroundStyle(.orange)                         AxisValueLabel(format: .dateTime.minute().second(),centered: false)                     } else {                         AxisGridLine(stroke:xDeciSecStyle)                                 .foregroundStyle(.orange)                     }                 }             } ..... // The func for creating the X Axis Marks private var deciSecValues : [Date] {         var xDates = [Date]()         if chartData.isEmpty { return xDates }         let deciSecs = (Int(chartData.last!.timeStamp!.timeIntervalSince(chartData.first!.timeStamp!)) + 1) * 10         for i in stride(from: 0, to: deciSecs, by: 1) {             let newTime = Double(i) / 10.0             xDates.append(chartData.first!.timeStamp!.addingTimeInterval(newTime))         }         return xDates     } Regards, Michaela
0
1
1.4k
Jul ’22
MacOS HTTP Listener for a Swift App
I have a wireless stateless-switch within my private network (LAN) which transmits HTTP posts to a configured URL (e.g. an always-on MacMini) about the switch's state. Upon receipt of a post, my MacOS app will then take appropriate action. No response to the sender is required. The frequency of posts will be minimal ( a few per day), but require immediate attention when received. The LAN is protected from external misuse by a secure gateway. I'd appreciate any suggestions for a lightweight solution (i.e. not a full-blown web-server), either as an overview of methods or sample source (e.g. on GitHub). Regards, Michaela
3
0
2.8k
May ’22
'Transaction' is ambiguous for type lookup in this context - SwiftUI
I'm developing a SwiftUI multi-platform, multi-user app for family budget management (not for the App Store) using CoreData and iCloud with NSPersistentCloudKitContainer. I use manual Codegen in Xcode to generate the CoreData entity classes, then add extensions for computed properties. These are in my DataModel (ViewModel), which is in an included framework. All data processing is done in the data model. All's working fine in the SwiftUI Views, except for one entity - 'Transaction', which throws a compiler error "'Transaction' is ambiguous for type lookup in this context". Some SO posts say to use the App Name as a prefix to the type, but this doesn't work. What does solve it, in this case, is to use the name of the Framework (Library) holding the type definition: import SwiftUI import OurMoneyLib // my framework holding the DataModel and CoreData entity classes struct TransactionRow: View {     let appAPI = AppAPI()     var transaction : OurMoneyLib.Transaction var body: some View { ...... Why this one entity throws an error, I know not - but it's fixed! I hope this helps someone, somewhere, sometime. Cheers, Michaela
1
0
2.6k
Mar ’22
Mac Catalyst Horizontal Size Class for .sheet
Mac Catalyst treats a SwiiftUI .sheet View as compact, even though there is as much horizontal space in the view as on a physical iPad. The outcome of this is that, with an iPhone, iPad, Catalyst app, abbreviated HStacks that I use for the iPhone version also appear on the Catalyst version, unless I use #if targetEnvironment(macCatalyst) A few months back a Stack Overflow user complained that a Catalyst popover (UIKit) was reporting as .regular and should be .compact. Apple agreed it was a bug. However, in my case (SwiftUI sheet), it doesn't make sense to regard a Catalyst SwiftUI sheet as compact when there is as much space available as on a physical iPad. Regards to all, Michaela
1
0
1.6k
Feb ’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
751
Jan ’22
MacOS MapKit - CVDisplayLinkSetPaused alternating true/false several times per second
When using MKMapView within SwiftUI in MacOS, via NSViewRepresentable and a Coordinator, the are several console messages per second "CVDisplayLinkSetPaused", alternating between [TRUE]and [FALSE] when displaying a poly line and/or annotations. This is possibly preventing NSClickGestureRecognizer from working correctly, although that's probably another, developer (my), issue. These console messages have only occurred in the last two Mac OS beta releases, the last being 21D5039d. Even if these messages are not an indication of a problem, they are very distracting and annoying - taking up so much time and space in the logs. Feedback submitted: FB9845486
11
0
2.5k
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.1k
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.3k
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
902
Sep ’21
Import SQLite legacy data into CoreData
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
1
0
1.1k
Sep ’21
Big Sur update and Monterey install stuck
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.
0
0
603
Aug ’21