Reputation: 1673
I am working on a SwiftUI App
and encountered a problem.
I have a View (
File Icon
). AData
object is passed to this View. To create theFile Icon
, the Data must be converted into a PDFDocument and an Image must be rendered from the first Document Page.
Idea: Since this is a heavy task and there are Views that display multiple File Icons (e.g. 20 File Icons are created at the same time), the plan is to create the File Icon
with some sort of placeholder, render the image in a Background Queue and, once its finished, change the displayed Image.
Code:
struct FileIcon: View {
let data: Data
@State private var image: UIImage?
init(for data: Data) {
self.data = data
renderImage() // Initiate the Background Task
}
var body: some View {
Group {
if let safeImage = image {
// Some Placeholder View
} else {
Image(uiImage: safeImage)
}
}
}
private func renderImage() {
DispatchQueue.global(qos: .userInitiated).async {
// Heavy Logic
DispatchQueue.main.async {
image = renderedImage
}
}
}
}
Problem:
As you can see, I am initiating the Background Task
inside the View's Initializer
. Xcode does not give any Error. However, when running this Code, the Placeholder View keeps being displayed and there is no switch to the actual rendered Image.
when calling renderImage()
inside .onAppear()
, the placeholder get replaced by the rendered image after some time.
Question: How can I initiate a Background Task
from a View's Initializer
?
Upvotes: 0
Views: 1461
Reputation: 257563
Here is a simple demo (as you want to do it in init
, however, since I don't know your scenario, probably it is better to move it into separated method and call explicitly)
Tested with Xcode 12.5 / iOS 14.5
struct DemoBackgroundTaskView: View {
class ViewModel: ObservableObject {
@Published var finished = false
init() {
print("Init") // test called once
// set up once performed background task
DispatchQueue.global(qos: .background).async {
sleep(10) // do your long task here
DispatchQueue.main.async { [weak self] in
self?.finished = true
}
}
}
}
@StateObject private var vm = ViewModel()
var body: some View {
Text(vm.finished ? "Done" : "Loading...")
}
}
Upvotes: 3