Posts

Post not yet marked as solved
1 Replies
I have succeeded in fetching weather data during an app refresh in the background! So it is indeed possible and it's been working great. My app already had background app refresh set up to launch a URL session, and I no longer need that now that WeatherKit can fetch the data I need. My question now, before I push my new version out to the store, is how to handle any errors that WeatherKit might throw at me. I don't want the app to crash if the service goes down or whatever. The documentation describes a WeatherError but I'm confused over how to implement that. Need an example
Post marked as solved
2 Replies
If you're still looking for a solution, try this: Sign into your developer account. Choose "Certificates, IDs and Profiles", then choose "Identifiers". Your sample app and identifier should appear on the list. Select it. On the screen that comes up you'll notice there are two tabs, Capabilities and App Services. WeatherKit must be checked ON BOTH tabs, and you must hit the SAVE button in the upper right for the checkmark to "stick".
Post not yet marked as solved
3 Replies
Same. How do you Simulate a Background Fetch like we used to? MacOS 14.2.1 Xcode 15.2 Current Simulator device is iOS 17.2 iPhone 15 Pro Max
Post not yet marked as solved
1 Replies
I did it! The key learning for me was to "include .minute" in the weather query and to then receive back an array of minute-by-minute data. You can then step thru the forecast array and pick out the data you need, in my case the precipitationChance. Successful code: import Foundation import WeatherKit @Observable class WeatherManager { private let weatherService = WeatherService() var weather: CurrentWeather? var forcast: Forecast<MinuteWeather>? var pop: [Double] = [] // Will build this array with precipitationChance for each minute forward func getWeather(lat: Double, long: Double) async { do { let weatherFetch = try await weatherService.weather(for: .init(latitude: lat, longitude: long), including: .current, .minute) weather = weatherFetch.0 // The current weather forcast = weatherFetch.1 // The forecast by minute for the next hour or so var i: Int = -1 forcast?.forEach { weatherEntry in // This loop loads the precipitationChance values into the pop array i = i + 1 let timer = DateFormatter.localizedString(from: weatherEntry.date, dateStyle: .short, timeStyle: .short) pop.append(weatherEntry.precipitationChance) print("Time \(i): \(timer) = \(pop[i])") // Time and printing are for debugging } } catch let error { print("Failed to get the weather and/or forecast. \(error)") } } // close getWeather function var icon: String { guard let iconName = weather?.symbolName else { return "--" } return iconName } var temperature: String { guard let temp = weather?.temperature else { return "--" } let convert = temp.converted(to: .fahrenheit).value return String(Int(convert)) + "°F" } var humidity: String { guard let humidity = weather?.humidity else { return "--" } let computedHumidity = humidity * 100 return String(Int(computedHumidity)) + "%" } var pressure: String { guard let press = weather?.pressure else { return "--" } let convertP = press.converted(to: UnitPressure.inchesOfMercury).value return String((convertP)) + " in. Hg" } var UVindex: String { guard let uv = weather?.uvIndex.value else { return "--" } return "\(uv)" + " index" } var POP: String { if pop.count > 11 { // Check that there are at least 11 minutes of forecast data var chance = pop[10] * 100.0 // Report the forecast for the (arbitrary) 11th entry return String(Int(chance)) + "%" } else { return "--" } } }
Post not yet marked as solved
8 Replies
Tests complete. I can successfully use the FlightPlanner demo or a simpler project from here: https://github.com/coledennis/WeatherKit_CoreLocation_SwiftUI_Tutorial ... (before and) after changing the bundle ID to my own, one which I have registered for WeatherKit. This was not the case when I started, though. But, a playground with any of the code samples above still throws the same errors as before. I can abandon trying to use Playground to learn my way around WeatherKit. I'll switch to using demo apps. I thought it would make things easier and faster to use a Playground but that's obviously not the case.
Post not yet marked as solved
8 Replies
Another clue? Following the example at https://blog.makwanbk.com/weatherkit-build-a-simple-ios-weather-app, I put the following code in my playground and got the error below. It's pretty much the same as before but now expressed in a localized description. I was looking at my developer account and discovered I have no current provisioning profiles. Would that do it? As I said the Airport Forecast example app works in Simulator import UIKit import WeatherKit import CoreLocation let service = WeatherService() let currentLocation = CLLocation(latitude: 37.7749, longitude: 122.4194) do { let weather = try await service.weather(for: currentLocation) print(weather) let currentWeather = Weather(temperature: weather.currentWeather.temperature.value, condition: weather.currentWeather.condition.rawValue, symbolName: weather.currentWeather.symbolName, humidity: weather.currentWeather.humidity, isDaylight: weather.currentWeather.isDaylight) print(currentWeather) } catch { assertionFailure(error.localizedDescription) } struct Weather { let temperature: Double let condition: String let symbolName: String let humidity: Double let isDaylight: Bool static func empty() -> Weather { Weather(temperature: 0, condition: "", symbolName: "", humidity: 0, isDaylight: false) } } __lldb_expr_17/Weather.playground:31: Fatal error: The operation couldn’t be completed. (WeatherDaemon.WDSJWTAuthenticatorServiceProxy.Errors error 0.)
Post not yet marked as solved
8 Replies
I'm having a harder time trying that than you'd expect. Getting it to work in an app was what I was trying to avoid by using the playground to get my code working first. Does your question/answer imply that it SHOULD be working in a playground? I mean, does that code work for you?
Post not yet marked as solved
8 Replies
Update: This code does not crash the app, and the error returned hints that I might have an access issue. I'm still wondering if this is even possible in a playground. As an aside, I have successfully used Apple's Flight Planner app that is offered as a WeatherKit demo. So my account seems to be "authorized" or whatever the word is. import UIKit import WeatherKit import CoreLocation let sanFrancisco = CLLocation(latitude: 37.7749, longitude: 122.4194) do { let weather = try await WeatherService().weather(for: sanFrancisco) } catch { print(error) } This produces the error: xpcConnectionFailed(Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.weatherkit.authservice" UserInfo={NSDebugDescription=connection to service named com.apple.weatherkit.authservice}) Playground execution failed: error: execution stopped with unexpected state. error: Execution was interrupted. The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
Post not yet marked as solved
8 Replies
Does that code not crash your Xcode? Again, it's in a playground.
Post not yet marked as solved
2 Replies
This is still an open question. @naticio, your reply suggests that it cannot run in the background. Am I misreading you? The inability to check the weather occasionally without user input would be a HUGE negative for WeatherKit. It means you can't "warn" the user of significant weather changes like imminent rain. Related question: Is it possible to play with weatherKit in an Xcode playground? So far I can reliably crash Xcode by even attempting this. I'll post this as a separate question.
Post marked as solved
9 Replies
Indeed. If you can attach two builds of your app, one with Info.plist string and one without, that’d be grand. I've been traveling and only now saw your reply. The working version is now available in the app store (as RIDS calc"). I'll be happy to provide a build with the string removed if you can detail what you mean by "attach".
Post marked as solved
34 Replies
Still a problem. Xcode Version 12.0 (12A7209) Per advice here, I deleted the iOS 14 folder inside ~/Library/Developer/Xcode/iOS DeviceSupport.   (My iPhone is now on 14.0.1, so I left the 13.7 folder alone.) It took a long time for Xcode to "copy cache files" from the iPhone but once that finally got done, normal build-and-run behavior is back. Nice and snappy.
Post not yet marked as solved
18 Replies
Still a problem. Xcode Version 12.0 (12A7209) Per advice here, I deleted the iOS 14 folder inside ~/Library/Developer/Xcode/iOS DeviceSupport. (My iPhone is now on 14.0.1, so I left the 13.7 folder alone.) It took a long time for Xcode to "copy cache files" from the iPhone but once that finally got done, normal build-and-run behavior is back. Nice and snappy.
Post not yet marked as solved
2 Replies
It's true that the typical BG update involves a URL session to get web data, but it doesn't have to. In the App Delegate, you need to set up your app. Here is the code I have in my didFinishLaunchingWithOptions in my app delegate: (Note that self.logSB is my logging, similar to a Print command.) &#9;&#9;&#9;&#9;switch UIApplication.shared.backgroundRefreshStatus {         case .available:             self.logSB.debug("backgroundRefreshStatus is AVAILABLE")         case .denied:             self.logSB.debug("backgroundRefreshStatus is DENIED")         case .restricted:             self.logSB.debug("backgroundRefreshStatus is RESTRICTED")         default:             self.logSB.debug("backgroundRefreshStatus is unknown")         }         if #available(iOS 13.0, *) {             self.logSB.verbose("iOS 13  Registering for Background duty")             BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.WH.myApp.myIdentifier", using: nil) { task in                 self.handleAppRefresh(task: task as! BGAppRefreshTask)                 self.logSB.verbose("iOS 13  Refresh requested")             }         } else {             self.logSB.verbose("iOS < 13  Registering for Background duty")             UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)             //                UIApplication.shared.setMinimumBackgroundFetchInterval(200)         } You can also see that I support the older background session method. That's what my app started with before iOS 13 came along. You could probably drop that. Up at the AppDelegate class level: &#9;&#9;&#9;&#9; var backgroundSessionCompletionHandler: (() -> Void)? &#9;&#9;     var backgroundSynchTask: UIBackgroundTaskIdentifier = .invalid The pre-iOS 13 fetch:     func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {         let dateFormatter = DateFormatter()         dateFormatter.dateStyle = DateFormatter.Style.medium         dateFormatter.timeStyle = DateFormatter.Style.long         var convertedDate = dateFormatter.string(from: Date())         if #available(iOS 13.0, *) {             self.logSB.info("iOS 13 Ignoring Background Fetch called at \(convertedDate)")             completionHandler(.newData)         } else {             self.logSB.info("Background Fetch called at \(convertedDate)")             let myState = UIApplication.shared.applicationState             switch myState {             case .background :                 self.logSB.info("The app state was found to be Background at \(convertedDate)")             case .inactive:                 self.logSB.info("The app state was found to be Inactive at \(convertedDate)")             case .active:                 self.logSB.info("The app state was found to be Active at \(convertedDate)")             default:                 self.logSB.info("The app state was found to be Unknown at \(convertedDate)")             }             Central().fetch {                    convertedDate = dateFormatter.string(from: Date())                 self.logSB.info("Calling the Fetch completion handler at \(convertedDate)\n\n")                 completionHandler(.newData)             } &#9;&#9;&#9;&#9;}     } The Central().fetch function is what goes and gets current web data, but it doesn't have to. It could be anything. And finally:     func applicationDidEnterBackground(_ application: UIApplication) { &#9;      let dateFormatter = DateFormatter()         dateFormatter.dateStyle = DateFormatter.Style.medium         dateFormatter.timeStyle = DateFormatter.Style.long         let convertedDate = dateFormatter.string(from: Date())         self.logSB.info("The app entered the background at \(convertedDate)")         if #available(iOS 13.0, *) {             scheduleAppRefresh()         }     }     @available(iOS 13.0, *)     func scheduleAppRefresh() {         logSB.info("Scheduling the AppRefresh in iOS 13 !!")         let request = BGAppRefreshTaskRequest(identifier: "com.WH.myApp.myIdentifier")         request.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60)         do {             try BGTaskScheduler.shared.submit(request)         } catch {             logSB.error("Could not schedule app refresh: \(error)")         }     }     @available(iOS 13.0, *)     func handleAppRefresh(task: BGAppRefreshTask) {         logSB.info("Handling the AppRefresh in iOS 13")         scheduleAppRefresh()         let queue = OperationQueue()         queue.maxConcurrentOperationCount = 1         let appRefreshOperation =  Central().fetch2         queue.addOperation(appRefreshOperation)         let lastOperation = queue.operations.last         lastOperation?.completionBlock = {             task.setTaskCompleted(success: !(lastOperation?.isCancelled ?? false))         }         task.expirationHandler = {             queue.cancelAllOperations()         } &#9;      let dateFormatter = DateFormatter()         dateFormatter.dateStyle = DateFormatter.Style.medium         dateFormatter.timeStyle = DateFormatter.Style.long         let convertedDate = dateFormatter.string(from: Date())         self.logSB.info("Completed the iOS 13 App Refresh at \(convertedDate)")     } 
Post marked as solved
9 Replies
SOLVED !! I gave up trying to figure out what was triggering the alert. So to clear Review, I relented and added the info.plist item: Privacy - Local Network Usage Description ...|... String ...|... "my text" Guess what? That squelches the alert entirely! My alert text is never seen. My app is not added to the list of apps that have asked for Local Network access. This fixed both of my affected apps. Seems like a bug. Agreed?