My app using GCDWebServer I get a photo from my iPhone album by using AssetsLibrary and I store them in NSDocumentDirectory. I just want to get access Url to this photo to show them in a web page.
There is my code :
[_webServer addHandlerForMethod:#"POST"
path:#"/"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
ALAsset *asset = [self.arrayPictures objectAtIndex:0];
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
UIImage *thumbnail = [UIImage imageWithCGImage:iref];
NSString* path ;
if (thumbnail != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
path = [[NSString alloc] initWithString:[documentsDirectory stringByAppendingPathComponent:#"test.png"]];
NSData* data = UIImagePNGRepresentation(thumbnail);
[data writeToFile:path atomically:YES];
}
NSURL *url = [NSURL fileURLWithPath:path];
NSString* html = [NSString stringWithFormat:#"<html><body><img src=\"%#\" width=\"400\" height=\"500\"/></body></html>", url];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
For DocumentDirectory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *imagePath = [[NSString alloc] initWithString:[documentsDirectory stringByAppendingPathComponent:#"test.jpg"]];
NSURL *baseURL2 = [NSURL fileURLWithPath:documentsDirectory];
NSString* html2 = [NSString stringWithFormat:#"<html><body><img src=\"%#\" width=\"400\" height=\"500\"/></body></html>", imagePath];
[_webView loadHTMLString:html2 baseURL:baseURL2];
For Bundled resource
NSString *path = [[NSBundle mainBundle] pathForResource:#"ahsan" ofType:#"jpg"]; // path to your image source.
NSURL *baseURL = [NSURL fileURLWithPath:path];
NSString* html = [NSString stringWithFormat:#"<html><body><img src=\"%#\" width=\"400\" height=\"500\"/></body></html>", path];
Try this code.
GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addGETHandlerForBasePath:#"/" directoryPath:[[NSBundle mainBundle] bundlePath] indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[webServer runWithPort:8080];
Looks like nobody is answering the actual question here: this is not about uploading a photo from iOS to a web server or viewing it in a local UIWebView, but about serving that photo using GCDWebServer so that users can view it by connecting to the device using a web browser e.g. by going to http://my-device.local/photo-1234.jpg.
The sample code you provided has 2 major issues:
You are creating a handler for POST requests while you need to handle GET requests (POST is when submitting forms from web browsers, while GET is for regular web browsing and getting web pages).
Instead of returning the image data, you are returning an HTML webpage, which contains a URL to the image file locally (e.g. file:///some/path/photo-1234.jpg) instead of HTTP URL handled by GCDWebServer (e.g. http://my-device.local/photo-1234.jpg). File URLs do not make sense outside of the device itself and will not work.
If you have all the photos as files in your documents folder, you can simply add a GET handler to GCDWebServer to serve the contents of the entire documents folder:
[webServer addGETHandlerForBasePath:#"/"
directoryPath:<PATH_TO_DOCUMENTS_DIRECTORY>
indexFilename:nil
cacheAge:3600
allowRangeRequests:YES];
This is not a very good solution though, as it requires copying photos from the assets library into the documents folder, which is wasteful. Instead, consider dynamically serving the photo assets as I explain in my answer to a related question here: https://stackoverflow.com/a/31778726/463432.
You can create one GET request which will return all custom URLs of the photo assets.
After that create one handler of that custom URLs which will return requested URL image Data.
In a custom URL handler use below code
//Get PHAsset image name with extionsion.
let filename = NSURL(string: (asset as! PHAsset).originalFilename!)
// Retrive image data from PHAsset object
PHImageManager.default().requestImageData(for: asset as! PHAsset, options: nil, resultHandler: { (imageData, str, nil, info) in
//Check imageData is nil or not.
if (imageData) {
//Return image Data with contentType
let imageDatas = UIImagePNGRepresentation(imageData)
completionBlock!(GCDWebServerDataResponse(data: imageDatas, contentType: "image/\((filename?.pathExtension)!)"))
} else {
// Image retrive error message return.
let jsonResponse = ["type":"failed","Message":"Image not avilable."] as [String : Any]
completionBlock!(GCDWebServerDataResponse(jsonObject: jsonResponse))
}
})
// MARK:- Get File name from PHAsset.
extension PHAsset {
var originalFilename: String? {
var fname:String?
if #available(iOS 9.0, *) {
let resources = PHAssetResource.assetResources(for: self)
if let resource = resources.first {
fname = resource.originalFilename
}
}
if fname == nil {
// this is an undocumented workaround that works as of iOS 9.1
fname = self.value(forKey: "filename") as? String
}
return fname
}
}
Related
I have a UIImageView that allows a user to place and hold an image until it can be saved. The problem is, I can't figure out how to actually save and retrieve the image I've placed in the view.
I have retrieved and placed the image in the UIImageView like this:
//Get Image
- (void) getPicture:(id)sender {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
picker.sourceType = (sender == myPic) ? UIImagePickerControllerSourceTypeCamera : UIImagePickerControllerSourceTypeSavedPhotosAlbum;
[self presentModalViewController:picker animated:YES];
[picker release];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage (UIImage *)image editingInfo:(NSDictionary *)editingInfo {
myPic.image = image;
[picker dismissModalViewControllerAnimated:YES];
}
It displays the selected image in my UIImageView just fine, but I have no idea how to save it. I'm saving all the other pieces of the view (mostly UITextfield) in Core Data. I've searched and searched, and tried many bits of code that people have suggested, but either I'm not entering the code correctly, or those suggestions don't work with the way I have my code set up. It's likely the former. I'd like to save the image in the UIImageView using the same action (a save button) I'm using to save the text in the UITextFields. Here's how I'm saving my UITextField info:
// Handle Save Button
- (void)save {
// Get Info From UI
[self.referringObject setValue:self.myInfo.text forKey:#"myInfo"];
Like I said earlier, I have tried several methods to get this to work, but can't get a grasp on it. For the first time in my life I've wanted to cause physical harm to an inanimate object, but I've managed to restrain myself.
I'd like to be able to save the image the user places into the UIImageView in the application's documents folder, and then be able to retrieve it and place it in another UIImageView for display when the user pushes that view onto the stack. Any help is greatly appreciated!
It's all good, man. Don't harm yourself or others.
You probably don't want to store these images in Core Data, since that can impact performance if the data set grows too large. Better to write the images to files.
NSData *pngData = UIImagePNGRepresentation(image);
This pulls out PNG data of the image you've captured. From here, you can write it to a file:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"image.png"]; //Add the file name
[pngData writeToFile:filePath atomically:YES]; //Write the file
Reading it later works the same way. Build the path like we just did above, then:
NSData *pngData = [NSData dataWithContentsOfFile:filePath];
UIImage *image = [UIImage imageWithData:pngData];
What you'll probably want to do is make a method that creates path strings for you, since you don't want that code littered everywhere. It might look like this:
- (NSString *)documentsPathForFileName:(NSString *)name
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
return [documentsPath stringByAppendingPathComponent:name];
}
Hope that's helpful.
Swift 3.0 version
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let img = UIImage(named: "1.jpg")!// Or use whatever way to get the UIImage object
let imgPath = URL(fileURLWithPath: documentDirectoryPath.appendingPathComponent("1.jpg"))// Change extension if you want to save as PNG
do{
try UIImageJPEGRepresentation(img, 1.0)?.write(to: imgPath, options: .atomic)//Use UIImagePNGRepresentation if you want to save as PNG
}catch let error{
print(error.localizedDescription)
}
Swift 4 with extension
extension UIImage{
func saveImage(inDir:FileManager.SearchPathDirectory,name:String){
guard let documentDirectoryPath = FileManager.default.urls(for: inDir, in: .userDomainMask).first else {
return
}
let img = UIImage(named: "\(name).jpg")!
// Change extension if you want to save as PNG.
let imgPath = URL(fileURLWithPath: documentDirectoryPath.appendingPathComponent("\(name).jpg").absoluteString)
do {
try UIImageJPEGRepresentation(img, 0.5)?.write(to: imgPath, options: .atomic)
} catch {
print(error.localizedDescription)
}
}
}
Usage example
image.saveImage(inDir: .documentDirectory, name: "pic")
This is Fangming Ning's answer for Swift 4.2, updated with a recommended and more Swifty method for retrieving the document directory path and with better documentation. Credits to Fangming Ning for the new method as well.
guard let documentDirectoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
//Using force unwrapping here because we're sure "1.jpg" exists. Remember, this is just an example.
let img = UIImage(named: "1.jpg")!
// Change extension if you want to save as PNG.
let imgPath = documentDirectoryPath.appendingPathComponent("1.jpg")
do {
//Use .pngData() if you want to save as PNG.
//.atomic is just an example here, check out other writing options as well. (see the link under this example)
//(atomic writes data to a temporary file first and sending that file to its final destination)
try img.jpegData(compressionQuality: 1)?.write(to: imgPath, options: .atomic)
} catch {
print(error.localizedDescription)
}
Check out all the possible Data writing options here.
#pragma mark - Save Image To Local Directory
- (void)saveImageToDocumentDirectoryWithImage:(UIImage *)capturedImage {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"/images"];
//Create a folder inside Document Directory
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder
NSString *imageName = [NSString stringWithFormat:#"%#/img_%#.png", dataPath, [self getRandomNumber]] ;
// save the file
if ([[NSFileManager defaultManager] fileExistsAtPath:imageName]) {
// delete if exist
[[NSFileManager defaultManager] removeItemAtPath:imageName error:nil];
}
NSData *imageDate = [NSData dataWithData:UIImagePNGRepresentation(capturedImage)];
[imageDate writeToFile: imageName atomically: YES];
}
#pragma mark - Generate Random Number
- (NSString *)getRandomNumber {
NSTimeInterval time = ([[NSDate date] timeIntervalSince1970]); // returned as a double
long digits = (long)time; // this is the first 10 digits
int decimalDigits = (int)(fmod(time, 1) * 1000); // this will get the 3 missing digits
//long timestamp = (digits * 1000) + decimalDigits;
NSString *timestampString = [NSString stringWithFormat:#"%ld%d",digits ,decimalDigits];
return timestampString;
}
In Swift:
let paths: [NSString?] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .LocalDomainMask, true)
if let path = paths[0]?.stringByAppendingPathComponent(imageName) {
do {
try UIImagePNGRepresentation(image)?.writeToFile(path, options: .DataWritingAtomic)
} catch {
return
}
}
I have an iOS application which is download from server an XML file encoded in Windows 1252.
I am using the following code to save it to my document folder :
NSString *path = #"http://server/file.xml";
NSURL *URL = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSWindowsCP1252StringEncoding]];
NSData *xmlData = [NSData dataWithContentsOfURL:URL];
if(xmlData == nil) {
// Error - handle appropriately
NSLog(#"ERROR");
}
NSString *applicationDocumentsDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *storePath=[applicationDocumentsDir stringByAppendingPathComponent:#"annonces.xml"];
[xmlData writeToFile:storePath atomically:TRUE];
NSLog(#"write xml");
It doesn't work, I've got an nil response when I try to read it with the parser. How could I do to get it properly. I cannot change the encoding of te xml which is on the server. If I change the XML encoding manually, I've got a correct response.
This is how I pass the XML string to parse it with XML Dictionnary class :
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *finalPath = [path stringByAppendingPathComponent:#"annonces.xml"];
NSString *string = [[NSString alloc] initWithContentsOfFile:finalPath encoding:NSWindowsCP1252StringEncoding error:NULL];
NSLog(#"string: %#", string);
NSDictionary *items = [NSDictionary dictionaryWithXMLString:string];
NSLog(#"dictionary: %#", items);
Your URL variable contains the url address of the location of the XML file you want to download.
However you are applying NSWindowsCP1252StringEncoding to the url, not to the content of the file.
xmlData is nil because dataWithContentsOfURL: cannot find the file at the location you have specified within URL.
You need to download the file first, then once its downloaded then you can be concerned about what encoding its in and how to parse it.
The way you are using NSWindowsCP1252StringEncoding has got nothing to do with the content of the file.
I want to save an image with some metadata changes in a temp folder, without re-encoding the actual image data.
The only method that I found able to do this is ALAssetsLibrary/writeImageDataToSavedPhotosAlbum:metadata:completionBlock:, however, this one saves the image to the Photo Library. Instead, I want to save the image to a temp folder (for instance to share it by email, without populating the Photo Library).
I've tried using CGImageDestinationRef (CGImageDestinationAddImageFromSource), but it can only be created using a decoded image, which means it's re-encoding it when saved (tested, pixel bytes look different).
Are there any other methods/classes available for iOS that can save image data along with metadata, besides using CGImageDestinationRef? Suggestions for workarounds would also be welcomed.
This is a frustrating problem with the iOS SDK. First, I recommend filing an enhancement request.
Now, here is a potential workaround: IF the ALAsset was created by you (i.e., its editable property is YES) then you can basically read the data, write with metadata, read again, save to disk, and then write with the original metadata.
This approach will avoid creating a duplicate image.
Please read the // comments as I skipped some stuff for brevity (like building a metadata dictionary):
ALAsset* asset; //get your asset, don't use this empty one
if (asset.editable) {
// get the source data
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc(rep.size);
// add error checking here
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
NSData *sourceData = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
// make your metadata whatever you want
// you should use actual metadata, not a blank dictionary
NSDictionary *metadataDictionary = [NSDictionary dictionary];
// these are __weak to avoid creating an ARC retain cycle
NSData __weak *originalData = sourceData;
NSDictionary __weak *originalMetadata = [rep metadata];
[asset setImageData:sourceData
metadata:metadataDictionary
completionBlock:^(NSURL *assetURL, NSError *error) {
//now get your data and write it to file
if (!error) {
//get your data...
NSString *assetPath = [assetURL path];
NSData *targetData = [[NSFileManager defaultManager] contentsAtPath:assetPath];
//...write to file...
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [searchPaths lastObject];
NSURL *fileURL = [NSURL fileURLWithPath:documentPath];
[targetData writeToURL:fileURL atomically:YES];
//...and put it back the way it was
[asset setImageData:originalData metadata:originalMetadata completionBlock:nil];
} else {
// handle error on setting data
NSLog(#"ERROR: %#", [error localizedDescription]);
}
}];
} else {
// you'll need to make a new ALAsset which you have permission to edit and then try again
}
As you can see, if the ALAsset isn't owned by you, you will need to create one, which will add a photo to the user's library, which is exactly what you wanted to avoid. However, as you may have guessed, you cannot delete an ALAsset from the user's photo library, even if your app created it. (Feel free to file another enhancement request for that.)
So, if the photo/image was created in your app, this will work for you.
But if not, it will create an additional copy the user must delete.
The only alternative is to parse the NSData yourself, which would be a pain. There is no open-source library that I am aware of to fill this gap in the iOS SDK.
Alongside iphone-exif, and Aaron's answer, you might also want to look at the libexif c library.
see also HCO23's answer to
How to write or modify EXIF data for an existing image on the filesystem, without loading the image?
(#HCO23 has used libexif in iOS projects).
It's also worth noting that many of the 'professional' metadata editing apps in the app store seem to skirt around the issue by creating sidecar/xmp files.
Try This :
- (void)saveImage: (UIImage*)image
{
if (image != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
#"test.png" ];
NSData* data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
}
}
- (UIImage*)loadImage
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
#"test.png" ];
UIImage* image = [UIImage imageWithContentsOfFile:path];
[self sendAction:path];
return image;
}
I'm trying to load a UIImage from the documents directory and set it to a UIImageView as per below:
NSString *pngfile = [[MyUtil getLocalDirectory] stringByAppendingPathComponent:#"school.png"];
NSLog(#"%#", pngfile);
if ([[NSFileManager defaultManager] fileExistsAtPath:pngfile]) {
NSData *imageData = [NSData dataWithContentsOfFile:pngfile];
UIImage *img = [UIImage imageWithData:imageData];
[schoolImage setImage:img];
}
However, whenever I try the above, the image never loads. The image is in Documents/MyAppCustomDirectory/school.png. Is the above correct to load from that directory?
I also tried a few others: UIImage imageWithContentsOfFile, among other ways based on SO responses.
To get the documents directory you should use:
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *pngfile = [documentsDir stringByAppendingPathComponent:#"school.png"];
I'm not quite sure if you also need to append the 'MyAppCustomDirectory', but I don't think so.
Swift 4 Solution:
guard let documentsDirectory = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor:nil,
create:false)
else {
// May never happen
print ("No Document directory Error")
return nil
}
// Construct your Path from device Documents Directory
var imagesDirectory = documentsDirectory
// Only if your images are un a subdirectory named 'images'
imagesDirectory.appendPathComponent("images", isDirectory: true)
// Add your file name to path
imagesDirectory.appendPathComponent("school.png")
// Create your UIImage?
let result = UIImage(contentsOfFile: imagesDirectory.path)
I have this code this code to save an image to the Documents folder.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDirectory stringByAppendingPathComponent:#"savedImage.png"];
UIImage *image = imageView.image; // imageView is my image from camera
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:NO];
I am looking for a way to be able to save multiple images as this one keeps over writing the savedImage.png name.
I do not mind looking for it on google or whatever, but I need to know what it is called, since looking with the wrong keywords really delays the world:-)
Cheers
You need to change the file name that you are appending to the image documentsDirectory path on line three. Each time you'll need to use a different name that isn't already used. NSFileManager has methods to see if a file exists so you can construct a file name and then test if it exists in that location and if so, increment your duplicate count and try the next one.
if num is an integer you define somewhere and keep around so you know the last one you thought you used (and that you've initialized to 1 somewhere).
// your code to get the directory here, as above
NSFileManager *fm = [NSFileManager ...]
do {
savedImagePath = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat: #"%#-%d.png", #"savedImage", num]];
num += 1; // for next time
if ( ![fm fileExistsAtPath: savedImagePath] )
{
// save your image here using savedImagePath
exit;
}
} while ( //some kind of test for maximum tries/time or whatever )
you'll have to look up the syntax to get an NSFileManager instance and the exact file exists method signature, but that's the gist of it.
If you save file with current dateTime you don't need to worry about same name override problem
-(NSString*)getCurrentDateTimeAsNSString
{
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setDateFormat:#"yyyyMMddHHmmss"];
NSDate *now = [NSDate date];
NSString *retStr = [format stringFromDate:now];
[format release];
return retStr;
}
you can create a new file each time to do that.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"YOURiMAGEfILE.IMAGEeXTENSION"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path])
{
path = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat: #"YOURiMAGEfILE.IMAGEeXTENSION] ];
}
and then you can perform your above operations on the created file.
Hope it help You Working For me!!
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage
{
let i = Int(arc4random())
let str = String(i).appending(".png")
let fileManager = FileManager.default
let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent(str)
print(paths)
let imageData = UIImagePNGRepresentation(image)
fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
}
else{
print("error")
}