When I present a UIImagePickerController, I want the user to be able to crop a 7.8 ratio for the banner that the user wants to import from their photo library.
This question is similar to many questions relating to creating custom crop rects built to work with UIImagePickerController but all the answers point to outdated libraries or libraries with too much complexity. I want something simple.
Try this:
open func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var newImage: UIImage
if picker.sourceType == .camera {
if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
// save photo
}
}
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad && picker.sourceType == .photoLibrary {
if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
newImage = possibleImage
}
} else {
if let possibleImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
newImage = possibleImage
} else if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
newImage = possibleImage
}
}
// logic to manipulate image
print("image size: \(newImage.size)")
let newHeight = 100 // change to preferred height
let scale = 7.8
let newWidth = newImage.size.width
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
newImage.draw(in: CGRect(x: 0, y: 0,width: newWidth, height: newHeight))
newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
print("image size: \(newImage.size)")
// add logic here
}
Related
I have this extension and it works perfect in app target but crash in share extension when trying to rotate image captured on camera. How to rotate image in share extension? Or maybe it possible to load image from Photo Library already in right orientation.
extension UIImage {
func fixOrientation() -> UIImage {
switch imageOrientation {
case .up:
return self
default:
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(in: CGRect(origin: .zero, size: size)) //Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result!
}
}
}
Crash screenshots:
First of all it is clear that you have a memory crash. According to App Extension Programming Guide:
Memory limits for running app extensions are significantly lower than the memory limits imposed on a foreground app. On both platforms, the system may aggressively terminate extensions because users want to return to their main goal in the host app.
And from error it is clear that you exceed 120 mb. But you might wonder what is took so much memory.
According to Optimizing Images
Written by Jordan Morgan:
iOS essentially derives its memory hit from an image’s dimensions - whereas the actual file size has much less to do with it.
So if we calculate size or 4032 x 3024 photo it will be... 46mb for 4 bit color and 79mb for 8 bit color. Pretty big, but still less that a limit...
Thing is - you have two copies of your image. One is original and second one - rotated.
To solve this issue you need load only rotated image into memory, without original. This can be done with Image I/O Framework:
extension UIImage {
static func imageWithFixedOrientation(at url: URL) -> UIImage? {
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
guard let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? Dictionary<CFString, Any> else { return nil }
guard
let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat,
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat
else { return nil }
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: max(width, height),
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true
]
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { return nil }
return UIImage(cgImage: cgImage)
}
}
In sample app:
extension ViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let url = info[.imageURL] as? URL else { return }
let image = UIImage.imageWithFixedOrientation(at: url)
}
}
it reduced memory peaks from 180+mb to just 80mb.
You may try to use more optimized UIGraphicsImageRendererFormat instead of using old UIGraphicsBeginImageContextWithOptions, you can find more information in this post. So your code will look something like below:
func fixOrientation() -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = scale
format.prefersExtendedRange = true
let renderer = UIGraphicsImageRenderer(size: size, format: format)
let image = renderer.image(actions: { context in
var workSize = size;
workSize.width = floor(workSize.width / scale)
workSize.height = floor(workSize.height / scale)
// if the orientation is already correct
// if image.imageOrientation == .up { draw image }
var transform = CGAffineTransform.identity
//TO-DO - set transform depends on current image orientation
//transform =
let ctx = context.cgContext
ctx.concatenate(transform)
guard let cgImageCopy = cgImage else {
return
}
switch imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
ctx.draw(cgImageCopy, in: CGRect(x: 0.0, y:0.0, width: workSize.height, height: workSize.width))
break;
default:
ctx.draw(cgImageCopy, in: CGRect(origin: .zero, size: workSize))
break;
}
})
return image
}
Did you try using the function you provided within an didFinishPicking delegate method?
func photoLibrary() {
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = .photoLibrary
self.present(myPickerController, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[.originalImage] as? UIImage else {
print("No image found")
return
}
// Flip the image here
self.mainImageView.image = image.fixOrientation()
}
I have set up my application so that when I press the button "cambiaimmagineutente" a picker controller appears and I can choose the image which I then upload to FIRStorage using the "UIImagePickerControllerReferenceURL". I cannot find a way to resize the image before uploading it to save space and to place it in a smaller image view.
Here is the code:
#IBAction func cambiaImmagineUtente(_ sender: UIButton) {
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion:nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
picker.dismiss(animated: true, completion:nil)
// if it's a photo from the library, not an image from the camera
if #available(iOS 8.0, *), let referenceUrl = info[UIImagePickerControllerReferenceURL] as? URL {
let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceUrl], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL
let filePath = FIRAuth.auth()!.currentUser!.uid +
"/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\(imageFile!.lastPathComponent)"
// [START uploadimage]
self.storageRef.child(filePath)
.putFile(imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
//an error occured
print("Error uploading: \(error)")
return
}
self.uploadSuccess(metadata!, storagePath: filePath)
}
// [END uploadimage]
})
} else {
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
guard let imageData = UIImageJPEGRepresentation(image, 0.8) else { return }
let imagePath = FIRAuth.auth()!.currentUser!.uid +
"/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
self.storageRef.child(imagePath)
.put(imageData, metadata: metadata) { (metadata, error) in
if let error = error {
//an error occured
print("Error uploading: \(error)")
return
}
self.uploadSuccess(metadata!, storagePath: imagePath)
}
}
}
func uploadSuccess(_ metadata: FIRStorageMetadata, storagePath: String) {
print("Upload Succeeded!")
//self.urlTextView.text = metadata.downloadURL()?.absoluteString
UserDefaults.standard.set(storagePath, forKey: "storagePath")
UserDefaults.standard.synchronize()
//self.downloadPicButton.isEnabled = true
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
You can use this:
func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
let size = image.size
let widthRatio = targetSize.width / image.size.width
let heightRatio = targetSize.height / image.size.height
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)
}
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
Use:
let resizedImage = resizeImage(image: selectedImage, targetSize: CGSize.init(width: 300, height: 300))
make sure you also make a write rule to a max value in your storage rules!
Hello I am displaying images on my app using sdwebImage. I have a code here to resize the image
func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage {
let scale = newWidth / image.size.width
let newHeight = image.size.height * scale
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
image.drawInRect(CGRectMake(0, 0, newWidth, newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
The problem is the above function accepts the UIImage as parameter and sdwebimage accepts the URL. How can I call the above resize function in sdwebimage. or in short how Can I resize the image that are presenting through sdwebImage here
cell.profileImageView.sd_setImageWithURL(UIImage().absoluteURL(profileImageUrl as! String), placeholderImage: UIImage.init(named: "default-profile-icon")?.circle!, completed: completionBlock)
do like
cell.profileImageView.sd_setImageWithURL(
NSURL(string: profileImageUrl as! String),
placeholderImage: UIImage.init(named: "default-profile-icon"),
options: nil,
progress: nil,
completed: { (image: UIImage?, error: NSError?, cacheType: SDImageCacheType!, imageURL: NSURL?) in
guard let image = image else { return }
print("Image arrived!")
cell.profileImageView.image = resizeImage(image, newWidth: 200)
}
)
SDWebImage supports image handling directly through its SDWebImageManagerDelegate protocol. You can use imageManager:transformDownloadedImage:withURL: method to transform the downloaded image.
You can set the image manager delegate like this:
SDWebImageManager.sharedManager.delegate = self;
Use shared SDWebImageManager is not good idea. Some processes can download images in this time.
My swift 3 example with custom SDWebImageManager in the class and custom SDWebImageManagerDelegate resizer.
import SDWebImage
class ImageResizer: NSObject, SDWebImageManagerDelegate {
private func resizeImage(_ image: UIImage, newHeight: CGFloat) -> UIImage {
let scale = newHeight / image.size.height
let newWidth = image.size.width * scale
UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: newHeight), false, 0)
image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
public func imageManager(_ imageManager: SDWebImageManager, transformDownloadedImage image: UIImage?, with imageURL: URL?) -> UIImage? {
guard let _image = image else {
return nil
}
return resizeImage(_image, newHeight: 20)
}
}
class BasicTrainView: XibView {
static let imageManager: SDWebImageManager = SDWebImageManager()
static let imageResizer = ImageResizer()
func xxx() {
BasicTrainView.imageManager.delegate = BasicTrainView.imageResizer
BasicTrainView.imageManager.loadImage(with: logoURL, options: [], progress: nil) { (image, _, error, sdImageCacheType, _, url) -> Void in
guard let _image = image else {
self.carrierLogoImageView.image = nil
return
}
self.carrierLogoImageView.image = _image
}
}
}
SDWebImage has this functionality built in. This is how to use it
let imageSize = cell.fanartImageView.bounds.size * UIScreen.main.scale
let transformer = SDImageResizingTransformer(size: imageSize, scaleMode: .fill)
cell.fanartImageView.sd_setImage(with: url, placeholderImage: image,
options: SDWebImageOptions(rawValue: 0),
context: [.imageTransformer: transformer],
progress: nil) { (image, error, cache, url) in
if error != nil {
// handle error
}
}
I'm trying to crop an image that has been selected and edited through the UIImagePicker. For some reasons the picker doesn't return a UIImagePickerControllerEditedImage value. So what I'm trying to do is to get the original value and crop it according to the rectangle in UIImagePickerControllerCropRect but that's a NSRect and I need to convert it to a CRRect to crop it. This is my image picker controller function :
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
//Cut photo or show subview
let chosenImage : UIImage
print(editingInfo)
if let possibleImage = editingInfo!["UIImagePickerControllerEditedImage"] as? UIImage {
print("Edited image")
chosenImage = possibleImage
} else if let possibleImage = editingInfo!["UIImagePickerControllerOriginalImage"] as? UIImage {
print("Non edited image")
if let rectangle = editingInfo!["UIImagePickerControllerCropRect"] as? [[Int]] {
print(rectangle[0])
}
let rect: CGRect = CGRectMake(0, 230, 750, 750)
let imageRef: CGImageRef = CGImageCreateWithImageInRect(possibleImage.CGImage, rect)!
let image: UIImage = UIImage(CGImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
chosenImage = image
} else {
return
}
self.profilePicture.image = chosenImage.rounded?.circle
dismissViewControllerAnimated(true) { () -> Void in
//update the server
}
}
Everything else works fine except it crops the image according to the rectangle in CGRectMake(0, 230, 750, 750) I'd like to make that instead of fixed values the values of UIImagePickerControllerCropRect . Please let me know if you have any suggestion on how to get that.
Thank you!
var rect: CGRect = CGRectMake(0, 230, 750, 750)
if let rectangle = editingInfo!["UIImagePickerControllerCropRect"] as? NSValue {
rect = rectangle.CGRectValue()
}
let imageRef: CGImageRef = CGImageCreateWithImageInRect(possibleImage.CGImage, rect)!
You used wrong variable I suppose
Hello I am using SDWebImage in my app. This is my code to make the image in circle
extension UIImage {
var circle: UIImage? {
let square = CGSize(width: min(size.width, size.height), height: min(size.width, size.height))
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
imageView.contentMode = .ScaleAspectFill
imageView.image = self
imageView.layer.cornerRadius = square.width/2
imageView.layer.masksToBounds = true
UIGraphicsBeginImageContext(imageView.bounds.size)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
imageView.layer.renderInContext(context)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
copied it from here
I used to do images in cicle like this
let profilePicture = UIImage(data: NSData(contentsOfURL: NSURL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!)!)!
profilePicture.circle
But Now As I am using SDWebImage its not working
cell.profileImageView.sd_setImageWithURL(UIImage().absoluteURL(profileImageUrl), placeholderImage: UIImage.init(named: "default-profile-icon")?.circle!)
Please let me know how can I make this extension work for SDWebImage
You can use the SDWebImageManager to download the image or take it from the cache and apply the circle in the completion block like this:
SDWebImageManager.sharedManager().downloadWithURL(NSURL(string:"img"), options: [], progress: nil) { (image:UIImage!, error:NSError!, cacheType:SDImageCacheType, finished:Bool) -> Void in
if (image != nil){
let circleImage = image.circle
cell.profileImageView.image = circleImage
}
}
Or you can use the version of the sd_setImageWithURL method that takes a completion block as a parameter
let completionBlock: SDWebImageCompletionBlock! = {(image: UIImage!, error: NSError!, cacheType: SDImageCacheType!, imageURL: NSURL!) -> Void in
if (image != nil){
let circleImage = image.circle
cell.profileImageView.image = circleImage
}
}
cell.profileImageView.sd_setImageWithURL(UIImage().absoluteURL(profileImageUrl), placeholderImage: UIImage.init(named: "default-profile-icon")?.circle!, completed: completionBlock)