I am creating iOS 14+ Widget using SwiftUI and I'm facing very strange situation. Problem is that I have just one "full widget" image (maybe or not its worth noting that it's downloaded from the internet but in the time of displaying its already loaded and injected as UIImage
) in my widget which is displayed correctly like that:
But after some time and after some more iPhone unlocks BOOM, I get this:
Few observations:
- it happens with
systemLarge
family as well - the same percentage of image's width is cropped - the image is cropped horizontally always by exactly the same percentage of width
- when my wallpaper is other than just plain black the cropped part is filled with black color as well
- the cropped part is filled with black no matter what user interface style (light/dark) I have
To make it easier I created minimal example which causes the error:
So, I have this EntryView
struct EntryView: View {
var viewModel: EntryViewModel
var body: some View {
Image(uiImage: image)
.resizable()
.scaledToFill()
}
}
which is used as content for Widget
itself
struct SomeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: xxx,
provider: SomeWidgetTimelineProvider(xxx)
) { item in
view(for: item)
}
.configurationDisplayName(xxx)
.description(xxx)
.supportedFamilies([.systemMedium, .systemLarge])
}
private func view(for item: Item) -> some View {
Group {
switch item {
case .aaa(let xxx):
EntryView(viewModel: xxx)
case .bbb(let xxx):
EntryView(viewModel: xxx)
}
}
}
}
and this Widget
is wrapped in WidgetBundle
@main
struct SomeWidgets: WidgetBundle {
var body: some Widget {
SomeWidget()
SomeWidget()
}
}
Maybe I should also show logic which is responsible for downloading the image. I use just simple Data(contentsOf:)
synchronous method which is called on background queue and then I call TimelineProvider
s callback on main queue:
final class SomeWidgetTimelineProvider: TimelineProvider {
...
func getTimeline(in context: Context, completion: @escaping (Timeline<SomeEntry>) -> Void) {
getSomeModel { model in
prefetchImage(for: model) { result in
switch result {
case .success(let image):
completion(
Timeline(
entries: [.aaa(EntryViewModel(image: image)],
policy: .after(Date() + 30 * 60)
)
)
case .error(let error):
completion(
Timeline(
entries: [.bbb(xxx)],
policy: .after(Date() + 30 * 60)
)
)
}
}
}
}
private func prefetchImage(for model: SomeModel, completion: @escaping (Result<UIImage, Error>) -> Void) {
DispatchQueue.global(qos: .background).async {
guard
let imageURL = URL(string: model.imageUrl),
let data = try? Data(contentsOf: imageURL)
else {
DispatchQueue.main.async {
completion(.failure(xxx))
}
}
let image = UIImage(data: data)
DispatchQueue.main.async {
completion(.success(image))
}
}
}
}
So my question is, is something wrong with my layout or fetching logic? What am I missing?
Thank you for any help!