Render Images on iOS 14 Widgets
Constucting timeline you need to specify explicit entry date for each entry. The policy parameter is for recreating next timeline.
So your getTimeline
should look like
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [ImageEntry] = []
let currentDate = Date()
for i in 1 ..< 4 {
let imageNumber = String(i)
let currentImage = "image_" + imageNumber
let entryDate = Calendar.current.date(byAdding: .minute, value: (i-1)*5, to: currentDate)!
let entry = ImageEntry(date: entryDate, image: currentImage)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
How to download images async for WidgetKit
Yes, you should download the images in the timeline provider and send the timeline when they are all done. Refer to the following recommendation by an Apple frameworks engineer.
I use a dispatch group to achieve this.
Something like:
let imageRequestGroup = DispatchGroup()
var images: [UIImage] = []
for imageUrl in imageUrls {
imageRequestGroup.enter()
yourAsyncUIImageProvider.getImage(fromUrl: imageUrl) { image in
images.append(image)
imageRequestGroup.leave()
}
}
imageRequestGroup.notify(queue: .main) {
completion(images)
}
I then use SwiftUI's Image(uiImage:) initializer to display the images
INImage from widgets intent collection is tinted with default blue color
Thanks to Edward's hints I managed to fix my issues this way:
func getIntentImage(imageLink: String) -> INImage?
{
guard let url = URL(string: imageLink),
let imageData = try? Data(contentsOf: url), // may use image local caching
var image = UIImage(data: imageData) else
{
return nil
}
let maxSize: CGFloat = 80
let size = CGSize(width: maxSize, height: maxSize)
if let resizedImage = image.resizeImage(targetSize: size)
{
image = resizedImage
}
return INImage(uiImage: image.withRenderingMode(.alwaysOriginal))
}
// Image resizer:
extension UIImage
{
func resizeImage(targetSize: CGSize) -> UIImage?
{
let size = self.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if widthRatio > heightRatio
{
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
}
else
{
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1)
self.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
iOS WidgetKit: remote images fails to load
Yes, as mentioned by Konstantin, it is not supported to load images asynchronously. There are 2 options to load network image in widgets
Either fetch all the images in TimelineProvider and inject them to the views directly.
OR
Use
Data(contentsOf: url)
to fetch the image. This way it still fetches them synchronously but the code is cleaner. Sample code -struct NetworkImage: View {
private let url: URL?
var body: some View {
Group {
if let url = url, let imageData = try? Data(contentsOf: url),
let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
}
else {
Image("placeholder-image")
}
}
}
}
This view can simply be use like this -
NetworkImage(url: url)
Related Topics
Is There Difference Between Using a Constructor and Using .Init
Convert Swift Encodable Class Typed as Any to Dictionary
Swiftui - Navigation View Opening with Back Button and Half Grey Screen/Weird Behavior
How to Unpack Multiple Levels of Nested JSON in Firebase Database
Ambiguous Method Overload with Closures in Swift, But Only When Closure Returns a Value
Swift MACos Popover Detect Change Dark Mode
Why Is the Following Giving Me Zero
Why Reloaddata Is Showing Error for My Tableview Inside Viewcontroller
Why Do We Need to Specify Init Method
Swiftui Tabview Gives an Error Message During Add/Delete the Element of Coredata
Swift: Nil Error When Using Self.Moc.Save() to Save in Core Data
Elegant 'Bounded' Methodology in Swift
How to Properly Test Against Certain Values in Nseventmodifierflags via Swift