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)
}
}