Render Images on iOS 14 Widgets

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

  1. Either fetch all the images in TimelineProvider and inject them to the views directly.

    OR

  2. 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



Leave a reply



Submit