Post

Replies

Boosts

Views

Activity

Reply to Scrollable Plot Area
I'm about to implement a similar use-case: I already chart data for the current week, current month, quarter and year, as selected by a picker. The key to the paging solution is to replace the chart dataset (series), when the user swipes left or right, with the appropriate next or previous set (period) of data. In my case, I'll use my existing Coredata fetch function with startDate, endDate parameters - called from within the Gesture Recogniser. We'll need a var with the currently displayed period in order to set the required retrieval startDate, endDate. And there also needs be a @State var to trigger a chart redraw once the new dataset has been returned (maybe the dataset itself or the period or a flag). Or, the period and dataset creation/recreation can be handled in the DataModel (my usual approach) with @Published vars. This should work fine for discrete periods (i.e. week, month, etc), but continuous scrolling over a large dataset is a different proposition. I've done it with a SwiftUI Canvas app, but it was hard to get smooth scrolling with many fetches from a large database. I'll post more when my paging solution works (or doesn't......). Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Sep ’22
Reply to Will AFib History be an open data source?
I can confirm that current versions of iOS and Watch OS (14+ and 7+) provide access to ECG classifications (e.g. Atrial Fibrillation). The relevant code is: let ecgType = HKObjectType.electrocardiogramType() let ecgPredicate = HKQuery.predicateForElectrocardiograms(classification: .atrialFibrillation) let ecgQuery = HKSampleQuery(sampleType: ecgType,                                      predicate: ecgPredicate,                                      limit: HKObjectQueryNoLimit,                                      sortDescriptors: nil) { (query, samples, error) in ............... If you then need to access the actual ECG Voltages (500 measurements per second, I think) for a sample: let voltageQuery = HKElectrocardiogramQuery(ecgSample) { (query, result) in             switch(result) {             case .measurement(let measurement):                 if let voltageQuantity = measurement.quantity(for: .appleWatchSimilarToLeadI) { // process each voltage measurement ........... } // Execute the query.         healthStore.execute(voltageQuery) Regards, Michaela
Aug ’22
Reply to Combine Duplicate Items in an Array
Create a function in your PersistenceController class to detect and remove duplicates: func deDup(_ items: [Item]) { let context = container.viewContext var prevItem : Item? for item in items { if prevItem == nil { prevItem = item continue } if item.name! == prevItem!.name! { // this is a duplicate prevItem!.value += item.value context.delete(item) } else { prevItem = item } } contextSave() } Change you combineItemsButton action to be Button(action:{ //combine duplicates here             persistence.deDup(items)             items = persistence.getItems() I have not tested this within any app (i.e. just written the code here) and have done it in a hurry, but I think that the logic is correct. I hope it works!!!! Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to Calendar with Correlating Data. Please Help!!!
Main Struct @main struct TestForWeagleWeagleApp: App {     let persistence = PersistenceController.shared  // initiates the CoreData stack     var body: some Scene {         WindowGroup {             ContentView()         }     } } Persistence import Foundation import CoreData class PersistenceController : ObservableObject {     static let shared = PersistenceController()     let container: NSPersistentContainer     init(inMemory: Bool = false) {         container = NSPersistentContainer(name: "Test")         if inMemory {             container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")         }         container.loadPersistentStores(completionHandler: { (storeDescription, error) in             if let error = error as NSError? {                 fatalError("Unresolved error \(error), \(error.userInfo)")             }         })         container.viewContext.automaticallyMergesChangesFromParent = true     }     func addItem(date: Date){         let context = container.viewContext         let item = Item(context: context)         item.timestamp = date         item.word = nil         contextSave()     }     func addToItem(item: Item) {         item.word = "Test"         contextSave()     }     func removeFromItem(item: Item){         item.word = nil         contextSave()     }     func contextSave() {         let context = container.viewContext         if context.hasChanges {             do {                 try context.save()                 self.objectWillChange.send()             } catch {                 print("**** ERROR: Unable to save context \(error)")             }         }     }     func getItemsFor(_ date: Date) -> [Item] {         let context = container.viewContext         var request = NSFetchRequest<Item>()         request = Item.fetchRequest()         //request.fetchLimit = 1         request.entity = NSEntityDescription.entity(forEntityName: "Item", in: context)         request.predicate = NSPredicate(format: "timestamp >= %@ and timestamp <= %@", Calendar.current.startOfDay(for: date) as CVarArg, Calendar.current.startOfDay(for: date).addingTimeInterval(86399.0) as CVarArg)         do {             let items = try context.fetch(request)             if items.count == 0 { return []}                   return items.sorted(by: {$0.timestamp! > $1.timestamp!})         } catch {             print("**** ERROR: items fetch failed \(error)")             return []         }     } } ContentView import SwiftUI struct ContentView: View {     @ObservedObject var persistence = PersistenceController.shared     @State private var items = PersistenceController.shared.getItemsFor(Date())     @State private var date = Date.now     var body: some View {         NavigationView{                 VStack {                     DatePicker("Calendar", selection: $date, in: Date.now...,displayedComponents: [.date])                     .datePickerStyle(.graphical)                     .onAppear(perform: {                         if items.isEmpty {                             persistence.addItem(date: date)                             items = persistence.getItemsFor(date)                         }                     })                 if !(items.isEmpty) {                     PlannedMealsView(item: items.last!)                     Spacer()                 }             }                  .navigationBarTitle("My Planner")         }         .onChange(of: date){ newDate in             items = persistence.getItemsFor(newDate)             if items.isEmpty {                 persistence.addItem(date: newDate)                 items = persistence.getItemsFor(newDate)             }         }     }     func getTitle(date: Date)->String{         let formatter = DateFormatter()         formatter.dateStyle = .medium         return formatter.string(from: date)     } } PerformanceMealsView import SwiftUI struct PlannedMealsView: View {     @ObservedObject var persistence = PersistenceController.shared     var item: Item     @State private var forceRefresh : Bool = false     var body: some View {         VStack{             Text(item.timestamp!, style: .date)                 .font(.title2)                 .bold()             Section("Word"){                 if(item.word != nil){                     HStack{                         Spacer()                         Text(item.word!)                         Spacer()                         Button(action: {                             persistence.removeFromItem(item: item)                         }){                             Image(systemName: "minus.circle").bold()                         }                         Spacer()                     }                 } else {                     Button(action: {                         persistence.addToItem(item: item)                         forceRefresh.toggle()                     }){                         Image(systemName: "plus.circle").bold()                             .padding(.vertical, 10)                             .padding(.horizontal, 20)                     }                 }             }             Spacer()         }     } } This works on my system, except that (probably unwisely) I used my development environment, which uses beta Xcode and beta iOS. I couldn't backwards convert the Xcode project (new format) to test on my production equipment without redoing everything. I hope this works for you too!!! Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to Calendar with Correlating Data. Please Help!!!
As per my comment on your other post, I've had a good look at your code and make the following observations: Your PersistenceController is a struct and you reference it a number of times in your Views e.g. PersistenceController().removeFromItem(item: items[0], context: managedObjContext), which means that your CoreData stack is being recreated each time (ie numerous copies) - with unpredictable results. The PersistenceController needs to be an ObservableObject class singleton, i.e. with let shared =PersistenceController(), and then refer to the shared instance. the date that you set from the calendar picker is the time of picking, i.e. date and time, so your predicate, which also uses the current date and time, will probably never match. I assume that you're looking for an item (or items) that occur on a selected day (date only, not time). The predicate therefore needs to search for a timestamp that occurs within the start and end of a day (date). It's not clear where, or if, you created the @StateObject for the @Environment(\.managedObjectContext) var managedObjContext, without which the @FetchedResults are unlikely to work (plus the problem of multiple CoreData stack instances). When the above issues are resolved, there remains the problem of getting SwiftUI to re-execute the Fetch on a change of date i.e. a dynamic predicate with immediate effect. I've created a working version of your code, but without the FetchRequest and Results in ContentView: I use a fetch function in PersistenceController to return Items with a timestamp that falls within the specified day (midnight to 11:59:59pm). When the selected date changes, the function gets called to return the item(s) for that date. That's the item (or item array) that then gets used in your existing code. I'll post the full solution tomorrow morning (about 00:00 UTC Sunday 26 June ) after I've further tested it. Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to View References
I haven’t done this in UIKit, but in SwiftUI I pass the entity to the second view, modify the attribute on that entity, then perform a Context save of the Entity (by using a function in my CodeData stack). CoreData entities are classes and therefore get passed by reference. Having modified and saved the Entity + attribute, you then probably need to force a view refresh, which can be tricky depending on your implementation of CoreData in your views and whether UIKit or SwiftUI. regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to Will AFib History be an open data source?
I’ve recently started looking at retrieving ECG data in HealthKit, as currently recorded by my Watch (mine’s on 8.7). I already have a database of ECG recordings from my Polar H10 strap and want to incorporate the Watch data (from a different lead position). i note that HealthKit now has an HKElectrocardiogram sample [https://developer.apple.com/documentation/healthkit/hkelectrocardiogram), which has a property for classification e.g. “atrialFibrillation”. So, we should be able, now, to do an HKQuery to retrieve AFib classifications with or without the voltage data. I haven’t tried yet, but soon will. I hope this helps. Regards, Michaela PS I’ve developed an ECG Viewer with SwiftUI Canvas for the voltage time series ( currently Polar H10 data) and will be adding the capability for plotting the Watch data.
Jun ’22
Reply to Master-Detail with CoreData in SwiftUI?
I had similar problems with an expenses management app, where I have a hierarchy of Groupings (master) with Transactions (detail) against the lowest Grouping in the hierarchy.  The app uses CoreData with CloudKit sync and is multi-platform., both of which add complexity to the design approach. My solution is this: In the CoreData model there are two entities, Grouping and Transaction: with a one-to-many relationship from Grouping to Transaction and one-to-one reciprocal from Transaction to Grouping.  A Fetch of a Grouping (master), or set of Groupings, therefore has the transactions (detail) as an NSManaged NSSet on each Grouping (master) record.  In the Xcode editor of the CoreData model I use Manual Codegen to put the NSManagedObject entity, properties and accessors in my project.  This allows for easier coding of extensions to create computed properties on the CoreData entities, e.g. summing transactions for a Grouping. I have a DataStore ObservableObject class singleton that handles all CoreData / CloudKit setup and data processing, e.g. all Fetches, insertions, deletions etc.  For example, there is a DataStore method to fetch all Groupings (master records) “getAllGroupings” which returns an array of CoreData Grouping objects, each of which has its own set of Transaction records (detail).  So, in a SwiftUI View, I have a @ObservedObject var dataStore = DataStore.shared, which means that I can therefore refer to the Groupings array in a List by using List(dataStore.getAllGroupings()) …..  To get the list of Transactions in another (detail) view, I pass the required Grouping (master record) to the detail view as a var, then use List(grouping.transactionsArray) - where “transactionsArray” is a computed property on my Grouping CoreData extension that turns an NSSet into a Swift Array. **** This particular solution doesn't need datastore to be an ObservedObject, but I use that for other reasons in the app. To delete a transaction in detail view, I call a method on dataStore e.g. deleteTransaction(transaction: Transaction) which does a context.delete(transaction) and then a context save.  This deletes the transaction and its entry in the Grouping’s transaction NSSet (according to the delete rules I set in CoreData). HOWEVER: this delete method (or add a transaction) does not update the view, because the changes are made to the CoreData context, which is not directly observable by SwiftUI. So, in DataStore I have a Combine property to send a notification public let transactionDeleted = PassthroughSubject<(String), Never>()   then in the “deleteTransaction” method, I use transactionDeleted.send(“txn deleted”) - you can set up the PassthroughSubject to send whatever you want. Either in your Master View or DetailView (depends on your processing - need to experiment), have .onReceive((dataStore.transactionDeleted), perform: { transaction in             self.statusTime = Date()         }) This updates a @State var statusTime which, unfortunately, needs to be used somewhere in the view: this triggers a View refresh and, voila, the deletion no longer appears in the View. NOTE: I use public for my methods and properties in DataStore because this is a Multiplatform app and DataStore is therefore in a framework. This is also why I use Manual CodeGen in Xcode's CoreData models, i.e. to use in a framework. I also use the PassthroughSubject in non-CoreData apps where there’s complex data processing, which if ObservedObjects are used can cause unnecessary view refreshes.  In these cases, once all processing has completed I then issue the PassthroughSubject send to update the view. I hope this helps.  Regards, Michaela
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to How to Build a TCP Client Server Communication
I recently wrote an HTTP Listener (i.e. TCP based) app in Swift for MacOS and at first I didn't know how to go about it, so I posted a question here on the Forum.. I ended up using the Network framework (3rd answer in the post), which is based on a WWDC video (as referenced in the post). My code and that video should help get you started. Regards, Michaela
Jun ’22
Reply to MacOS HTTP Listener for a Swift App
OMG, the solution is very easy - at least in a prototype state. Now let's see how it performs in reality, with code for handling state changes. import Foundation import Network class HTTPServer {     static let shared = HTTPServer()     var listener : NWListener     var queue : DispatchQueue     var connected : Bool = false     let nwParms = NWParameters.tcp          init() {         queue = DispatchQueue(label: "HTTP Server Queue")         listener = try! NWListener(using: nwParms, on: 80)         listener.newConnectionHandler = { [weak self] (newConnection ) in             print("**** New Connection added")             if let strongSelf = self {                 newConnection.start(queue: strongSelf.queue)                 strongSelf.receive(on: newConnection)             }         }         listener.stateUpdateHandler = { (newState) in             print("**** Listener changed state to \(newState)")         }         listener.start(queue: queue)     }     func receive(on connection: NWConnection) {         connection.receive(minimumIncompleteLength: 0, maximumLength: 4096) { (content, context, isComplete, error) in             guard let receivedData = content else {                 print("**** content is nil")                 return             }             let dataString = String(decoding: receivedData, as: UTF8.self)             print("**** received data = \(dataString)")             connection.cancel()         }     } } This solution is a modification of a Bonjour Listener demo app in a WWDC presentation. As I said in my original question, this is for listening for infrequent HTTP packets on an internal, secure network. I wouldn't dare use it as a fully open web-server. For my purposes, the information in the incoming HTTP command is all I need: it contains the new state of the WiFi switch that is on the local network. Regards, Michaela
Topic: Programming Languages SubTopic: Swift Tags:
May ’22
Reply to Using an excel document as a database/dictionary in Swift
If your data are static, or change only infrequently, putting them into the app is fine. If the data change, and you are distributing the app to others, you will need to replace the app’s data file, recompile, and re-release the app.  There’s a way of using files from the Documents folder of iOS, or “external” files, but this can be a bit of a learning curve. Assuming that your data are static, then here’s what to do: Place your term/phrase csv file into your App’s project folder using MacOS’ Finder, preferably using Copy (not Move), so as to keep a backup copy. In Xcode, select File -> Add Files to….  From the Top Menu and a pop-up screen will appear Select your file from the list (others should be greyed out) and make sure that the Destination - copy files if needed box is ticked and the Add to targets box - YourAppName is also ticked Press the Add button (far right, bottom of pop-up) Back in Xcode’s main screen, replace the getData function in DataModel with: func getData() { // DatFrame(contentsOfCSVFile) needs a URL reference, so we need to get the app's data (Bundle resource) reference as a URL         guard let fileURL = Bundle.main.url(forResource: "TermPhrases", withExtension: "csv") else { // CAUTION - withExtension is case sensitive, so if your CSV file was created as TermPhrases.CSV then you need to use uppercase in the parameter             print("**** error getting file url")             return         }         let csvOptions = CSVReadingOptions(hasHeaderRow: true, ignoresEmptyLines: true, delimiter: ",")         do {             dataTable = try DataFrame(contentsOfCSVFile: fileURL,columns: nil, rows: nil, types: ["Term":CSVType.string,"Phrase":CSVType.string], options: csvOptions)             } catch {                 print("Failed to load datatable from CSV \(error)")                 return         }         print("**** Loaded \(dataTable.rows.count) Terms")  // check to make sure terms got loaded     } I haven't tested this with iOS (just on the Mac), but it should work - assuming that you created a new iOS project or multi-platform project with Shared resources. Best wishes and regards, Michaela
Topic: Programming Languages SubTopic: Swift Tags:
Mar ’22
Reply to Using NSSortDescriptor(key: "date", ascending: true
Further to my comment on my previous answer, a computed property (realDate) cannot be used as an NSSortDescriptor key (my mistake, sorry) but FetchedResults can be sorted, upon return, using Swift array's .sorted(by: {$0.realDate! > $1.realDate!}) and then used in your table (or whatever), but you have to be careful in converting the String date to a system date, or in dealing with nil values coming from the conversion. In my sample code for realDate, the dateFormat should be df.dateFormat = "MMM dd, yyyy" not df.dateFormat = "MMM d, yyyy". Maybe check your date strings for variations to the string format, just to be sure? I made a couple of typos when creating test data and that messed the solution up!! If you do have variations, you can test for them in the reaDate extension code and convert accordingly. Regards, Michaela
Mar ’22
Reply to Swift Code Example using map inside of forEach
Only the last iteration of thing persists because it is recreated on each iteration of i. Change the initialisation of thing to be str, i.e. to "SOME SEARCH TEXT" and the reference to str.map to be thing.map and it will work: let str = "SOME SEARCH TEXT" var thing = str let letters = ["A","E","S"] let replacements = ["a", "e", "s"]  letters.enumerated().forEach { (i,r) in         thing = String(thing.map {$0 == Character(String(r)) ? Character(String(replacements[i])) : $0});     print(thing) } If it's OK to have str mutate, then you can change str to a var and then use this code: var str = "SOME SEARCH TEXT" let letters = ["A","E","S"] let replacements = ["a", "e", "s"]  letters.enumerated().forEach { (i,r) in         str = String(str.map {$0 == Character(String(r)) ? Character(String(replacements[i])) : $0});     print(str) } Best wishes and regards, Michaela
Topic: Programming Languages SubTopic: Swift Tags:
Mar ’22
Reply to Using NSSortDescriptor(key: "date", ascending: true
There is a way of fixing the problem without restructuring your CoreData model and then recreating the CoreData records. However, this can be a bit tricky if you haven't had experience with manually generating class definitions for CoreData entities. In essence the solution is this: In Xcode's CoreData Model editor, click on the Entity you need to fix, make sure that the Inspector panel is open (far right of Xcode screen) then change the Codegen option to Manual/None (it's probably set at "Class definition"). Then (still in the Model editor) on the Xcode menu bar (far top of screen) select Editor, CreateNSManagedObject Subclass and follow the prompts to create Swift files in your project for the CoreData Entity classes. Create a new Swift file for an extension to your Entity. For example, if your Entity is called "Commitment", create a file called "Commitment_Developer" and then use code like this: import Foundation extension Commitment  {     var realDate : Date? {         guard let strDate = strAttribute else { // where "strAttribute" is the name of your CoreData date-as-string attribute             return nil         }         let df = DateFormatter()         df.timeZone = NSTimeZone.local         df.dateFormat = "MMM d, yyyy"         guard let rDate = df.date(from:strDate) else {             return nil         }         return rDate     } } Then, in your NSSortDescriptor use the "realDate" property that was created in the extension. Apart from that, your code remains the same as now - without any refactoring of your CoreData model or actual data. You also have the ability to use the "realDate" elsewhere whenever you need access to a system date, as opposed to a string. Note that "realDate" is Optional, so needs to be unwrapped upon use. It could be defined as non-optional, if you're really confident in the integrity of the string-dates, but then there's a problem if you later try to use iCloud syncing with non-optionals. I hope this helps. best wishes and regards, Michaela
Mar ’22
Reply to Using an excel document as a database/dictionary in Swift
Here's a revised Example App for your situation, where there's now a Search Bar and only search results are shown in the View - not the full set of Terms and Phrases. ContentView import SwiftUI import TabularData struct ContentView: View {     @ObservedObject var model = DataModel.shared     @State private var searchText = ""     var body: some View {         NavigationView{             Text("Searching for \(searchText)")                             .searchable(text: $searchText)             List(model.searchResults.rows,id:\.index) { row in                 HStack{                     Text(row["Term"] as! String)                     Text(String(row["Phrase"] as! String))                 }             }         }         .onChange(of: searchText) { srchText in             model.searchResults = model.findTerm(srchText)         }     } } Data Model import Foundation import TabularData class DataModel : ObservableObject {     static let shared = DataModel()     var dataTable = DataFrame()     @Published var searchResults = DataFrame() init() {         getData()     }     func getData() {         var url: URL?          do {              url = try FileManager.default.url(for: FileManager.SearchPathDirectory.downloadsDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: true)         } catch {               print("Failed to get Downsloads URL \(error)")             return         }                      let csvOptions = CSVReadingOptions(hasHeaderRow: true, ignoresEmptyLines: true, delimiter: ",")         let fileURL = url?.appendingPathComponent("TermPhrases.csv")         do {             dataTable = try DataFrame(contentsOfCSVFile: fileURL!,columns: nil, rows: nil, types: ["Term":CSVType.string,"Phrase":CSVType.string], options: csvOptions)             } catch {                 print("Failed to get load datatable from CSV \(error)")                 return         }     }     func findTerm(_ searchTerm: String) -> DataFrame {         let results = dataTable.filter ({ row in             guard let term = row["Term"] as? String else {                 return false             }             if term == searchTerm {                 return true             }             return false         })         return DataFrame(results)     } } extension DataFrame.Rows : RandomAccessCollection {   } Caution!! The findTerm func checks for an exact match of the Term, i.e. case sensitive and full match. If you need case insensitivity and partial matches, you need to change the findTerm function. If your purpose is only to look up phrases for a Term, then this is pretty much a complete solution, although if you've started an Xcode project with View Controllers (Storyboard) then to use my solution you'll need to create a new project with SwiftUI Interface. Best wishes and regards, Michaela
Topic: Programming Languages SubTopic: Swift Tags:
Mar ’22