I'm having issues with the UIImage crashing my app whenever I run it. I think there is a possibility the images are somehow currupt perhaps. I ran debug and everything seems to run fine up until this line below:
let image = UIImage(named: photo!)?.decompressedImage
The image key is showing up and everything is running as it should. It is the next line that seems to make the app crash:
self.init(caption: caption!, comment: comment!, image: image!)
I know I am force unwrapping the image, however I know that the image exists. Do you also agree that maybe the image is corrupt? Is there a way to double check this or perhaps fix it? The images are JPG form.
Code:
import UIKit
class Photo {
class func allPhotos() -> [Photo] {
var photos = [Photo]()
if let URL = NSBundle.mainBundle().URLForResource("Photos", withExtension: "plist") {
if let photosFromPlist = NSArray(contentsOfURL: URL) {
for dictionary in photosFromPlist {
let photo = Photo(dictionary: dictionary as! NSDictionary)
photos.append(photo)
}
}
}
return photos
}
var caption: String
var comment: String
var image: UIImage
init(caption: String, comment: String, image: UIImage) {
self.caption = caption
self.comment = comment
self.image = image
}
convenience init(dictionary: NSDictionary) {
let caption = dictionary["Caption"] as? String
let comment = dictionary["Comment"] as? String
let photo = dictionary["Photo"] as? String
let image = UIImage(named: photo!)?.decompressedImage
self.init(caption: caption!, comment: comment!, image: image!)
}
func heightForComment(font: UIFont, width: CGFloat) -> CGFloat {
let rect = NSString(string: comment).boundingRectWithSize(CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(rect.height)
}
}
Related
What do I simply do?
let pasteboard = UIPasteboard.general
let base64EncodedImageString = "here_base_64_string_image"
let data = Data(base64Encoded: base64EncodedImageString)
let url = data?.write(withName: "image.jpeg")
pasteboard.image = UIImage(url: url) //and now when I try to paste somewhere that image for example in imessage, it is rotated... why?
What may be important:
It happens only for images created by camera.
However, if use exactly the same process (!) to create activityItems for UIActivityViewController and try to use iMessage app, then it works... why? What makes the difference?
I use above two simple extensions for UIImage and Data:
extension Data {
func write(withName name: String) -> URL {
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(name)
do {
try write(to: url, options: NSData.WritingOptions.atomic)
return url
} catch {
return url
}
}
}
extension UIImage {
convenience init?(url: URL?) {
guard let url = url else {
return nil
}
do {
self.init(data: try Data(contentsOf: url))
} catch {
return nil
}
}
}
Before server returns base64EncodedString I upload an image from camera like this:
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
let image = info[.originalImage] as? UIImage
let encodedBase64 = image?.jpegData(compressionQuality: 0.9)?.base64EncodedString() ?? ""
//upload encodedBase64 to the server... that is all
}
I am not sure but I think UIPasteBoard converts your image to PNG and discards its orientation. You can explicitly tell the kind of data you are adding to the pasteboard but I am not sure if this would work for your scenery.
extension Data {
var image: UIImage? { UIImage(data: self) }
}
setting your pasteboard data
UIPasteboard.general.setData(jpegData, forPasteboardType: "public.jpeg")
loading the data from pasteboard
if let pbImage = UIPasteboard.general.data(forPasteboardType: "public.jpeg")?.image {
}
Or Redrawing your image before setting your pasteboard image property
extension UIImage {
func flattened(isOpaque: Bool = true) -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
UIPasteboard.general.image = image.flattened()
I want to store a couple of images locally in my app on the user's device.
What I was using until now (it's still in development):
static func filePath(forKey key: String) -> URL? {
let fileManager = FileManager.default
guard let documentURL = fileManager.urls(for: .documentDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask).first else { return nil }
return documentURL.appendingPathComponent(key + ".png")
}
static func savePhoto(imageKey: String) {
if let filePath = Helpers.filePath(forKey: imageKey) {
do {
try Constants.PHOTO_DATA.write(to: filePath, options: .atomic)
} catch {
print("error")
}
} else {
print(" >>> Error during saving photo. Filepath couldn't be created.")
}
}
static func getPhoto(imageKey: String) -> (image: UIImage, placeholder: Bool) {
if let filePath = Helpers.filePath(forKey: imageKey),
let fileData = FileManager.default.contents(atPath: filePath.path),
let image = UIImage(data: fileData) {
// Retrieve image from device
return (image, false)
}
return (UIImage(named: "placeholder")!, true)
}
Now, during testing I realized that it is not working (but I'm almost 100% sure it was working until now, strange..). It is changing the App's container directory upon every launch.
E.g.
Path:
/var/mobile/Containers/Data/Application/1F3E812E-B128-481C-9724-5E39049D6C81/Documents/D5F14199-CFBF-402A-9894-3487976C4C74.png
Restarting the app, then the path it gives (and where it does not find the image):
/var/mobile/Containers/Data/Application/0A9FCE45-1ED4-46EB-A91B-3ECD56E6A31B/Documents/D5F14199-CFBF-402A-9894-3487976C4C74.png
I read a bit and as far as I see it is 'expected' that it is not working, as the app's directory can change any time the user restarts the app. I should use bookmarkData of the URL class.
My problem is that I couldn't get it working with bookmarkData as I don't really see how should I use it, and couldn't understand its behavior based on some example codes/articles I found. Until now I was simply using URLs to store/retrieve the photo but now I should go with this bookmarkData which is a Data type, which confuses me.
I'm not sure what you want your code means, since both Helper and Constants.PHOTO_DATA are unknown. The code that will definitely will save a UIImage in the documents directory is here:
class ImageSaver {
private let imageStore = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first
//Make this static variable to allow access from all objects without instantiating this class
static var shared : AuxiliaryObjects {
return AuxiliaryObjects()
}
/**
Declaration: save(image : UIImage, with fileName: String, and imageName: String?)
Description: This method saves the received image to the persistent store in the documents directory of the user.
- Parameter image: The UIImage object that must be stored in the documents directory.
- Parameter fileName: A string with the name under which the image must be stored.
- Parameter imageName: The name of the image if needed.
*/
func save(image: UIImage, with fileName: String, and imageName: String?) {
let fileStore = imageStore?.appendingPathComponent(fileName)
let imageData = UIImagePNGRepresentation(image)
do {
try imageData?.write(to: fileStore!)
} catch {
print("Couldn't write the image to disk.")
}
}
/**
Declaration: getImage(with fileName: String, with rectangle: CGRect) -> UIImage?
Description: This method retrieves the image with the specified file name and a given size.
- Parameter fileName: a string with the file name to retrieve.
- Parameter rectangle: the size of the image to return.
- Returns: UIImage?, the image retrieved from the documents directory.
*/
func getImage(with fileName: String, with rectangle: CGRect) -> UIImage? {
var returnImage : UIImage?
var imageRectangle = rectangle
do {
imageStoreArray = try FileManager.default.contentsOfDirectory(at: imageStore!, includingPropertiesForKeys: resourceKeys, options: .skipsHiddenFiles) as [NSURL]
} catch {
return returnImage
}
for url in imageStoreArray {
let urlString = url.lastPathComponent
if urlString == fileName {
let retrievedImage = UIImage(contentsOfFile: url.path!)
//When there is no size set, the original size image is returned
if (rectangle.size.height > 0) || (rectangle.size.width > 0) {
let imageWidth = retrievedImage?.size.width
let imageHeight = retrievedImage?.size.height
if imageWidth! > imageHeight!
{
//The picture is wider than it is high
imageRectangle.size.height *= (imageHeight! / imageWidth!)
} else {
imageRectangle.size.width *= (imageWidth! / imageHeight!)
}
UIGraphicsBeginImageContextWithOptions(imageRectangle.size, false, UIScreen.main.scale)
retrievedImage?.draw(in: imageRectangle)
returnImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
} else {
returnImage = retrievedImage
}
}
}
return returnImage
}
}
Let me know if this works for you.
Kind regards,
MacUserT
I am adding text to an array of images using swift. Below is my code for the loop:
var holderClass = HolderClass()
var newImages = [UIImage]()
var counter = 0
for (index, oldImage) in holderClass.oldImages.enumerated(){
let newImage = drawTextAtLoaction(text: "testing", image: oldImage)
newImages[index] = newImage
counter+=1
if(counter == self.storyModule.getImageCount()){
completionHandler(newImages)
}
}
Here is the adding text function:
func drawTextAtLoaction(text: String, image: UIImage) -> UIImage{
let textColor = UIColor.white
let textFont = UIFont(name: "Helvetica Bold", size: 12)!
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
let textFontAttributes = [
NSAttributedString.Key.font: textFont,
NSAttributedString.Key.foregroundColor: textColor,
] as [NSAttributedString.Key : Any]
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: image.size)
text.draw(in: rect, withAttributes: textFontAttributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if newImage != nil{
return newImage!
}else{
return image
}
}
Intuitively this loop should take constant storage, however, as shown by the following image it takes linear storage and causes memory errors with larger numbers of images.
How can I use constant memory for such an operation?
edit: I think that I may have oversimplified this a bit. The oldImages array is really an array of images inside an object. So the initialization of old images looks as so.
class HolderClass{
private var oldImages: [UIImage]!
init(){
oldImages = [UIImage(), UIImage()]
}
}
edit 2:
This is how the image data is loaded. The following code is in viewDidLoad.
var holderClass = HolderClass()
DispatchQueue.global(qos: .background).async {
var dataIntermediate = [StoryData]()
let dataRequest:NSFetchRequest<StoryData> = StoryData.fetchRequest()
do {
dataIntermediate = try self.managedObjectContext.fetch(dataRequest)
for storyData in dataIntermediate{
var retrievedImageArray = [UIImage]()
if let loadedImage = DocumentSaveManager.loadImageFromPath(imageId: Int(storyData.id)){
retrievedImageArray.append(loadedImage)
}
holderData.oldImages = retrievedImageArray
}
}catch{
print("Failed to load data \(error.localizedDescription)")
}
}
This is the DocumentSaveManager class.
static func documentDirectoryURL() -> URL {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
return documentsURL
}
static func saveInDocumentsDirectory(foldername:String, filename: String) -> URL {
let fileURL = documentDirectoryURL().appendingPathComponent(foldername).appendingPathComponent(filename)
return fileURL
}
static func loadImageFromPath(moduleId: Int, imageId: Int) -> UIImage? {
let path = saveInDocumentsDirectory(foldername: String(describing: moduleId), filename: String(describing: imageId)).path
let image = UIImage(contentsOfFile: path)
if image == nil {
return nil // Remember to alert user
}
return image
}
I need to convert an image I downloaded from Firebase using SDWebImage to a UIImage. How can I do this? Here is my code so far:
Database.database().reference().child("movies").child(movieNumString).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let UrlString: String? = ((dictionary["posterPhotoURL"] as? String))
let Url = URL(string: (UrlString)!)
self.moviePosterImageView.sd_setImage(with: Url, placeholderImage: #imageLiteral(resourceName: "PImage"))
}
You can try this callBack method
self.moviePosterImageView.sd_setImage(with:Url,
placeholderImage: #imageLiteral(resourceName: "PImage"),
options: [],
completed: { (image, error,cacheType, url) in
// here image is the UIImage
self.moviePosterImageView.image = image?.roundedWithBorder(width: 2, color: .red)
})
My iOS app (Swift 3) needs to important images from other apps using an Action Extension. I'm using the standard Action Extension template code which works just fine for apps like iOS Mail and Photos where the image shared is a URL to a local file. But for certain apps where the image being shared is the actual image data itself, my action extension code isn't getting the image.
for item: Any in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: Any in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { //we'll take any image type: gif, png, jpg, etc
// This is an image. We'll load it, then place it in our image view.
weak var weakImageView = self.imageView
itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: { (imageURL,
error) in
OperationQueue.main.addOperation {
if let strongImageView = weakImageView {
if let imageURL = imageURL as? NSURL {
strongImageView.image = UIImage(data: NSData(contentsOf: imageURL as URL)! as Data)
let imageData = NSData(contentsOf: imageURL as URL)! as Data
self.gifImageView.image = UIImage.gif(data: imageData)
let width = strongImageView.image?.size.width
let height = strongImageView.image?.size.height
.... my custom logic
}
}
For reference, I reached out to the developer for one of the apps where things aren't working and he shared this code on how he is sharing the image to the Action Extension.
//Here is the relevant code. At this point the scaledImage variable holds a UIImage.
var activityItems = Array<Any?>()
if let pngData = UIImagePNGRepresentation(scaledImage) {
activityItems.append(pngData)
} else {
activityItems.append(scaledImage)
}
//Then a little later it presents the share sheet:
let activityVC = UIActivityViewController(activityItems: activityItems,applicationActivities: [])
self.present(activityVC, animated: true, completion: nil)
Figured it out thanks to this post which explains the challenge quite well https://pspdfkit.com/blog/2017/action-extension/ . In summary, we don't know if the sharing app is giving us a URL to an existing image or just raw image data so we need to modify the out of the box action extension template code to handle both cases.
for item: Any in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: Any in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { //we'll take any image type: gif, png, jpg, etc
// This is an image. We'll load it, then place it in our image view.
weak var weakImageView = self.imageView
itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: { (imageURL,
error) in
OperationQueue.main.addOperation {
if let strongImageView = weakImageView {
if let imageURL = imageURL as? NSURL {
strongImageView.image = UIImage(data: NSData(contentsOf: imageURL as URL)! as Data)
let imageData = NSData(contentsOf: imageURL as URL)! as Data
self.gifImageView.image = UIImage.gif(data: imageData)
let width = strongImageView.image?.size.width
let height = strongImageView.image?.size.height
.... my custom logic
}
else
guard let imageData = imageURL as? Data else { return } //can we cast to image data?
strongImageView_.image = UIImage(data: imageData_)
//custom logic
}