I have a UIPageViewController showing a UIViewController that contains a UIImageView.
The images displayed in the UIImageView originally come from the camera roll. I copy them in the application's folder. When I display the UIPageViewController, it loads the image for the current page and displays it:
if let image = UIImage(contentsOfFile: imagePath) {
imageView.image = image
}
The problem is that it's really slow. Loading the first image is slow and then swiping is very slow.
I am testing this on my iPhone 5. I installed the app through XCode. Is there a way to make it faster? Should I save a smaller version of the image when I save the images from the camera roll into my application's folder?
You could preload the images like Sergii suggested or load the images a background thread to avoid blocking the main thread.
NSOperationQueue().addOperationWithBlock {
if let image = UIImage(contentsOfFile: imagePath) {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.imageView.image = image
}
}
}
You can load images you'll need before transitioning to UIPageViewController. Also, take a look at some projects for image caches
FastImageCache, SDWebImage, Haneke
Also, you can be interest in this article Avoiding Image Decompression Sickness
To increase the speed of swiping you'd need to move where you are assigning the image to the UIImageView from the main thread to a background thread.
There are a few ways to do this...
Using NSOperationQueue
NSOperationQueue().addOperationWithBlock {
if let image = UIImage(contentsOfFile: imagePath) {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.imageView.image = image
}
}
}
Or you can use GCD and calling dispatch_async()
if let image = UIImage(contentsOfFile: imagePath) {
dispatch_async(dispatch_get_main_queue()) {
imageView.image = image
}
}
With regards to saving an image from the Camera Roll when/if you save it to your apps file system you could try using:
let qualityFloat = 1.0
let imageData = UIImageJPEGRepresentation(imageFromCameraRoll, qualityFloat)
Playing around with qualityFloat (between 0.0 - 1.0) will change the file size of the image which could help you with the loading times.
Related
Is there a way to integrate a UIImageView when taking photos?
I can't seem to find a topic discussing this.
What I wanted to do is to have a UIImageView overlay on the preview. When the shutter is tapped, it should take the picture with the UIImageView embedded in it.
So here's where I am right now. The "test image" part will be made draggable on top of the preview view. I want to include that image with the image I'll be taking when I push the shutter button.
Is this possible? Or is there a better way?
I'm using AVCaptureVideoPreviewLayer, AVCaptureSession and AVCapturePhotoOutput. I'm just putting the UIIMageView above the UIView where the AVCaptureVideoPreviewLayer is also a subview.
EDIT: Tested saving the UIView as an image, and it doesn't include the preview layer (only returns the UIView + UIImageView overlay).
There is at least two ways of doing this.
1) Capture photo from camera and add overlay image based on draggable image view position. This will produce best possible quality.
2) If you don't need photo quality you can use another solution - render draggable view layer in graphic context and video preview layer:
func takePhotoWithOverlay() -> UIImage? {
let overlayImageView // UIImageView with overlay image
let videoPreviewLayer // AVCaptureVideoPreviewLayer
guard let superview = overlayImageView.superview else { return nil }
UIGraphicsBeginImageContextWithOptions(superview.bounds.size, false, 0.0)
defer { UIGraphicsEndImageContext() }
guard let context = UIGraphicsGetCurrentContext() else { return nil }
videoPreviewLayer.render(in: context)
overlayImageView.layer.render(in: context)
return UIGraphicsGetImageFromCurrentImageContext()
}
I'm loading images from the photo library via UIImagePickerController and with that image I'm setting the material of a SCNPlane:
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
plane.firstMaterial?.diffuse.contents = imageView
With some images, this works fine, no problem at all, the image shows up properly. However, with other images, the texture of the plane shows up as entirely black and Xcode prints "Unsupported IOSurface format: 0x00000000". It seems to be only images that were screenshots causing this, although some screenshot images work just fine.
SceneKit can display both PNGs and JPGs, some PNGs just seem to cause issues. Not sure why or whether it's a bug or not. What you can do that's probably better than converting everything to JPGs (so you can retain transparency) is set your material contents to the image's CGImage:
if let cgImage = image.cgImage {
geometry.firstMaterial?.diffuse.contents = cgImage
}
You can also convert your image to a JPEG via something like this, as SceneKit doesn't seem to have issues with JPGs:
if let jpegData = image.jpegData(compressionQuality: 1.0) {
image = UIImage(data: jpegData)!
}
You will lose transparency doing it this way however.
My UIImage holds a .png image loaded from disk and is used to set backgroundColor in a background view. That works.
I now want to convert to .JPG to remove transparency / alpha channel. I have tried a few variations, but here's an example:
var orgImage: UIImage? = nil;
var newImage: UIImage? = nil;
orgImage = UIImage(contentsOfFile: "...");
let tmpData: NSData? = UIImageJPEGRepresentation(orgImage!, 1.0);
newImage = UIImage(data: tmpData);
However, when using newImage for the background, the image appears zoomed in. I don't want that. I want the same image, simply converted to JPG.
The code I use for this is:
// works
myView.backgroundColor = UIColor(patternImage: orgImage!)
// appears zoomed/scaled
myView.backgroundColor = UIColor(patternImage: newImage!)
I have tried (for testing purposes) to do a PNG to PNG (over disk or NSData) and saving/loading to/from disk instead. All tests yielding the same result.
Any idea what I am doing wrong? I am testing in iOS simulator.
I'm working on a Swift iOS app where I want the user to be able to place an image (a sticker such as a beard or an eyepatch) over another image.
Currently I have it working to where they can take a photo or pull an image up from their Photos and have that display in a UIImage. Obviously next is adding the sticker. I've been able to dig up zero data on this from Google or Stack. And I mean zero.
Does anyone have a general idea of how I would execute this? Specifics would help too of course.
Here's the easy way.
have a UIView as a parent, let's say stickerView
add image to stickerView
add stickers to stickerView
save a snapshot of stickerView
Here's the code to snapshot UIView,
extension UIImage {
class func imageWithView(view: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
}
Usage
let snapshotImage = UIImage.imageWithView(stickerView)
I would suggest you to add UIImageView for sticker instead of editing image when adding sticker. And after that when save the image then you should edit the image.
I have an UICollectionView which loads images of the iPad's memory and displays them in a grid,like Apple's Photos app. The UICollectionViewCell loads thumbnails asynchronously:
func setImage(img:String){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//load the image in the background
let image = UIImage(contentsOfFile: img)
//when done, assign it to the cell's UIImageView
dispatch_async(dispatch_get_main_queue(), {
if let imageView = self.imageView{
imageView.image = UIImage(contentsOfFile: img)
}
})
})
}
However, while scrolling the view lags as if it is waiting for the images to load, especially with Retina graphics. The cells and images are about 240x180px big. Is there anything wrong with the image loading above or further optimisations need to be made?
UPDATE: Time profiler results
You've already found that you're loading the UIImage again on the main queue; fixing that will help.
UIImage lazy loads its internal image data in most cases. One trick is to call its CGImage property while still on the background queue to force it to actually create its internal image data instead of lazily loading it when the image view is drawn the first time:
func setImage(img:String){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//load the image in the background
let image = UIImage(contentsOfFile: img)
image.CGImage // <- Force UIImage to not lazy load the data
//when done, assign it to the cell's UIImageView
dispatch_async(dispatch_get_main_queue(), {
if let imageView = self.imageView {
imageView.image = image
}
})
})
}
Note: If you have a lot of images you may end up getting a memory warning fairly quickly doing this. If you do, this probably won't help because the memory warning will typically cause UIImage to clear its internal image data again to free up resources.
On the line
imageView.image = UIImage(contentsOfFile: img)
I was loading the image again on the main thread, not the image loaded asynchronously. Changed to
imageView.image = image
Scrolling is a bit better, but yet choppy. The time profiler shows similar results as before. May the bottleneck be in the UIImageView drawing? It works fine with non-retina thumbnails.
The bottleneck was that some thumbnails were scaled improperly and were a lot bigger than the UIImageView, which caused the longer loading time. I also presume this caused slower drawing since the UIImage had to be downscaled to fit in the UIImageView.
I fixed the thumbnail-generating code and scrolling is smooth again.
P.S. I also fixed the misplaced UIImage(contentsOfFile: img) variable in the initial code, see my other answer.
in Swift:
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let imagem = UIImage(named :"image")
dispatch_async(dispatch_get_main_queue()) {
cell.IMG_Novidades.image = imagem
}
}
//THIS IS VERY IMPORTANT
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale