I've been wrestling with this issue ad nauseam but still cant figure out what is causing the crash. I instantiate the camera from a button, thereafter when the user chooses the photo i add the photo to my images array and reload the collectionView. After approximately 20 times i get the output in the console "Connection to assets was interrupted or assets died" subsequently followed by a crash. When i remove the collectionView?.reloadData() method from within the delegate method "didFinishPickingMediaWithInfo" it doesnt crash. This has left me extremely obfuscated. See code below:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: Dictionary) {
if let image = info["UIImagePickerControllerOriginalImage"] as? UIImage {
images.append(image)
// when i remove this method the app doesnt crash!
collectionView.reloadData()
collectionView?layoutIfneeded()
dismiss(animated: true, completion: nil)
}
This is the solution, note the compression size is hardcoded, you may want to adapt the code to your needs or refactor and improve the method.
func imageCompression(_ imageDict: [String : UIImage]) -> [String : Data] {
var compressedImages = [String : Data]()
for (key, value) in imageDict {
var resizeAttempts = 5
var compressionRatio: CGFloat = 1
var imageData = UIImageJPEGRepresentation(value, compressionRatio)
if imageData?.count <= 90000 {
compressedImages[key] = imageData
} else {
while imageData?.count > 90000 && resizeAttempts > 0 {
resizeAttempts -= 1
compressionRatio = compressionRatio * 0.5
imageData = UIImageJPEGRepresentation(value, compressionRatio)
compressedImages[key] = imageData
print("image size now is \(imageData?.count)")
}
}
}
return compressedImages
}
Related
I have successfully implemented an Image Picker in my app - it pulls pictures from the library and you can select specific images.
Now after the user selects the image, I want to store the image to a variable somehow in my code so that I can upload the image to a database.
Here's what I currently have in my code:
public class PhotoPickerService : IPhotoPickerService
{
TaskCompletionSource<Stream> taskCompletionSource;
UIImagePickerController imagePicker;
public Task<Stream> GetImageStreamAsync()
{
//Create and define UIImagePickerController
imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
};
//Set Event Handlers
imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
imagePicker.Canceled += OnImagePickerCancelled;
//Present UIImagePickerController
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
viewController.PresentViewController(imagePicker, true, null);
//Return Task Object
taskCompletionSource = new TaskCompletionSource<Stream>();
return taskCompletionSource.Task;
}
void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
{
//assigns var image to the edited image if there is one, otherwise it'll assign it to the original image
UIImage image = args.EditedImage ?? args.OriginalImage;
if (image != null)
{
//Convert UIImage to .NET stream object
NSData data;
if(args.ReferenceUrl.PathExtension.Equals("PNG") || args.ReferenceUrl.PathExtension.Equals("png"))
{
data = image.AsPNG();
//Console.WriteLine(data);
}
else
{
data = image.AsJPEG(1);
Console.WriteLine(data);
}
Stream stream = data.AsStream();
UnregisterEventHandlers();
taskCompletionSource.SetResult(stream);
}
else
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
}
imagePicker.DismissModalViewController(true);
}
void OnImagePickerCancelled(object sender, EventArgs args)
{
UnregisterEventHandlers();
taskCompletionSource.SetResult(null);
imagePicker.DismissModalViewController(true);
}
void UnregisterEventHandlers()
{
imagePicker.FinishedPickingMedia -= OnImagePickerFinishedPickingMedia;
imagePicker.Canceled -= OnImagePickerCancelled;
}
Here is what I'm wondering: I need to upload this image to a database with the datatype varbinary. The image currently has the datatype NSData which as I've researched is used to store items such as pictures with raw binary data. Am I storing this correctly as of right now to upload to a database? Do I need to do some sort of conversion?
You should implement UIImagePickerControllerDelegate to your ViewController
import UIKit
import AVFoundation
import MobileCoreServices
class ViewController: UIViewController {
var choosenImageData: Data?
fileprivate lazy var imagePicker: UIImagePickerController = {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.mediaTypes = [(kUTTypeImage as String)]
imagePicker.allowsEditing = false
return imagePicker
}()
#IBAction func cameraButtonPressed(_ sender: Any) {
self.imagePicker.sourceType = .camera // .photoLibrary
self.present(self.imagePicker, animated: true, completion: nil)
}
}
extension ViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
self.choosenImageData = image.jpeg(.lowest)
}
}
}
Use this extension to convert UIImage to Data
import Foundation
// MARK: - Compress UIImage Quality
extension UIImage {
enum JPEGQuality: CGFloat {
case lowest = 0
case low = 0.25
case medium = 0.5
case high = 0.75
case highest = 1
}
/// Returns the data for the specified image in JPEG format.
/// If the image object’s underlying image data has been purged, calling this function forces that data to be reloaded into memory.
/// - returns: A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
func jpeg(_ quality: JPEGQuality) -> Data? {
return self.jpegData(compressionQuality: quality.rawValue)
}
}
After you have successfully retrieved selected image data, you can perform your tasks to save/upload to your online or local DB.
This answer is an alternative that you might consider instead of using UIImagePicker
Here this function will allow you to get the photos on the library on the phone, so you can use the result of this function which will be an away of [UIImage] and store it on a variable I understand you need to do. but to actually check what image what the user to upload you will need to build a CollectionView for example and give the user the option to select the photos to upload, will the user selecting the photos from the collection view you can save the index that you can use to define which pictures to upload.
Note: you can specify the request option: (the delivery mode defines the quality of the image you can use fastFormat , opportunistic or highQualityFormat)
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .opportunistic
This is the function just a copy past on your project will do the trick
private func getPhotos() -> [UIImage]? {
// Image Array declaration
var imageArray = [UIImage]()
// Image manager and specify the request option
let imageManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .opportunistic
// Sorted by creation date
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
// Perform the fetch and appand to Image array
if let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count {
imageManager.requestImage(for: fetchResult.object(at: i), targetSize: CGSize(width: 280, height: 280), contentMode: .aspectFill, options: requestOptions) { image, error in
imageArray.append(image!)
}
}
return imageArray
} else {
print("You dont have any photos!")
}
}
return nil
}
I want to show gif image in a UIImageView and with the code below (source: https://iosdevcenters.blogspot.com/2016/08/load-gif-image-in-swift_22.html, *I did not understand all the codes), I am able to display gif images. However, the memory consumption seems high (tested on real device). Is there any way to modify the code below to reduce the memory consumption?
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://cdn-images-1.medium.com/max/800/1*oDqXedYUMyhWzN48pUjHyw.gif"
let gifImage = UIImage.gifImageWithURL(url)
imageView.image = gifImage
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
extension UIImage {
public class func gifImageWithData(_ data: Data) -> UIImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
print("image doesn't exist")
return nil
}
return UIImage.animatedImageWithSource(source)
}
public class func gifImageWithURL(_ gifUrl:String) -> UIImage? {
guard let bundleURL:URL? = URL(string: gifUrl) else {
return nil
}
guard let imageData = try? Data(contentsOf: bundleURL!) else {
return nil
}
return gifImageWithData(imageData)
}
public class func gifImageWithName(_ name: String) -> UIImage? {
guard let bundleURL = Bundle.main
.url(forResource: name, withExtension: "gif") else {
return nil
}
guard let imageData = try? Data(contentsOf: bundleURL) else {
return nil
}
return gifImageWithData(imageData)
}
class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
var delay = 0.1
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifProperties: CFDictionary = unsafeBitCast(
CFDictionaryGetValue(cfProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()),
to: CFDictionary.self)
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
}
delay = delayObject as! Double
if delay < 0.1 {
delay = 0.1
}
return delay
}
class func gcdForPair(_ a: Int?, _ b: Int?) -> Int {
var a = a
var b = b
if b == nil || a == nil {
if b != nil {
return b!
} else if a != nil {
return a!
} else {
return 0
}
}
if a < b {
let c = a
a = b
b = c
}
var rest: Int
while true {
rest = a! % b!
if rest == 0 {
return b!
} else {
a = b
b = rest
}
}
}
class func gcdForArray(_ array: Array<Int>) -> Int {
if array.isEmpty {
return 1
}
var gcd = array[0]
for val in array {
gcd = UIImage.gcdForPair(val, gcd)
}
return gcd
}
class func animatedImageWithSource(_ source: CGImageSource) -> UIImage? {
let count = CGImageSourceGetCount(source)
var images = [CGImage]()
var delays = [Int]()
for i in 0..<count {
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
images.append(image)
}
let delaySeconds = UIImage.delayForImageAtIndex(Int(i),
source: source)
delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
}
let duration: Int = {
var sum = 0
for val: Int in delays {
sum += val
}
return sum
}()
let gcd = gcdForArray(delays)
var frames = [UIImage]()
var frame: UIImage
var frameCount: Int
for i in 0..<count {
frame = UIImage(cgImage: images[Int(i)])
frameCount = Int(delays[Int(i)] / gcd)
for _ in 0..<frameCount {
frames.append(frame)
}
}
let animation = UIImage.animatedImage(with: frames,
duration: Double(duration) / 1000.0)
return animation
}
}
When I render the image as normal png image, the consumption is around 10MB.
The GIF in question has a resolution of 480×288 and contains 10 frames.
Considering that UIImageView stores frames as 4-byte RGBA, this GIF occupies 4 × 10 × 480 × 288 = 5 529 600 bytes in RAM, which is more than 5 megabytes.
There are numerous ways to mitigate that, but only one of them puts no additional strain on the CPU; the others are mere CPU-to-RAM trade-offs.
The method I`m talking about is subclassing UIImageView and loading your GIFs by hand, preserving their internal representation (indexed image + palette). It would allow you to cut the memory usage fourfold.
N.B.: even though GIFs may be stored as full images for each frame (which is the case for the GIF in question), many are not. On the contrary, most of the frames can only contain the pixels that have changed since the previous one. Thus, in general the internal GIF representation only allows to display frames in direct order.
Other methods of saving RAM include e.g. re-reading every frame from disk prior to displaying it, which is certainly not good for battery life.
To display GIFs with less memory consumption, try BBWebImage.
BBWebImage will decide how many image frames are decoded and cached depending on current memory usage. If free memory is not enough, only part of image frames are decoded and cached.
For Swift 4:
// BBAnimatedImageView (subclass UIImageView) displays animated image
imageView = BBAnimatedImageView(frame: frame)
// Load and display gif
imageView.bb_setImage(with: url,
placeholder: UIImage(named: "placeholder"))
{ (image: UIImage?, data: Data?, error: Error?, cacheType: BBImageCacheType) in
// Do something when finish loading
}
I am making a custom view of camera, in which i'm taking photos from gallery and from camera, photo gallery works ok, but camera is not working fine, I'm using UIImagePickerController for it, after taking 3 4 pictures it causes memory leaks and shut down the app, i'm properly presentingviewcontroller and dissmissingviewcontroller but it creates memory leaks issues anyway, i Used leak instrument to track down the issue and i found that UIImagePickerController creats new instance every time it appears to take photo
Avfoundation -[AVCapturePhotoOutput init]
NSSmutableArray Avfoundation -[[AVCapturePhotoOutput init]
please guide me how can i resolve it? because I'm not good in managing memory leaks.
Edit:
this is didfinishdelegate method!
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]){
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage{
self.delegate?.didFinishTakingPhoto(image)
picker.dismissViewControllerAnimated(true, completion: { () -> Void in
self.popMe(false)
})
}
}
func didFinishTakingPhoto(image: UIImage)
{
self.imageView.image = image;
self.startActivity("", detailMsg: "")
self.view.userInteractionEnabled = false
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { () -> Void in
if let chokusItem = self.item {
var size = CGSizeMake(600.0, CGFloat.max)
if Global.shared.highQualityPhotoEnables {
size.width = 900.0
}
let scaledImage = self.imageView.image!.resizedImageWithContentMode(UIViewContentMode.ScaleAspectFit, bounds: size, interpolationQuality: CGInterpolationQuality.High)
let thumbSize = CGSizeMake(80.0, CGFloat.max)
self.thumbImage = self.imageView.image!.resizedImageWithContentMode(UIViewContentMode.ScaleAspectFit, bounds: thumbSize, interpolationQuality: CGInterpolationQuality.High)
self.photo = PhotoViewModel(image: scaledImage, parent: chokusItem)
let delay = 0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
self.imageView.image = scaledImage
self.stopActivity()
self.removeCommentTableViews()
self.removeCommentViews()
self.view.userInteractionEnabled = true
self.showPhotoLimitAlertIfRequired()
})
if Global.shared.shouldSavePhotoToGallery {
let assetsLibrary = ALAssetsLibrary()
assetsLibrary.saveImage(scaledImage, toAlbum: "Inspection Images", completion: { (url, error) -> Void in
print("success", terminator: "")
}, failure: { (error) -> Void in
print("failure", terminator: "")
})
}
}
}
}
This appears to be some kind of bug in iOS , I am not sure however, I got pointed to this thread after making my own question, since I didn't find this one.
However, I am initializing my code in a completely different way than you but the leaked object appears the same.
I have opened a radar to this on:
https://openradar.appspot.com/29495120
You can also see my question here:
https://stackoverflow.com/questions/41111899/avcapturephotooutput-memory-leak-ios
Hope this answer atleast gives you some less headache wasting time while I am awaiting a confirmation.
I've a ViewController where I Crop Image, and then another where I Filter Images which means I've to transfer the cropped data to the filter section. But, I don't get any error. Just that my debugger hangs on that line of code until I step out and still nothing happens
internal func moveToFilter() {
var cropFrame = cropOverlay.frame
cropFrame.origin.x = scrollView.contentOffset.x
cropFrame.origin.y = scrollView.contentOffset.y
if let i = image {
let croppedImage = i.crop(cropFrame, scale: scrollView.zoomScale)
print(croppedImage)
let confirmViewCamera = self.storyboard?.instantiateViewControllerWithIdentifier("filterPhoto") as! FilterViewController <-- This is where it hangs
confirmViewCamera.imageToFilter = croppedImage
self.navigationController?.pushViewController(confirmViewCamera, animated: true)
}
}
I am a beginner programmer and am creating a game using iOS sprite-kit. I have a simple animated GIF (30 frames) saved as a .gif file. Is there a simple way (few lines of code maybe similar to adding a regular .png through UIImage) of displaying this GIF in my game? I have done some research on displaying an animated GIF in Xcode and most involve importing extensive classes, most of which is stuff I don't think I need (I barely know enough to sift through it).
The way I think of it gifs are just like animating a sprite. So what I would do is add the gif as textures in a SKSpriteNode in a for loop and then tell it to run on the device with SKAction.repeatActionForever().
To be honest I'm fairly new to this as well. I'm just trying to give my best answer. This is written in Swift, but I don't think it'll be to hard to translate to Objective-C.
var gifTextures: [SKTexture] = [];
for i in 1...30 {
gifTextures.append(SKTexture(imageNamed: "gif\(i)"));
}
gifNode.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(gifTextures, timePerFrame: 0.125)));
Michael Choi's answer will get you half way there. The rest is getting the individual frames out of the gif file. Here's how I do it (in Swift):
func load(imagePath: String) -> ([SKTexture], TimeInterval?) {
guard let imageSource = CGImageSourceCreateWithURL(URL(fileURLWithPath: imagePath) as CFURL, nil) else {
return ([], nil)
}
let count = CGImageSourceGetCount(imageSource)
var images: [CGImage] = []
for i in 0..<count {
guard let img = CGImageSourceCreateImageAtIndex(imageSource, i, nil) else { continue }
images.append(img)
}
let frameTime = count > 1 ? imageSource.delayFor(imageAt: 0) : nil
return (images.map { SKTexture(cgImage: $0) }, frameTime)
}
extension CGImageSource { // this was originally from another SO post for which I've lost the link. Apologies.
func delayFor(imageAt index: Int) -> TimeInterval {
var delay = 0.1
// Get dictionaries
let cfProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil)
let gifPropertiesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 0)
if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque(), gifPropertiesPointer) == false {
return delay
}
let gifProperties: CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self)
// Get delay time
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self)
}
delay = delayObject as? TimeInterval ?? 0.1
if delay < 0.1 {
delay = 0.1 // Make sure they're not too fast
}
return delay
}
}
Note that I assume that each frame of the gif is the same length, which is not always the case.
You could also pretty easily construct an SKTextureAtlas with these images.