I'm trying to add Background Tasks (not a timer) to a SwiftUI project for iOS 15 to fetch data from an API periodically.
It seems I'm seeing issues with my Published variable not updating my SwiftUI View until the application has been opened. Second issue is, I'm seeing AsyncImage not loading after a background task has been run, it will just show my placeholder ProgressView loading.
I'm notified by UserNotifications when the background task has been run, and I assumed the Published variable from my ObservableObject class would have been updated in the background? I'm also not sure why AsyncImage breaks while using Background Tasks.
Data Model
struct Pokemon: Codable {
let sprites: Sprites
let name: String
enum CodingKeys: String, CodingKey {
case sprites
case name
}
}
struct Sprites: Codable {
let frontDefault: String
enum CodingKeys: String, CodingKey {
case frontDefault = "front_default"
}
}
class DataModel: ObservableObject {
let taskIdentifier = "com.example.test.refresh"
private var cancellables = Set<AnyCancellable>()
@Published var pokemon = Pokemon(sprites: Sprites(frontDefault: "", frontShiny: ""), name: "", types: [])
func getpokemon() {
let request = URLRequest(url: URL(string: "https://pokeapi.co/api/v2/pokemon/\(Int.random(in: 1..<450))")!)
URLSession.shared.dataTaskPublisher(for: request)
.map{ $0.data }
.decode(type: Pokemon.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { print ("Received completion: \($0).") },
receiveValue: { pokemon in
self.pokemon = pokemon
})
.store(in: &cancellables)
}
func register() {
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: taskIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 10 * 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh()
task.expirationHandler = {
print("expired")
//task.setTaskCompleted(success: false)
}
self.getpokemon()
task.setTaskCompleted(success: true)
}
}
App
import SwiftUI
import BackgroundTasks
@main
struct BackgroudTaskApp: App {
@ObservedObject var data = DataModel()
init() {
data.register()
data.scheduleAppRefresh()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(data)
.onAppear(perform: data.getpokemon)
}
}
}
SwiftUI View
struct ContentView: View {
@EnvironmentObject var data: DataModel
var body: some View {
Button(action: {
withAnimation {
data.getpokemon()
}
}, label: {
VStack(spacing: 0) {
AsyncImage(url:URL(string:data.pokemon.sprites.frontDefault)) { image in
image
.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 120, height: 120)
}}
).buttonStyle(.plain)
}
}
Post
Replies
Boosts
Views
Activity
I'm having issues trying to install any swift packages in Xcode 13.2, 13.2.1. I had to downgrade Xcode 13.3.1 back to Xcode 13.2+ because of a bug. Now the package I have won't resolve. Also I cannot add any packages regardless. I deleted the derived data, reset the packages in package dependencies, resolved the package version. I also used command line xcodebuild -resolvePackageDependencies, without success. Adding the swift package locally also doesn't work.
Could not resolve package dependencies:
Package.resolved file is corrupted or malformed; fix or delete the file to continue: unsupported schema version 2
After following along with the documentation and WWDC22 video on using the new SwiftUI Background Tasks API. My application was successfully updating in the background, then I ran into an issue where the data won't update resulting in a progress view showing and no new data being fetched but will eventually correct itself which doesn't seem right. See below screenshots below.
Data is nil at 9:13pm
Corrected itself at 9:34pm
struct DemoApp: App {
@StateObject var viewModel = ViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
}
.backgroundTask(.appRefresh("myapprefresh")) {
await viewModel.fetchData()
}
}
}
class ViewModel: ObservableObject {
@Published var pokemon = PokeAPI(name: "", sprites: Sprites(frontDefault: ""))
func fetchData() async {
URLSession.shared.dataTaskPublisher(for: request)
.map{ $0.data }
.decode(type: PokeAPI.self, decoder: JSONDecoder())
.replaceError(with: PokeAPI(name: "", sprites: Sprites(frontDefault: "")))
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in
}, receiveValue: { value in
self.pokemon = value
}).store(in: &cancellables)
}
}
Is it not possible to test Group Activities without a paid developer account? I don't see Group Activities in Signing & Capabilities in Xcode. I'm looking to try adding support for my app but unable to try this myself and just seeing if I need a paid developer account to do so?
I'm seeing a strange and random crash with my combine networking after updating Xcode to version 14.3 (14E222b) a few days ago. The code below is crashing with EXC_BAD_ACCESS on .store(in:) and wasn't on the previous version of Xcode.
private var cancellables = Set<AnyCancellable>()
func request() async {
URLSession.shared
.dataTaskPublisher(for: URL(string: "")!)
.map{$0.data}
.decode(type: Type.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {
print ("Received completion: \($0).")
}, receiveValue: { data in
print(data)
})
.store(in: &cancellables) // <- CRASHING HERE
}
When testing the simulator for CarPlay the screen remains black. It was working fine now the screen remains black even after restarting my computer. I have an M1 MacBook Air with Xcode 15.0.1.
Adding CPTabBarTemplate to my Root Template seems to break CPListTemplate, it ends up highlighting the second indexed value. I'm unable to figure out what's causing this issue. Removing the tab bar does fix the issue but I need to use tabs. Any help would be greatly appreciated!
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) {
self.interfaceController = interfaceController
let listTemplate = CPListTemplate(title: "List", sections: [])
listTemplate.tabImage = UIImage(systemName: "person.crop.rectangle.stack.fill")
let settingsTemplate = CPListTemplate(title: "Settings", sections: [])
settingsTemplate.tabImage = UIImage(systemName: "gear")
let tabBarTemplate = CPTabBarTemplate(templates: [listTemplate, settingsTemplate])
interfaceController.setRootTemplate(tabBarTemplate, animated: true, completion: nil)
}
I'm having troubles adding CarPlay support to my SwiftUI app. I have added the proper entitlements, scene configurations, and template delegate for CarPlay. It's unfortunately not showing up in the CarPlay simulator on Xcode 15. Am I missing something simple?
CarPlay Scene Delegate
class CarPlaySceneDelegate: NSObject, CPTemplateApplicationSceneDelegate {}
Info.plist
<key>UISceneConfigurations</key>
<dict>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlay</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
</dict>
</array>
</dict>
Entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.carplay-audio</key>
<true/>
</dict>
</plist>
I'm getting unable to locate a developer disk image that can be used to enable DDI services with Xcode Version 15.0.1 (15A507). I've deleted Xcode, deleted ~/Library/Developer and reinstalled Xcode from the App Store, rebooted my M1 and still seeing the same issue.
I'm trying to add Background Tasks to my SwiftUI app using the modifier backgroundTask(_:action:). I've been unable to get a working example, nothing ever updates in my app in the "background".
I've added the identifier to the permitted background task scheduler identifiers , enabled the required background modes capabilities. Any help would be appreciated.
It appears that neither appRefresh nor urlSession background tasks are effective in handling the update of JSON data in my app. Also which background task would be best suited for updating json data for my app?
When debugging and launching using e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"apprefresh"] doesn't get called on a real device.
App
struct MyApp: App {
@State private var viewModel = ViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environment(viewModel)
}
.backgroundTask(.appRefresh("apprefresh")) {
scheduleAppRefresh()
await viewModel.appRefresh()
}
.backgroundTask(.urlSession("apprefresh")) {
scheduleAppRefresh()
await viewModel.urlSession()
}
.onChange(of: phase) {
switch phase {
case .background: scheduleAppRefresh()
default: break
}
}
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "apprefresh")
request.earliestBeginDate = .now.addingTimeInterval(15 * 60)
try? BGTaskScheduler.shared.submit(request)
}
}
ViewModel
@Observable
class ViewModel {
func appRefresh(
) async {
}
func urlSession(
) async {
let config = URLSessionConfiguration.background(
withIdentifier: "apprefresh"
)
config.sessionSendsLaunchEvents = true
let session = URLSession(
configuration: config
)
let request = URLRequest(
url: URL(
string: ""
)!
)
let response = await withTaskCancellationHandler {
try? await session.data(
for: request
)
} onCancel: {
let task = session.downloadTask(
with: request
)
task.resume()
}
}
}
I’m looking for guidance on what to do with the new changes [coming] (https://developer.apple.com/support/third-party-SDK-requirements/) for adding Privacy Manifest to my app. I’m using on of the listed SDK which they include a Privacy Manifest of their own, do I need to include one in my app or do I just use the third-party’s?
Also what happens when a developer hasn't updated its swift package to contain one?
UPDATED: I determined the line causing the hang was .animation(.default, value: layout).
I'm seeing a strange issue when switching between a ScrollView/LazyVGrid and a List with my SwiftData but when toggling the layout it ends up freezing and I can't confirm what's causing the app to hang since there's no crash. I'm not getting much info from the debugger. Any help would be appreciated.
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
let gridItemLayout = [ GridItem(.adaptive(minimum: 150))]
@State private var layout = Layout.grid
var body: some View {
NavigationStack {
ZStack {
if layout == .grid {
ScrollView {
LazyVGrid(columns: gridItemLayout, spacing: 5) {
ForEach(items) { item in
}
}
}
} else {
List {
ForEach(items) { item in
}
}
}
}
// MARK: HERE'S THE ERROR
.animation(.default, value: layout)
.navigationTitle("ScrollView")
.toolbar {
ToolbarItemGroup {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
Menu {
Picker("Layout", selection: $layout) {
ForEach(Layout.allCases) { option in
Label(option.title, systemImage: option.imageName)
.tag(option)
}
}
.pickerStyle(.inline)
} label: {
Label("Layout Options", systemImage: layout.imageName)
.labelStyle(.iconOnly)
}
}
}
}
}
}
@Model
public class Item: Codable {
public let id: String
public enum CodingKeys: String, CodingKey {
case id
}
public init(id: String) {
self.id = id
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
}
}