Description/ What I've tried
I imported a library that lets me beautifully load images asynchronously into my UITableView (https://github.com/natelyman/SwiftImageLoader) . The problem is that it doesn't support gifs. So I did case statement to independently retrieve the gif if need be and display the gif using another library.
Problem
Now the problem is that whenever I try loading a gif, the scrolling gets really choppy.
Question
Can someone please help me figure this out? Does it involve putting it on a background thread? If so, how?
Code
if let url = NSURL(string: arrayByVotes[indexPath.row].objectForKey("thumbnail") as NSString) {
let imageURL = arrayByVotes[indexPath.row].objectForKey("thumbnail") as NSString
if (imageURL.lowercaseString.rangeOfString("gif") != nil) {
cell.thumbnail.image = UIImage.animatedImageWithAnimatedGIFURL(url)
} else {
ImageLoader.sharedLoader.imageForUrl(arrayByVotes[indexPath.row].objectForKey("thumbnail") as NSString, completionHandler:{(image: UIImage?, url: String) in
cell.thumbnail.image = image
})
}
}
Maybe having many gif images in memory at the same time is affecting the performance of the scrolling. Try removing the images from the cells that are invisible and add the image only when the cell will show up. A good method for this would be tableView(_:willDisplayCell:forRowAtIndexPath:) from the UITableViewDelegate
Related
In my SwiftUI app I have a view for people to edit a book's metadata, such as its title and author. In this view I have an image view that shows the book's cover image.
struct ImageView: View {
#Binding var book: Book
var body: some View {
// coverFile is the URL to the image file.
let image = NSImage(byReferencing:
book.metadata.coverFile)
Image(nsImage: image)
.aspectRatio(contentMode: .fit)
}
}
When I open a book that I've set a cover image and open the metadata editor, the image view displays nothing. The metadata editor has a button to choose a cover image. When I click the button and choose an image file from the file importer, the image appears in the image view. If I close the book, reopen it, and open the metadata editor, the image appears in the image view. But if I restart the app, open the book, and open the metadata editor, the image view displays nothing.
I have tried the various NSImage initializers. I tried building the image from a Data object and from NSBitmapImageRep.
extension Book {
func coverImage() -> NSImage {
do {
let data = try Data(contentsOf: metadata.coverFile)
return NSImage(data: data) ?? NSImage()
} catch {
Swift.print("Error reading the image data. Error: \(error)")
}
return NSImage()
}
}
struct ImageView: View {
#Binding var book: Book
var body: some View {
let image = book.coverImage()
Image(nsImage: image)
.aspectRatio(contentMode: .fit)
}
}
But when I set a breakpoint in the image view and check the image variable, it's either nil or invalid. When I check the coverFile variable, it displays the proper URL, including the .png extension.
In an AppKit version of this app, I have the same code to create the NSImage, and the image displays in the image view.
According to Apple's documentation, the NSImage byReferencing function does not attempt to retrieve the data from the specified URL or create any image representations from that data until an app attempts to draw the image or request information about it. How do I get the SwiftUI view to trigger retrieving the image data so it displays in the image view?
UPDATE
I created a computed property for the image in the ImageView struct.
var image: NSImage {
do {
let data = try Data(contentsOf: book.metadata.coverFile)
return NSImage(data: data) ?? NSImage()
} catch {
Swift.print("Error creating the image. Error: \(error)")
}
return NSImage()
}
When I run this code, the try Data call throws an exception, the catch block runs, and the following error message is printed in Xcode's Console:
Error creating the image. Error: Error Domain=NSCocoaErrorDomain
Code=257 "The file “ImageFile” couldn’t be opened because you
don’t have permission to view it."
Choosing a file from the file importer causes the data to be created properly. I have read/write permission for the folder where the image file is.
From what I understand the problem boils down to lazily initializing the NSImage.
Have you tried using init(contentsOfFile:) or init(contentsOf:)? It does not initialize it lazily therefore should not cause this problem.
The image does not display because of a file permissions problem. If I turn on the App Sandbox and give the app Read access to the Pictures folder, the cover image appears when I open the metadata editor.
I have added an image (jpg) from my Photo Album to the UserResources folder (via the + menu in the upper right of the Playground app). How do I reference that file when declaring a SwiftUI Image?
I have tried using the entire path:
let path: String = { Bundle.main.paths(forResourcesOfType: "JPG", inDirectory: nil)[0] }()
It seems to give me the path to the desired file, “PICT0024.JPG”:
/var/mobile/Containers/Data/PluginKitPlugin/F01FA67E-97CA-4590-915A-C8071507C6E0/Library/Application Support/Main Module Resources Bundles/55929C8C-46C9-4691-B86B-389D1473E9A8.bundle/Contents/Resources/PICT0024.JPG
I then declare:
Image(systemName: path)
But the image is not shown. I probably have the wrong reference, but cannot figure out what I need to specify.
Oh, just this
Image(“PICT0024.JPG”)
doesn’t work either.
What is the proper way to reference that image file?
Please confine answers to SwiftUI 5 and editing on an iPad. Using XCode at the moment isn’t an option.
Thanks.
Image(systemName: path)
this constructor one is for system SF Symbols
Image(“PICT0024.JPG”)
this constructor is for images in Assets catalog
The external images you have to use like this
Image(uiImage: UIImage(contentsOfFile: path))
import SwiftUI
import PlaygroundSupport
// Add image to the project with the "+" button,
// then select either the file, or image icon,
// then navigate to desired image,
// then add it to the iPad Playground project
let imageFileNameString = "IMG_2702.jpeg"
struct ContentView: View {
var body: some View {
// the method below requires the UIImage be unwrapped
Image(uiImage: UIImage(named: imageFileNameString)!)
// -or-
// The commented-out method below does not require any unwrapping.
// Typos will fail with "something went wrong around here" error
// Image(uiImage: UIImage(imageLiteralResourceName: imageFileNameString))
.resizable()
}
}
PlaygroundPage.current.setLiveView( ContentView() )
This is what I used:
https://www.appcoda.com/swiftui-swift-playgrounds-ipad-mac/
[Edit by original poster]
Summarizing from the article (worth a read because it discusses far more than how to access an image resource), here’s how to do it:
Type Image. and using the autocomplete bar, pick the moon and mountains icon (ImageLiteral)
That displays the “Images” popup. If you have not already added an image, choose either “Insert From...“, “Photo Library”, or “Take Photo”. Once you add an image, select it and press, “Use” (upper right corner of popover).
The result looks like this:
[The image here is of buttercups, Ranunculus acris.]
i have an application where i am loading large amount of images from database and showing them in my gallery, what i was doing earlier is i was getting photo url (local identifier) from PHAsset and then accessing the image using PHCacheManager, but it slows down my scrolling and also shows lags while updating images in new cell. I was trying to use SDWebImage here by passing the url to the sd_setImageWithURL: method, however i found out that the URL is actually a local identifier which is not supported by the method. Do we have other mechanism where i can use the identifier to load the images faster using SDWebImage or any other framework.
This is my code:
NSURL *photoURL = [NSURL URLWithString:photoPath];
[imageView sd_setImageWithURL:photoURL placeholderImage:defaultImage];
photoPath is the path to my asset "current image to be loaded"
i am getting the url something like:
73F05642-CAE6-49BE-879B-9B00BF15391F/L0/001
Please ask if more information is needed, i am new to iOS and all this stuff. Thanks in advance :)
it's late , but i am posting it so that it will be helpful for others .
SDWebImageManager * manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:[NSURL URLWithString:localIdentifier] options:SDWebImageHighPriority targetLocalAssetSize:CGSizeMake(150, 150) progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
imageview.image = image;
}
}];
HI all,
I wnt to develop an ImageViewer using qt. I m trying to resize big images by scaling them. My problem is , when i change the screen orientation some part of the image gets clipped and also if i open the image in landscape mode, by default the size of image remains small even when i change back to portrait mode. What am i Doin wrong?
Please help me out. Heres the code dat i hv written
ImageViewer::ImageViewer()
{
setAttribute(Qt::WA_DeleteOnClose);
QAction *back = new QAction(this);
back->setText(QString("Back"));
connect(back,SIGNAL(triggered()),this,SLOT(close()));
back->setSoftKeyRole(QAction::PositiveSoftKey);
addAction(back);
imageLabel = new QLabel();
imageLabel->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
imageLabel->setAlignment(/*Qt::AlignLeft|*/Qt::AlignCenter);
QWidget *widget = new QWidget;
layout=new QStackedLayout();
layout->addWidget(imageLabel);
widget->setLayout(layout);
setCentralWidget(widget);
}
void ImageViewer::showImage(QString filePath)
{
QImageReader reader;
reader.setFileName(filePath);
QSize imageSize = reader.size();
imageSize.scale(size(), Qt::KeepAspectRatio);
reader.setScaledSize(imageSize);
QImage image = reader.read();
imageLabel->setPixmap(QPixmap::fromImage(image));
imageLabel->adjustSize();
}
You should re-implement QLabel's resizeEvent or install event filter to it and handle QResizeEvent there
The content of showImage method should go to handler of a resize event.
Currently you are using size() of ImageViewer widget (which seems to be derived from QMainWindow), it's better to use imageLabel.size(); or the best QResizeEvent::size() as this will prevent a problem if you will change UI layout in future.
I'm using a standard caching method to cache some UIImages loaded from the Documents directory. The images are being displayed in a UITableView. They're pretty large – the images themselves are up to 600x600 and are displayed in imageviews that are 240x180 (on a retina display, so the res discrepancy is not large).
Loading the images in realtime causes some lag when a new cell is about to come onscreen. So I've implemented a caching method in the object that handles the image:
- (UIImage *)imageWithStyle:(NSString *)styleName {
NSLog(#"looking for image %#", self.imageFileName);
/* if we have a cached image in dictionary, return it */
if (imageCache == nil) imageCache = [[NSMutableDictionary alloc] init];
UIImage *returnImage = [imageCache objectForKey:styleName];
if (returnImage != nil) {
NSLog(#"returning cached image");
return returnImage;
}
/* otherwise, look for image at path */
NSString *path = [self cacheFilePathWithStyle:styleName];
UIImage * originalImage = [[UIImage alloc] initWithContentsOfFile:path];
/* if image doesnt exist at path, start download and return nil */
if (originalImage == nil) {
NSLog(#"image not found. downloading.");
[self downloadImageFromS3];
return nil;
}
/* found image at path */
/* scale image for screen */
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2){
returnImage = [UIImage imageWithCGImage:[[originalImage autorelease] CGImage] scale:2.0 orientation:UIImageOrientationUp];
NSLog(#"scaling image for retina");
} else {
returnImage = [originalImage autorelease];
NSLog(#"image scaled for standard resolution");
}
/* cache image in dictionary */
NSLog(#"caching image");
[imageCache setObject:returnImage forKey:styleName];
return returnImage;
}
Before the tableview appears on screen, I force all of the image handling objects to run this caching method to ensure that the images are present in the dictionary to be retrieved when they need to be displayed. By the NSLog's I can see that things are working as they should.
I'm getting lag-free performance now, but only after the image has appeared once on screen. So, when I initially see the tableview, I scroll down and the NSLogs tell me that the images are being retrieved from the cache, but still I get the same loading lag. After a cell has appeared on screen once, it thereafter loads up with no lag.
Is there something I'm missing here? Is there something more that I have to to to actually cache the image? Loading it and putting it in the dictionary doesn't seem to do the trick.
Thanks!
UPDATE
I've given up on this for now. There are some attempts out there to force the images to load by drawing them in a new context, but I'm not familiar with core graphics programming at this point. I've tried some code that folks have shared, but with no luck.
Instead, I'm going to display low-res versions of the images while the tableview is scrolling and load high-res versions when the tableview stops scrolling as announced through its delegate methods. At least I know this approach will work with any number of images.
From the documentation for -[UIImage initWithContentsOfFile:]:
This method loads the image data into memory and marks it as purgeable. If the data is purged and needs to be reloaded, the image object loads that data again from the specified path.
My guess is that, by loading all images into memory, your app consumes too much memory, causing the UIImage class to release the image data it can reload from files later.
If you use [UIImage imageNamed:], it'll do all this caching business for you. No need to roll your own cache and then wonder if it's working.
An upside and a downside to using that method. Upside: If you use the same image file twice, it won't actually load it twice, saving you that memory. Downside: Caching has a big memory impact, and you want to think pretty hard about doing it, whether you roll your own or not.