Turns out my code is good so it's just a matter of the ambiguity of running background and app refresh tracks. After 12 hours the tasks started executing on cycle. We can close this one out.
Post
Replies
Boosts
Views
Activity
That suggestion was a dud. I used the following code to delete the store which it did delete but the problem persists:
if let storeURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("default.store") {
do {
try FileManager.default.removeItem(at: storeURL)
print("default.store deleted successfully.")
} catch {
print("Error deleting default.store: \(error)")
}
}
I still get this error:
error: the replacement path doesn't exist: "/var/folders/fk/m2x_2qzj44508m9zzcll2j_w0000gn/T/swift-generated-sources/@__swiftmacro_11OpExShellV112FilteredListV11helpSection33_232B54C76A2ED061EC4416CE3B2A99A1LL5QueryfMa_.swift"
It's got to be hanging on to the tracking or reference somewhere else.
Here is the bug number in feedback assistant: FB15037288
I updated to latest beta on the device and now works on device so it's just the simulator that looks like its having trouble. Thanks for your help. Hopefully, can sort out what's going on in the simulator. I'm using an iphone11 Pro
Here is a simple example:
struct ContentView: View {
@State private var isUnlocked = false
var body: some View {
VStack {
if isUnlocked {
Text("Unlocked")
} else {
Text("Locked")
}
}
.padding()
.onAppear(perform: authenticate)
}
}
import LocalAuthentication
func authenticate() {
let context = LAContext()
var error: NSError?
// check whether biometric authentication is possible
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
// it's possible, so go ahead and use it
let reason = "We need to unlock your data."
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
// authentication has now completed
if success {
// authenticated successfully
} else {
// there was a problem
if let error = authenticationError as? NSError {
print("Authentication failed: \(error), \(error.userInfo)")
} else {
print("Authentication failed: \(String(describing: authenticationError))")
}
}
}
} else {
// no biometrics
}
}
Here is the error it spits back:
Authentication failed: Error Domain=com.apple.LocalAuthentication Code=-1000 "UI service connection invalidated." UserInfo={NSDebugDescription=UI service connection invalidated., NSLocalizedDescription=Authentication failure.}, ["NSDebugDescription": UI service connection invalidated., "NSLocalizedDescription": Authentication failure.]
I get this error pop up from Xcode:
"CoreAuthUI quit unexpectedly."
This may help from the Problem Report:
-------------------------------------
Translated Report (Full Report Below)
-------------------------------------
Incident Identifier: 44ECD006-5C1C-4E2B-A580-003F46E298E5
CrashReporter Key: 74F42D79-158E-2606-618B-4DC2CCB64CD7
Hardware Model: Mac15,9
Process: CoreAuthUI [13984]
Path: /Volumes/VOLUME/*/CoreAuthUI.app/CoreAuthUI
Identifier: com.apple.CoreAuthUI
Version: 1.0 (1656.40.15)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd_sim [13492]
Coalition: com.apple.CoreSimulator.SimDevice.BCC8C055-BE32-4735-BA12-890CFD1512FC [3618]
Responsible Process: SimulatorTrampoline [1694]
Date/Time: 2024-09-02 21:20:46.9564 -0700
Launch Time: 2024-09-02 21:20:46.4760 -0700
OS Version: macOS 14.6.1 (23G93)
Release Type: User
Report Version: 104
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Termination Reason: SIGNAL 6 Abort trap: 6
Terminating Process: CoreAuthUI [13984]
I have the plist entry:
Privacy - Face ID Usage Description = "Allow the app to use FaceID"
Three beta releases, code is current and correct yet still does not work. Buelher? Anybody? Anybody?
If it is any help I'm getting unkown error messages when trying to authenticate using FaceID in Xcode 16 beta 5. I think there must be issues as my code appears to be correct so I think this is at the IOS 18 beta level.
While it did mention in the text of the video that you have to have the background activity session enabled before you start the updates it was bit out of context in terms of a working example per the sample code. Enabling the background activity this way did the trick.
self.updatesStarted = true
// TODO: Looks like you have to add this here.
backgroundActivity = true
let updates = CLLocationUpdate.liveUpdates()
Per usual the Apple documentation is sparse at best and while I appreciate the demo example there are some gaps there too. So the following issues/questions:
The video and liveUpdatesSample app talk about activating the background activity session which supposedly allows your code to operate in the app is terminated by the user. However, no practical example is provided to show the path to do this successfully. I presume that the code in the startedUpdates would continue to execute if started and the background session is turned on but that is not what happens. Nothing continues or resumes when you relaunch the app.
The liveUpdatesSample app in the simulator always thinks that the device is not stationary even though I just have Apple set for location
in the liveUpdateSamples app it's also not clear if/when the background activity flag should be set. I'm guessing it's just a value to be picked up by the AppDelegate when that runs but it does not seem to control any behavior other than to be just a saved boolean.
I would be very helpful to have an example that makes it clearer when and how to ensure and validate that processing is happening in the background when the app is running or terminated.
I would appreciate any insights or guidance on how to ensure my code is running in the background.
Here is where I start the updates:
@MainActor class LocationsHandler: ObservableObject {
let logger = Logger(subsystem: "com.***.yyy", category: "LocationsHandler")
static let shared = LocationsHandler() // Create a single, shared instance of the object.
private let manager: CLLocationManager
private var background: CLBackgroundActivitySession?
@Published var lastLocation = CLLocation()
@Published var count = 0
@Published var isStationary = false
@Published
var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") {
didSet { UserDefaults.standard.set(updatesStarted, forKey: "liveUpdatesStarted") }
}
@Published
var backgroundActivity: Bool = UserDefaults.standard.bool(forKey: "BGActivitySessionStarted") {
didSet {
backgroundActivity ? self.background = CLBackgroundActivitySession() : self.background?.invalidate()
UserDefaults.standard.set(backgroundActivity, forKey: "BGActivitySessionStarted")
print("** background actvity changed to: \(backgroundActivity)")
}
}
private init() {
self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`.
}
func startLocationUpdates() {
if self.manager.authorizationStatus == .notDetermined {
self.manager.requestWhenInUseAuthorization()
}
self.logger.info("** Starting location updates")
Task() {
do {
self.updatesStarted = true
let updates = CLLocationUpdate.liveUpdates()
var locationUpdateCounter = 0
for try await update in updates {
/// If updates are not started the break out of the loop
if !self.updatesStarted { break }
/// If the location is not nill then we can process the update
if let loc = update.location {
/// Update the Published variables
self.lastLocation = loc
self.isStationary = update.isStationary
self.count += 1
/// If we are not stationary then then process the lcation
if update.isStationary == false {
locationUpdateCounter += 1
if locationUpdateCounter >= UserSettings.init().trackingSampleRate {
saveLocationUpdates(location: self.lastLocation)
locationUpdateCounter = 0
}
}
}
}
} catch {
self.logger.error("** Could not start location updates")
}
return
}
}
func stopLocationUpdates() {
self.logger.info("** Stopping location updates")
self.updatesStarted = false
}
func saveLocationUpdates(location: CLLocation) {
do {
// Access the sharedModelContainer
guard let container = AppEnvironment.sharedModelContainer else {
LogEvent.print(module: "LocationsHandler.saveLocation()", message: "shared model container has not been initialized")
return
}
let context = ModelContext(container)
let entry = GpsJournalSD(
timestamp: Date(),
longitude: location.coordinate.longitude,
latitude: location.coordinate.latitude,
speed: location.speed,
processed: false,
code: "",
note: ""
)
context.insert(entry)
print("**+ Location saved: \(entry.timestamp) \(formatMPH(convertMPStoMPH( entry.speed))) mph")
}
}
}
My saveLocationUpdate just writes data to swiftdata and that works fine with the app is in the foreground/background (but doesn't update if app terminated)
Here is the code in the appDelegate:
class AppDelegate: NSObject, UIApplicationDelegate {
static let shared = AppDelegate()
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
let logger = Logger(subsystem: "com.***.yyy", category: "AppDelegate")
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
/// Set as a singleton. Important otherwise you get competing location handlers
let locationsHandler = LocationsHandler.shared
// If location updates were previously active, restart them after the background launch.
if locationsHandler.updatesStarted {
self.logger.info("** Restart liveUpdates Session")
locationsHandler.startLocationUpdates()
}
// If a background activity session was previously active, reinstantiate it after the background launch.
if locationsHandler.backgroundActivity {
self.logger.info("** Reinstantiate background activity session")
locationsHandler.backgroundActivity = true
}
return true
}
}
}
created this variable:
// Define a global access point for the sharedModelContainer in a way that it can be accessed outside of SwiftUI views
class AppEnvironment {
static var sharedModelContainer: ModelContainer!
}
Added the following to my @Main app:
init() {
LogEvent.print(module: "\(AppValues.appName)App.init()", message: "App startup...")
// TODO: Do stuff at startup
LogEvent.print(module: "\(AppValues.appName)App.init()", message: "Settings..." + printUserSettings(description: "Settings", indent: " "))
NetworkStatus.shared.startMonitoring()
AppEnvironment.sharedModelContainer = initializeModelContainer()
// Set any default settings here
UserSettings.init().userMode = .development
LogEvent.print(module: "\(AppValues.appName).init()", message: "App startup...")
}
// Setup the shared model container so the data can be access across the app
//
var sharedModelContainer: ModelContainer = {
let schema = Schema([
GpsJournalSD.self,
TripSummariesSD.self,
TripJournalSD.self,
SectionsSD.self,
ArticlesSD.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
private func initializeModelContainer() -> ModelContainer {
let schema = Schema([
GpsJournalSD.self,
TripSummariesSD.self,
TripJournalSD.self,
SectionsSD.self,
ArticlesSD.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}
var body: some Scene {
WindowGroup {
MasterView()
.environmentObject(UserSettings())
}
.modelContainer(sharedModelContainer)
...
for access in a function:
do {
guard let container = AppEnvironment.sharedModelContainer else {
LogEvent.print(module: "processTrips()", message: "container is nil")
return
}
let context = ModelContext(container)
...
for access in my view
@Environment(\.modelContext) private var modelContext
...
The best info I can find is to delete the the persistence history... maybe. However, since this is created in SwiftData there is no ".xcdatamodeld" and thus no way to call up and delete that persistence history. I've tried using the following to get at it but the historyFetchRequest fails because it is looking for the name of the coredata model.
func fetchAndClearPersistentHistory() {
let context = container.newBackgroundContext() // Using a background context for operations
let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest!
CoreData: error: Failed to load model named MySampleApp
ZenTrac/CoreDataStackClass.swift:31: Fatal error: Unexpectedly found nil while unwrapping an Optional value
@yosi199 Did you ever sort this out? I'm having the very same issue and I can't find a single answer and no one is contributing any suggestions even though I've posted on stackoverflow.com as well.
Some progress on permissions. By asking in the proper order I can now get the app to ask for both the permissions. For the AppDelegate I added the following:
extension AppDelegate {
func requestLocationAuthorization() {
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
}
and in my LocationManager added the following to then immediate ask for Always:
func locationManager(
_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus
) {
/// Handle location permission changes...
/// This asks for Always Allow right after asking for When in Use
if locationManager.authorizationStatus == .authorizedWhenInUse {
AppDelegate.shared.requestLocationAuthorization()
}
}
Now the issue remains of how to collect location information when the app is not "active" or in the "background". I can't find any info on how to do this yet I know other apps out there do collect location information when the user is not "using" the app.
I'll add using a variable outside the functions does not work. They do not update. I'm guessing that either the value can't escape the context of the function or somehow out of the task has to move up to the main thread in some controlled sequence to ensure it updates. I've tried playing with Dispatch Queues but I am either not doing them right or they don't work either.
Resolved! cleanup here in the "authenticateUser" code.
class func authenticateUser(username: String, password: String) -> Bool {
let serviceName = AppValues.appName
let accountData = username.data(using: .utf8)!
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: serviceName,
kSecAttrAccount: accountData,
kSecReturnData: true,
kSecMatchLimit: kSecMatchLimitOne
]