Hi, I'm Alex (short for Alexander).
- Blog: kean.blog
- Built Nuke, Pulse, and other popular frameworks and tools
- Developed the initial CreateAPI version which is now maintained by @cookpad
Contacts
- Mastodon: @[email protected]
Lazy image loading for Apple platforms: SwiftUI, UIKit, AppKit
License: MIT License
Hi, I'm Alex (short for Alexander).
Contacts
In release 0.5.0, the minimum supported version was bumped to iOS 14.0 "temporarily". Is this still temporary and will iOS 13 be supported again soon? Our app will likely be supporting iOS 13 for another few months.
It would be nice if this package implemented the same features, such as the caching and gif support, using the new, OOB, AsyncImage in SwiftUI. Perhaps using phases?
I'm using LazyImage like so:
LazyImage(source: imageUrl) { state in
if let image = state.image {
image
} else if state.error != nil {
Image(systemName: "xmark.octagon")
.resizable()
.padding()
.scaledToFit()
.foregroundColor(.red)
} else {
ProgressView()
}
}
and it works just fine for images. However if the imageUrl
is a video then nothing is displayed.
If I remove the trailing closure then the video shows up but then I loose my error icon & loading indicator.
I didn't see anything related to videos in LazyImageState
. How can I make this work?
I am building an app that has an internal private Swift Package where I do most of my work shared across multiple targets
One of those targets is a macOS Catalyst app
My SPM product contains NukeUI
as one of the target dependencies, however there are build errors since macOS Catalyst seems to build as an iOS app under a macOS environment? I'm new to it so I am not sure what's happening, but this is the list of errors I get, which is odd since they are inside a #if os(iOS) || os(tvOS)
Just to note, building the target using any iOS device works, but when I change the scheme to macOS, it fails
Essentially we can't upgrade to Nuke 10 until we can remove FetchImage
.
You can mark this as closed if its something you just don't want to do but I imagine there will be holdouts until people can drop support for iOS 13.
The Video playback works perfectly however the video stops when the app is put in the background.
I would expect the Video playback to resume when I bring the app back in the foreground but it does not.
Is this expected and is there a way to get the video to resume?
Hello there,
First of all, let me start by saying, thank you for creating such a great package. It has greatly simplified our image loading and caching, something that we wish Apple offered out of the box.
When running the app on an iOS 15 device, we have discovered that even if the image has already been loaded, our placeholder view will always be shown before displaying the cached image. When the view reappears, the placeholder will be shown for a brief moment before the cached image is loaded in. This creates the effect that the image is being loaded again, however I have confirmed that there is no network activity. It appears to be something to do with the fact that the image has an animation added to it.
Example code:
LazyImage(source: url) { state in
if let image = state.image {
image
.aspectRatio(contentMode: .fit)
} else {
generic
.aspectRatio(contentMode: .fit)
}
}
.frame(height: targetHeight)
.animation(.easeIn)
.clipShape(Circle()))
Putting a breakpoint in the content closure shows that it is initially called with a nil
value for state.image
, before almost immediately being called again with a non-nil
image. This results in the behaviour I described above.
When removing the animation, it works as expected. The above code works fine running on iOS 14, using the same build. We are building using Xcode 12.5.1, and I have also confirmed that this issue is present when building using Xcode 13.
I suspect that this may be something to do with changes to the way SwiftUI handles animations, but I thought I'd point it out here nonetheless in case it is something you need to look into.
Is it possible to read in the pixel data of the image as a UIImage or NSImage to be used with a plugin that can get the common color of the image so set it as a backdrop / background color?
As a bonus it would be fantastic if this receiving a background color of the image as built into NukeUI.
I am using LazyImage in a VStack and I have a question. When a gif is loaded I only want to display its first frame. But how can I prevent the animation using LazyImage? Its my last piece of a puzzle..
While loading a list of images, sometimes, randomly, the image does not load.
If you restart, after few times, it works.
Console logs:
Task <72B7A449-92F7-41E1-AB00-5A2D634E541B>.<9> finished with error [303] Error Domain=kCFErrorDomainCFNetwork Code=303 "(null)" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <72B7A449-92F7-41E1-AB00-5A2D634E541B>.<9>, _kCFStreamErrorDomainKey=4, NSErrorPeerAddressKey=<CFData 0x60000086caf0 [0x1da82daf0]>{length = 16, capacity = 16, bytes = 0x100201bbd15e5a010000000000000000}, _kCFStreamErrorCodeKey=-2201, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask <72B7A449-92F7-41E1-AB00-5A2D634E541B>.<9>" )}
Used with:
LazyImage(source: imageUrl, resizingMode: .aspectFit) .frame(maxWidth: .infinity, maxHeight: .infinity) .aspectRatio(contentMode: .fit)
Any idea?
I am trying to publish NukeUI
to cocoapod, but I encountered the following error.
NukeUI/NukeUI/Classes/LazyImage.swift:254:23: error: type 'ImageDecoders' has no member 'Video'
It seems that Video
is not defined for WatchOS.
Cocoapod ensure all published pods can be compiled, but Swift Package doesn't have such feature.
Actually, SPM is inferior to Cocoapod in all aspects.
I really don't understand why people switch to SPM.
I'd like to use NukeUI on my app but I don't want Gifu, I think it would be better to have two targets (one without gif support), in order to reduce unused dependencies.
p.s. Thank you for this great library !
I have images that take a while to load. Is it possible to have the images written onto disk and read from disk the next time I launch my app?
This is my code, but disk caching does not seem to be happening in between sessions. What am I doing wrong? Thanks in advance :)
let pipeLine = ImagePipeline(configuration: .withDataCache)
struct AsyncCircularImageView: View {
let url: URL?
let imageLength: CGFloat
var body: some View {
LazyImage(source: url) { state in
switch state.result {
case .success(let resp):
Image(uiImage: resp.image)
.resizable()
.aspectRatio(
contentMode: .fill
)
.clipShape(Circle())
.frame(
width: imageLength,
height: imageLength
)
default:
ProgressView()
}
}
.pipeline(pipeLine)
}
}
Hi,
I'm using 2 types of images (one for compact, one for regular) and i'm switching images after a device rotation or window resize.
But the good image is loaded only the first time, after a device rotation, the result are "inversed", ie: seeing portrait image in landscape and landscape image in portrait.
You can test with this code on an iPhone and rotate the device:
import SwiftUI
import NukeUI
struct TestView: View {
@Environment(\.horizontalSizeClass) var wSizeClass
@Environment(\.verticalSizeClass) var hSizeClass
var body: some View {
VStack {
Text("LazyImage").foregroundColor(.white)
LazyImage(source: hSizeClass == .regular ? "https://liquidchaostattoos.com/wp-content/uploads/2015/06/artist-portrait-placeholder.jpg" : "https://www.ctsfw.edu/wp-content/uploads/2016/02/landscape-placeholder-image.jpg")
.frame(width: 150, height: 150)
Text("AsyncImage").foregroundColor(.white)
if #available(iOS 15.0, *) {
AsyncImage(url: hSizeClass == .regular ?
URL(string: "https://liquidchaostattoos.com/wp-content/uploads/2015/06/artist-portrait-placeholder.jpg")! :
URL(string: "https://www.ctsfw.edu/wp-content/uploads/2016/02/landscape-placeholder-image.jpg")!) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 150, height: 150)
} else {
// Fallback on earlier versions
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TestView()
.environment(\.colorScheme, .dark)
}
}
}
Can we add play and pause buttons to the video playback?
And mute and non-mute buttons.
just as Instagram playback.
When can we expect the first release? Thank you,
I'm trying to use LazyImage
inside List
, by giving it an aspect ratio like this:
struct ContentView: View {
var data = (1...50).map { $0 }
var ratios: [CGFloat] = [16/9, 21/3, 1]
var aspectRatio: CGFloat {
ratios.randomElement()!
}
var body: some View {
NavigationView {
List {
ForEach(data, id: \.self) { model in
LazyImage(source: "https://picsum.photos/300/300")
.aspectRatio(aspectRatio, contentMode: .fill)
.padding(.horizontal)
.padding(.bottom)
}
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.listStyle(.plain)
}
}
}
The problem is that the elements get overlapped when you scroll, like this:
This doesn't reproduce if i use ScrollView
, only with List
In our app we sometimes need to make static images of our UI and it seems the SwiftUI method drawHierarchy
seems to not show anything in places of a LazyImage
, .e.g:
let controller = UIHostingController(rootView: self)
let renderer = UIGraphicsImageRenderer(size: controller.view.size)
let image = renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
I was just scrolling a view with images.
Fatal error: Attempted to read an unowned reference but the object was already deallocated2022-01-26 15:27:24.629614+0100 Sybel[28238:2059206]
Fatal error: Attempted to read an unowned reference but the object was already deallocated
Fatal error: Attempted to read an unowned reference but the object was already deallocated
CoreSimulator 776.4 - Device: iPhone X (ECF98794-9275-4AC5-854C-235CA8CD9FAF) - Runtime: iOS 14.4 (18D46) - DeviceType: iPhone X
Hi,
I'm trying to add the package to my Xcode project but getting the error "Failed to resolve dependencies".
Clicking "Add Anyway" adds the package, but it fails to resolve and causes the project to fail to build (copying components from another project that already use this).
Is there something special I need to do?
Thanks
Is it possible to not set const image height if it's cached and loaded from the memory or when it's loaded? So, I want to show a placeholder for the very first load and then allow an image to fill all width provided by a container and let an image preserve its aspect ratio for height.
I'm having an issue where some images do not load. I've found that when loading a very large number of items in a vertical scrolling list, some of the images do not load; although, they do start. I've tried checking errors with the .onFailure, and setting up the onProgress handler to check if they are downloading, but they're not. I only see the Color that I've set in the final else statement inside of the LazyImage init closure.
You'll notice that the images load if I slightly scroll. It's as though they've downloaded, but something is blocking the UI thread from refreshing.
Xcode and iOS beta 3.
Here is the code:
LazyImage(source: imageUrl) { state in
if let image = state.image {
image
.clipped(antialiased: true)
.clipShape(RoundedRectangle(cornerRadius: 10))
} else if state.error != nil {
Color.red // Indicates an error
} else {
Color
.gray
.cornerRadius(10)
.opacity(0.2)
}
}
.onSuccess { response in
debugPrint("image success")
}
.onStart{ task in
debugPrint("Started: \(task.request.url!)")
}
.onFailure{ error in
debugPrint("Failure: \(error)")
}
.onProgress{ response, completed, total in
debugPrint("Progress: \(total)")
}
.frame(minHeight: mediaHeight, maxHeight: mediaHeight, alignment: .center)
How does one implement disk caching with LazyImage?
In my SwiftUI App i am trying to load post requested image by passing imagerequest to source initializer but if fails
trying to load image from this link:
https://ipfs.infura.io:5001/api/v0/block/cat?arg=QmcrJRMrFnGGHtC4bwkDYsnqaG9fmGSV6qHATa9h6j3jxi
let url = try! ImageRequest(urlRequest: URLRequest(url: obj.displayCoverLink, method: .post)).asImageRequest()
LazyImage(source: url) { state in
if let img = state.image {
img
.aspectRatio(contentMode: .fill)
} else {
Image("placeholder")
.resizable()
.frame(width: 25, height: 25, alignment: .center)
.scaledToFit()
}
}
.onSuccess({ response in
print(response)
})
.onFailure({ error in
print(error)
})
.frame(width: 80, height: 100)
.transition(.fade(duration: 0.5))
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.25), radius: 4, x: 0, y: 4)
I might be wrong but I do not see any way to alter the default view displayed while an image is loading. I am using LazyImage to load a somewhat transparent gif and it shows a gray square while its loading and I'd love to use a clear color or hide the view entirely until it's ready to be shown. Is this possible today or something you would accept a PR for?
Thanks!
Thank you for this great library
I'm using Kingfisher and implement its ImageDataProvider to fetch data from images in Photos Gallery.
Is it possible to do that with NukeUI
as well?
Hello,
First off - thanks for the effort on the library - really appreciate it!
In my project I'm using NukeUI and Introspect
I have a view that uses about 10 LazyImage
s to download and show an mp4 and another piece of that view that responds to a sheet modifier that utilizes detents on iOS 15 via Introspect, as they're not yet in SwiftUI.
The issue that I'm seeing is that if I show that view via a NavigationView
more than 3 times, the mp4s no longer load and play.
Additionally, inspecting the memory graph, shows that even if I navigate away from that view, there are about 60 LazyImage
and VideoPlayerView
sticking around in memory, which I assume is the crux of the issue.
Commenting out the Introspect portion fixes the issue and everything works as expected, but I need it in my app for other things as well.
Now, this might very well be an Introspect issue and I'm raising an issue in that repo as well, but just wanted to see whether the maintainer here might have any insights into why this is happening.
Code that reproduces the issue
Tested on iOS 15.4.1 on iPhone 12 Pro Max, compiled with Xcode 13.3.1 (interestingly the simulator doesn't show the issue)
NukeUI version: 0.8.1
Introspect version: 0.1.4
Video of the issue:
Memory graph:
Thanks!
Hi,
I work on a shared photo library. The user has access to a list of images, most-recent-first, and loads older images as the user scrolls. The UI is similar to a simplified Apple Photos app with square images.
I have a LazyVGrid
(3 columns) which contains LazyImage
s. The loaded images are medium sized (max side 1190px). The list holds initially 100 URLs. Using infinite scroll logic it keeps appending 100 images to the end of the list until the end of the user's library is reached. Most users only need the first few hundred images loaded, but having everything in the list needs to be a possibility in the app's lifecycle.
As I see while scrolling it is incredibly smooth and the images appear appropriately as they are displayed (thank you @kean 🙌). However I think I'm doing something wrong because it looks like the images are totally cached in full size in memory. If I scroll to the past far enough (total 1235 images), Xcode shows a memory usage of ~3.66 Gb for iOS Simulator which is the weight for that amount of images (tested on iPhone 6s, lowest config available for our app target which is iOS 14.0).
I have a TileView
which essentially renders the LazyImage within:
LazyImage(source: urlString) { state in
if let image = state.image {
image
.scaledToFill()
.squareFrame(sideLength: sideLength)
.cornerRadius(5)
} else {
EmptyView()
}
}
.onDisappear(.reset)
.processors([ImageProcessors.Resize(size: CGSize(width: sideLength, height: sideLength), crop: true)])
.squareFrame(sideLength: sideLength)
It looks like ImageProcessors.Resize
is not enough to limit the memory impact. Are there other modifiers I could use?
I tried using the pipeline .withDataCache
instead of the default, but the memory impact is the same.
Can I tell LazyImage to either keep the cache on disk instead of RAM, and if not possible, to display only a resized thumbnail and throw away the recenly loaded image? I can redownload it later in a separate zoomable view which is acceptable for our needs because it is not often used.
Can I tell LazyImage to not bother loading if the user "scrolls too fast"?, I was hoping that onDissappear(.cancel)
would do the trick but maybe the internals of LazyVGrid are messing with this modifier.
In a future version of this list we will implement a thumbnail URL which will be the first LazyImage
loaded, and when done, will load another LazyImage with the medium URL. I would like this to happen only for LazyImages that are shown on screen.
Thank you again very much for this incredible library.
Cheers
Sébastien
I'm using a LazyImage in a LazyVGrid. The LazyImage is called:
LazyImage(source: url, resizingMode: .aspectFill)
When I long press the image, the image goes blank when long pressed to trigger the contextMenu. I replaced the LazyImage with the following ImageView and everything works fine.
My app is iOS 14.3 and above, and my device has iOS 15.0.2 installed.
struct ImageView: View {
let url: URL?
@StateObject private var image = FetchImage()
var body: some View {
ZStack {
Rectangle().fill(Color.primary)
image.view?
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
}
.onAppear {
if let url = url {
image.load(url)
}
}
.onChange(of: url) { image.load($0) }
.onDisappear(perform: image.reset)
}
}
Is this a known issue, or is there something I need to adjust?
Thanks
I have the following code:
LazyImage(source: model.fileUrl)
.processors([ImageProcessors.Resize(width: 800)])
.transition(.fadeIn(duration: 0.15))
.aspectRatio(1, contentMode: .fit)
.background(Color(.controlColor))
The image appears, however its not filling/scaling up as I'd expect to fill the container. It appears to have a fixed size, cantering inside of the container? The background therefore shows around the edges. Is this currently unsupported?
FYI, I've reverted for the moment to the old FetchImage
implementation directly and that works fine, but would be great to use this new type as I think its far better from an API POV 👍
Great work by the way, my favourite image library in over a decade!
This is really great, thx for this!
Can you tag this and lock the Nuke version in the package manager to 10.0.0
? Because I use other dependencies, like GetStream, that also uses Nuke as a dependency so the versions need to line up.
Hello,
When trying to load images this way:
LazyImage(source: $0) { state in if let image = state.image { image // Displays the loaded image } else if state.error != nil { Color.red // Indicates an error } else { Color.blue // Acts as a placeholder } }
gif are not animated.
But it works with the simple call: LazyImage(source: imageUrl, resizingMode: .aspectFill)
.
Any idea?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.