I've just done a prototype app for recognising text from photos of product labels, using the new Vision API with Xcode 16.2 beta 2 and images from an iPad Pro (2020 M1 chip) and iPhone 15 Pro. The recognition is very accurate, even correctly recognising neatly handwritten label text.
These are the settings I'm using:
ocrRequest.recognitionLevel = .accurate
ocrRequest.usesLanguageCorrection = true
ocrRequest.automaticallyDetectsLanguage = true
Some of the labels are very small (1cm x 2cm), but even then with the iPhone camera on x2 or x3 and macro-mode the recognition is near perfect.
Regards, Michaela
Post
Replies
Boosts
Views
Activity
I don't know if it's related, but in a test project I'm using to try out text recognition from photos Xcode does not recognise .HEIC as a valid extension for images in the Asset catalogue. Changing (in Finder's Get Info) the extension to .heic (i.e. lowercase) solves the issue. A device/app interoperability issue methinks..... Images from my iPhone and iPad all have the .HEIC extension, which (of course) doesn't get changed to lowercase on Airdrop to my Mac.
I'm using Xcode 16.2 Beta 2 with iOS devices on 18.2 and Mac on 15.2.
Regards, Michaela
UPDATE: The problem with this sample (Test) code was with the way I passed the CoreData Entity to the Form.
My in-development app updates the NSManaged vars correctly and recomputes the derived data, but there's a problem (yet to be resolved) in getting all those back into the Form for display, something to do with the way I've generalised the Form's processing.
Apologies for any inconvenience.
Regards, Michaela
This appears to have been resolved in Xcode Version 16.1 beta 3 (16B5029d) - available from 7 October 2024. I'm also now using Sequoia 15.1 Beta (24B5070a) on my M1MacMini - dunno if the MacOS version makes a difference, or just Xcode.
Regards, Michaela
I was having the same problem with Xcode 15 betas on an M1MacMini with Ventura betas. For me it seems to have been a space problem: Developer files on the main HD kept ballooning to over 60GB on an already constrained machine. When I moved Xcode to an external USB-C SSD and set Locations in Xcode Settings to that drive - the download and registration worked fine, plus there's more free space for other uses.
Fixed the problem by these steps:
Created a backup of the MacMini's persistent store, using code for a temporary persistent store coordinator.migratePersistentStore.
Also created CSV file backups of all objects of all entities (as a fall-back if the migrate didn't work correctly).
Deleted all objects of all Entities in the MacMini persistent store.
Waited for deletions (Step 3) to be propagated to the CloudKit container.
Checked the CloudKit container (Web Console) and manually deleted any Entity Objects not deleted by Steps 3 and 4 - there were quite a few.
Restored the MacMini persistent store by code for:
a persistentStoreCoordinator.destroyPersistentStore of the original MacMini store
b persistentStoreCoordinator.addPersistentStore based on the backup store
c persistentStoreCoordinator.migratePersistentStore from the backup to the "new" store (step 6 b)
Waited for the "new store" Entity Objects to be propagated to the CloudKit container.
Checked the CloudKit container (Web Console) - all good
Checked that additions/deletions worked correctly whether on the MacMini or via The CloudKit Console - all good.
Somewhat of a pain, but necessary given the amount of data and its complexity. An advantage is that I now have backup and restore functions in the file menu of the MacMini App.
Regards, Michaela
I've created a test app, with CoreData records synced via CloudKit. Records created on the Mac get synced to the iPad Pro. It's a very simple app, just Listing records from a FetchedResults.
I then Reset development environment in the CloudKit console Web Portal, with both devices' app closed. This Reset, of course, deleted all records, schema and coredata.cloudkit zone from the Development environment.
On restarting each app, CoreData records on both devices remained and the app worked correctly, fetching existing records and also recreated the CloudKit zone, schema and records. Thereafter new records on the Mac were correctly synced to the iPad Pro.
I'll do some more testing, making the test app more complex with the sort of relationship I was trying to achieve in the messed-up app, then might be bold enough to use this approach to fix the problem app.
However, it would still be better if I could somehow purge the CloudKit update queue - although I can't be sure that the schema is correct anyway: probably not.
Regards, Michaela
This what I use in my apps (during the initialisation of my Data Controller):
self.mainContainer = {
let container = NSPersistentCloudKitContainer(name: "MyDataModel")
container.loadPersistentStores(completionHandler: { description, error in
if let error = error {
print("**** ERROR loading persistent store \(error)")
}
//Setup auto merge of Cloudkit data
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//Set the Query generation to .current. for dynamically updating views from Cloudkit
try? container.viewContext.setQueryGenerationFrom(.current)
})
return container
}()
The key lines are the 2 below //Setup auto merge.
Also, be sure to have enabled Remote Notifications in Background modes of the App's Signing and Capabilities.
I hope this helps. Regards, Michaela
If the app referenced by Claude31 is available to you/your university, then this would be the way to go in getting data from a variety of volunteer subjects who have an ECG capable Apple Watch and an iPhone.
However, if you're a developer with an Apple Watch & iPhone and/or have colleagues with them and you're only collecting your data (or your colleagues') , then a fairly simple Swift App will suffice to get the raw data, which are a timestamp and voltage. The Apple documentation is https://developer.apple.com/documentation/healthkit/hkelectrocardiogram
I use my such app to create a CSV file, which I then import to an ECG Viewer. I use this setup quite frequently, as well as data from a Polar H10 ECG app, to monitor my various arrhythmias and provide data to EDs as required (unfortunately, too often for comfort!).
Regards, Michaela
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
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
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
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
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
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