Post

Replies

Boosts

Views

Activity

Reply to Background location tracking challenge
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()
Apr ’24
Reply to Background location tracking challenge
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 } } }
Apr ’24
Reply to CoreData/SwiftData persistent history error after adding additional models
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 ...
Mar ’24
Reply to CoreData/SwiftData persistent history error after adding additional models
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
Feb ’24
Reply to Store location data when app is not running using Xcode 15.1 and SwiftUI
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.
Dec ’23
Reply to Accessing a completion variable outside the function
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.
Nov ’23
Reply to Problem authenticating username/password stored in Keychain
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 ]
Aug ’23
Reply to How to form a search predicate using swift data with a one to many model
Thanks for all your help on this and filling the ticket as well. Clearly there are some issues with the basic predicate not being able to do it as we first tried and having to move it. Let's hope they address that issue (it's still early beta so hopeful). Yes, it's a pain to do two separate sorts. This is the difference between simple todo list examples vs more real world use cases. Why have a sorted section list with orphan sections and not be able to sort out the irrelevant items in each section in the predicate searches. I guess part of this is that Apple still views these as basically persistent arrays and are having to do gymnastics to interrelate them. Why not just bite the bullet and provide a sql like interface over it? In the end it's a big improvement over core data and makes it way easier for developers to do a lot of the backend data management for apps not just view based data entry. The .listRowSeparator placement was me just being tired and not attentive when I was putting in the secondary filter. Thanks for being kind on pointing that out. I have some cleanup to do but this all worked in the end and I'll look for it to morph with each beta release. Eventually it will get there, right? I'll force unwrap just in case and I have some other tidbits of cleanup to do.
Jun ’23
Reply to How to form a search predicate using swift data with a one to many model
Your suggestion of moving it outside and gave me the expected result. So now I get the net sections per the filter which is good. While that gets my sections narrowed my model still has all the articles under each section. In the past I did this with a filter in the list (see commented out if statement): ForEach(result.toArticles!) { article in // if article.search.contains(filter) { NavigationLink(destination: ArticleView(title: article.title, summary: article.summary, content: article.body)) { VStack(alignment: .leading) { Text(article.title) .font(.custom("Helvetica", size: 16)) if isExpanded { Text(article.summary) .font(.custom("Helvetica", size: 12)) .foregroundColor(Color(UIColor.systemGray)) } } } // } /// Hide the row separator .listRowSeparator(.hidden) } but it barfs on the NavigationLink line of code with: "No exact matches in reference to static method 'buildExpression'" So I'm guess it's just still too early a beta here given the problem you originally located with the predicate and we just encounter problems further downstream until this is fixed. Or, under this model I have to find a way in the predicate as the second stage filter down to just the articles that in the toArticles that match to avoid using my simple "if" statement. Not sure how to do that in a predicate or if it's even possible.
Jun ’23
Reply to How to form a search predicate using swift data with a one to many model
Thank you partially helpful in getting past the errors so syntax is right but when I enter my search string (when it's 3 or more characters) and it kicks in using the predicate the result is no matches even though I know for certain that the search fields are present. If I don't run the predicate I get my full list with search entries (see screen shot) but if I type in a known string that's in the search string so should return a true (and maybe it does) I get nothing in the resulting sectionsDB. I've included some code here to show how I enter the predicate and pass it. Must be something down stream in "FilteredList" I need to do? struct FilteredList: View { @EnvironmentObject var userSettings: UserSettings @Environment(\.modelContext) private var context @Query() var sectionsSD: [SectionsSD] private var isExpanded: Bool var body: some View { List(sectionsSD) { result in Section(result.section) { ForEach(result.toArticles!) { article in NavigationLink(destination: ArticleView(title: article.title, summary: article.summary, content: article.body)) { VStack(alignment: .leading) { Text(article.title) .font(.custom("Helvetica", size: 16)) if isExpanded { Text(article.summary) .font(.custom("Helvetica", size: 12)) .foregroundColor(Color(UIColor.systemGray)) Text(article.search) } } } /// Hide the row separator .listRowSeparator(.hidden) } } } } init(filter: String, isExpanded: Bool) { if filter.count >= 3 { let searchPredicate = #Predicate<SectionsSD> { $0.toArticles.flatMap { $0.contains { $0.search.contains(filter) } } == true } _sectionsSD = Query(filter: searchPredicate) } self.isExpanded = isExpanded } }
Jun ’23