Building for iOS 7+ ,
Building on Xcode 6.1 ,
Using Amazon SDK AWSiOSSDKv2 2.0.12 ,
Testing on iPhone 5s and iPad 2
I am downloading images from my Amazon S3 bucket with the Amazon SDK for iOS.
The downloading is working fine but I want to use the ifModifiedSince property to retrieve only images that have been modified since a certain date
(see http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSS3GetObjectRequest.html#//api/name/ifModifiedSince )
However, this is not working. Even when I specify a ifModifiedSince date that is LATER THAN the modified date of the file on S3, the file is returned.
According to the Amazon documentation:
ifModifiedSince -
Return the object only if it has been modified since the specified
time, otherwise return a 304 (not modified).
So I am not sure if I am doing something wrong or Amazon has a bug in the SDK.
Here is my code:
-(void)downloadPhotoWithName:(NSString*)name completed:(retrievedImage)completed {
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [cachePaths firstObject];
NSString *filename = [NSString stringWithFormat:#"%#.jpg", name];
NSString *filePath = [cacheDirectory stringByAppendingPathComponent:filename];
AWSS3 *transferManager = [AWSS3 defaultS3];
AWSS3GetObjectRequest *profilePhoto = [[AWSS3GetObjectRequest alloc] init];
profilePhoto.bucket = S3BUCKETNAMEIMAGE;
profilePhoto.key = filename;
if ([manager fileExistsAtPath:filePath isDirectory:NULL]) {
NSDictionary *att = [manager attributesOfItemAtPath:filePath error:nil];
if (att) {
//Get the date of when the file was modified so we can request to retrieve
//the file only if it was modified SINCE that date
NSDate *modifiedDate = [att objectForKey:NSFileModificationDate];
if (modifiedDate) {
profilePhoto.ifModifiedSince = modifiedDate;
}
}
}
[[transferManager getObject:profilePhoto] continueWithBlock:^id(BFTask *task) {
//If it was working we should get a 304 not the image file
if (task.result) {
AWSS3GetObjectOutput *output = (AWSS3GetObjectOutput*)task.result;
NSData *imageData = (NSData*)output.body;
if (imageData) {
NSDictionary* attr = [NSDictionary dictionaryWithObjectsAndKeys:output.lastModified, NSFileModificationDate, NULL];
//The log confirms that the date I am passing for ifModifiedSince is LATER THAN
//the last modified date of the file on S3
//but the the image file is returned anyway.. is it a problem with Amazon ?
NSLog(#"output.lastModified: %#\nmodifiedDate: %#", output.lastModified, profilePhoto.ifModifiedSince);
if ([manager createFileAtPath:filePath contents:imageData attributes:attr]) {
completed(imageData);
}
else {
NSLog(#"Could not save image to disk for some reason");
completed(nil);
}
}
else {
completed(nil);
}
}
else if (task.error) {
NSLog(#"DownloadPhotoError: %#", task.error);
completed(nil);
}
return nil;
}];
}
We've released the AWS Mobile SDK for iOS 2.0.14. It should address the time formatting issue.
Related
I am using UIDocumentPickerViewController to allow the user to select files that will be "attached" and available within the App. The concept is allow the user to send detail via email with the selected file attachments.
As each file is attached, I copy the file from the tmp Inbox (where fileManager puts the imported file) to a directory I create within the App document directory called "fileAttachments".
I list the files in a UITableView and the user can select each entry and preview the content within a QLPreviewController view using the path stored in the file object fileOJ.filePath.
It all works swimmingly well, until a reload of the project down to my test iPad, then all the files seem to disappear. My list of the files is still fine, but there is no file at the path location.
Any help with just what is happenning would be greatly appreciated.
- (IBAction)selectFilesAction:(UIBarButtonItem *)sender {
NSArray *UTIs = [NSArray arrayWithObjects:#"public.data", nil];
[self openFilePicker:UTIs];
}
- (void)openFilePicker:(NSArray *)UTIs {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:UTIs inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
documentPicker.allowsMultipleSelection = FALSE;
documentPicker.popoverPresentationController.barButtonItem = self.selectFilesButton;
[self presentViewController:documentPicker animated:TRUE completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls {
NSLog(#"picked URLs %#", urls);
// selecting multiple documents is cool, but requires iOS 11
for (NSURL *documentURL in urls) {
//get file details
NSDictionary *attr = [documentURL resourceValuesForKeys:#[NSURLFileSizeKey,NSURLCreationDateKey] error:nil];
NSLog(#"object: %#", attr);
NSNumber *fileSize = [attr valueForKey:NSURLFileSizeKey];
NSDate *dateFileCreated = [attr valueForKey:NSURLCreationDateKey];
NSDateFormatter *storageDateFormat = [[NSDateFormatter alloc] init];
[storageDateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *createdDateString = [storageDateFormat stringFromDate:dateFileCreated];
MMfile *fileObj = [[MMfile alloc]init];
fileObj.fileName = documentURL.lastPathComponent;
fileObj.meetingID = _meetingID;
fileObj.fileSize = fileSize;
fileObj.fileCreateDate = createdDateString;
//move file to new directory
fileObj.filePath = [self movefile:documentURL.lastPathComponent sourceFilePath:documentURL.path directory:#"fileAttachments"];
//save file details
[self.meetingModel saveFile:fileObj];
//refresh array and reload table
self.fileArray = [self.meetingModel getFiles:self.meetingID];
[self.tableView reloadData];
}
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
NSLog(#"cancelled");
}
-(NSString *)movefile:(NSString *)filename sourceFilePath:(NSString *)sourcePath directory:(NSString *)directoryName{
// Move file from tmp Inbox to the destination directory
BOOL isDir;
NSError *error;
NSFileManager *fileManager= [NSFileManager defaultManager];
//get directory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString* directoryPath;
if (paths>0) {
NSString *documentsDirectory = [paths objectAtIndex:0];
directoryPath = [NSString stringWithFormat:#"%#/%#",documentsDirectory,directoryName];
}
if(![fileManager fileExistsAtPath:directoryPath isDirectory:&isDir])
if(![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:NO attributes:nil error:NULL])
NSLog(#"Error: Create folder failed %#", directoryPath);
NSString *destinationPath = [NSString stringWithFormat:#"%#/%#",directoryPath,filename];;
BOOL success = [fileManager moveItemAtPath:sourcePath toPath:destinationPath error:&error];
if (success) {
NSLog(#"moved file");
}else{
NSLog(#"error %#",error.description);
}
return destinationPath;
}
Found the issue. When the project is rebuilt and downloaded to the iPad the AppID changes, and as the documents path includes the AppID, so the documents path changes. Key is not to save the file path, only the file name and rebuild the path each instance. After having found the issue, I now see other similar posts I didn't find earlier. Also see Document directory path change when rebuild application
I am trying to add a feature to an existing iOS app so that documents "copied" within Mail (say a PDF) can be pasted into my app.
After a PDF document is viewed in Mail and "Copy" chosen from the Action menu, within my app's code, when I inspect:
[[UIPasteboard generalPasteboard] items]
I can see the document as per:
{
"com.adobe.pdf" = {length = 875113, bytes = 0x25504446 2d312e35 0d0a25b5 b5b5b50d ... 300d0a25 25454f46 };
}
)
And further retrieve it via:
po [[UIPasteboard generalPasteboard] valueForPasteboardType:#"com.adobe.pdf"]
<OS_dispatch_data: data[0x2820e5c00] = { leaf, size = 875113, buf = 0x1125e0000 }>
However, is there any way to get the document's name? In the Files.app when I paste the same document it gets pasted as the original file name.
Should I be using the pasteboard for this, or if there another API which can get access to the copied document with the file name?
Even if this question is a little older I happened to search for the same and finally found the solution. So for future reference:
Starting with iOS 11 UIPasteboard contains an array itemProviders which gives you all the information about the objects that are available. The same is used for Drag/Drop.
So with the following logic you can save the items to a certain path:
NSArray *items = [[UIPasteboard generalPasteboard] itemProviders];
for (NSInteger i = 0; i < itemProvider.count; i++) {
NSItemProvider *itemProvider = itemProvider[i];
NSString *identifier = itemProvider.registeredTypeIdentifiers[0];
[itemProvider loadDataRepresentationForTypeIdentifier:identifier completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
NSMutableDictionary *result;
if (!error) {
NSString *ext = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(identifier), kUTTagClassFilenameExtension));
if ([ext isEqualToString:#"jpeg"]) {
ext = #"jpg";
}
NSString *mime = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(identifier), kUTTagClassMIMEType));
if (!mime) {
mime = #"application/*";
}
NSString *filename = itemProvider.suggestedName;
NSString *path = [folderPath stringByAppendingPathComponent:filename];
[data writeToFile:path options:NSDataWritingAtomic error:&error];
}
}
}
I retrieve an .ics file from a certain url. I'd like to present the information of this file in a calendar and/or list view in my own app. I had a look at iCal4Objc("https://github.com/cybergarage/iCal4ObjC") and parsed the information like this:
-(NSMutableArray *)getPlanFromURL:(NSURL *)url {
NSMutableArray *planEntries = [[NSMutableArray alloc]init];
NSString *storePath = [self loadICSAndReturnPathFromURL:url];
CGICalendar *parserCalendar = [[CGICalendar alloc]initWithPath:storePath];
for(CGICalendarObject *planObject in [parserCalendar objects]) {
for(CGICalendarComponent *component in [planObject components]) {
for (CGICalendarProperty *icalProp in [component properties]) {
NSString *icalPropName = [icalProp name];
NSLog(#"%#",icalPropName);
if([icalPropName isEqualToString:SUMMARY]) {
[self.summaryArray addObject:[icalProp value]];
}
else if([icalPropName isEqualToString:LOCATION]) {
[self.locationArray addObject:[icalProp value]];
}
else if([icalPropName isEqualToString:CATEGORIES]) {
[self.categoryArray addObject:[icalProp value]];
}
else if([icalPropName isEqualToString:DTSTART]) {
[self.startArray addObject:[icalProp dateValue]];
}
else if([icalPropName isEqualToString:DTEND]) {
[self.endArray addObject:[icalProp dateValue]];
}
}
}
}
for(int i = 0;i<[self.summaryArray count];i++) {
DECalEntry *entry = [[DECalEntry alloc]init];
entry.summary = [self.summaryArray objectAtIndex:i];
entry.roomInformation = [self.locationArray objectAtIndex:i];
entry.category = [self.categoryArray objectAtIndex:i];
entry.startDate = [self.startArray objectAtIndex:i];
entry.endDate = [self.endArray objectAtIndex:i];
if ([entry.category isEqualToString:#"Prüfung"]) {
entry.isExam = true;
}
else entry.isExam = false;
[planEntries addObject:entry];
}
return planEntries;
}
-(NSString *)loadICSAndReturnPathFromURL:(NSURL *)url {
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:url];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory, #"planData.ics"];
[data writeToFile:storePath atomically:YES];
return storePath;
}
NSLog(#"Error: %#",[error localizedDescription]);
return nil;
}
Since this is not very clean and it would be a pain to also implement UITableView and a calendar view to illustrate the parsed data i asked me whether there exists a framework that one of you knows which already can handle .ics files and shows them accordingly.
If you have an idea how to solve this issue less complicated and/or with less effort i would be really grateful.
Also have a look at my comment. At the moment the rrule property is not considered at all. But this is very important since it shows me if a event is repeated, until it is repeated and so on...
Nevermind i got it working myself by parsing the rrule property like i did it with the other properties and constructed a method which creates the right amount of objects i need by following the information that this property contains. Well i just think this was the only way because apparently the is no framework for importing and parsing .ics files. When i finish my project i might give it a shot for people who might face this one day.
To my knowledge there is no framework or control provided by Apple that displays .ics files, but once you construct an EKEvent, there is:
https://developer.apple.com/library/ios/documentation/EventKitUI/Reference/EKEventEditViewControllerClassRef/Reference/Reference.html#//apple_ref/doc/uid/TP40009571
Specifically, you want to take a look at EventKitUI.framework.
I have a file structure which is being built in the following manner.
1) Each user is provided an ID in MySQL database.
2) When user uploads an image from iphone the image is placed in the following directory:
`../images/[USER_ID]/[IMAGE_NUMBER].jpg`
3) In the path noted above, [IMAGE_NUMBER] is generated based upon the submission value. So the first image uploaded will be titled 1.jpg, the second as 2.jpg etc…
So to build a sample directory for the purposes of this question the structure could look something like this:
../images-->
../10/1.jpg
../10/2.jpg
../10/3.jpg
../11/4.jpg
../11/5.jpg
../10/6.jpg
../11/7.jpg
So in this case, User #10 uploaded 3 images, then logged out. Along comes User #11 and she uploads 2 images before logging out. And finally, User #10 logs back in and uploads another image.
Okay now that we have the summary of out the directories are being generated and images are dynamically input, let me get to the ultimate question. I would like to be able to display thumbnails of all the images on a view in the iphone when a user clicks a button that we will call btnRefresh
Here is the method that is associated with btnRefresh:
-(IBAction)btnRefreshTapped
{
[self refreshStream];
}
-(void)refreshStream {
[[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"stream",#"command",
nil]
onCompletion:^(NSDictionary *json) {
[self showStream:[json objectForKey:#"result"]];
}];
}
As you can see this is referencing the API sharedInstance here is that method:
+(API*)sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kAPIHost]];
});
return sharedInstance;
}
and finally we come full circle to the URLWithString value:
-(NSURL*)urlForImageWithId:(NSNumber*)IdPhoto isThumb:(BOOL)isThumb {
int IdValue = [[user objectForKey:#"id"] intValue];
NSString* urlString = [NSString stringWithFormat:#"%#/%#upload/%d/%#%#.jpg",
kAPIHost, kAPIPath, IdValue, IdPhoto, (isThumb)?#"-thumb":#""
];
return [NSURL URLWithString:urlString];
}
So, herein lies my problem. As this is currently being defined I can only see the images of the user that is logged into the app when they click btnRefresh. The other image locations show up as greyed out images due to the broken URLs. So, how can I redefine IdValue to cycle through available folders and pull the associated images for display?
I know this is a complex problem so thank you to putting some brainpower into a solution.
I don't know is it what you asking but you can get all files from the directory via this code:
NSArray *contents = [fileManager contentsOfDirectoryAtURL:YourURL
includingPropertiesForKeys:#[] // <-Add key/s if needed
options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"pathExtension == 'jpg'"];
for (NSURL *fileURL in [contents filteredArrayUsingPredicate:predicate])
{
// Enumerate each .jpg file in directory
}
You can specify images directory and you should be able to find the images and get url to that.
The other way is recursively enumerate files in directory, it's more powerful and it can be amended to your requirement:
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:YOURURL
includingPropertiesForKeys:#[NSURLNameKey, NSURLIsDirectoryKey] // <-Add more key if needed
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:^BOOL(NSURL *url, NSError *error)
{
NSLog(#"Error %#", error);
}];
NSMutableArray *mutableFileURLs = [NSMutableArray array];
for (NSURL *fileURL in enumerator) {
NSString *filename;
[fileURL getResourceValue:&filename forKey:NSURLNameKey error:nil];
NSNumber *isDirectory;
[fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
if (![isDirectory boolValue]) {
[mutableFileURLs addObject:fileURL];
}
}
Hope one of those will be usefull for you.
Modifying what Greg just answered so that you don't have to iterate the array.
NSArray *paths = [fileManager contentsOfDirectoryAtURL:YourURL
includingPropertiesForKeys:#[]
options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
[NSString stringWithFormat:
#"self ENDSWITH '/filename.jpg'"]];
NSArray *filteredArray = [paths filteredArrayUsingPredicate:predicate];
if ([newarray lastObject]) {
//you have your path here
}
filename.jpg is the IdPhoto.jpg in your code. This code with work because of the type of data you have. I have included "/" in filename.jpg so that there are no issues in detecting 110.jpg vs 10.jpg.
Trying to upload files bigger than 5 mb to Amazon S3
http://aws.amazon.com/articles/0006282245644577, here it is stated that
// The S3UploadInputStream was deprecated after the release of iOS6
Files under 5 mb I can easily upload over wifi with:
NSData *dataForAamzon = [[NSFileManager defaultManager] contentsAtPath:pathForFiile];
#try {
NSString *uploadName= #"somestring";
S3PutObjectRequest *por = [[S3PutObjectRequest alloc] initWithKey:uploadName
inBucket:[Constants userEventBucket]];
por.contentType = nil;
por.data = dataForAamzon;
// Put the image data into the specified s3 bucket and object.
S3PutObjectResponse *putObjectResponse = [self.s3 putObject:por];
[self performSelector:#selector(uploadAllFilesToAmazon:error:) withObject:nil withObject:putObjectResponse.error];
}
#catch ( AmazonServiceException *exception ) {
NSLog( #"Upload Failed, Reason: %#", exception );
}
Since S3UploadInputStream *stream was deprecated , how can I check if file is bigger than 5mb and use multipart upload in IOS 6 or later versions.
Thanks,
I hope you need to check the size of a local file exceeds 5MB right ?
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:_client.filename error:nil];
if(fileAttributes)
{
NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
long totalFileSize = [fileSizeNumber longLongValue];
if(totalFileSize > 5*1024*1024)
// Go for multipart upload
else
// Go for Direct Put request
}
Or from the NSData you can take the Length and perform the same calculation.
New release of AWS SDK solved my problem.
New AWS SDK is handling files size automatically you need to create trasnfer manager variable and delegate it to s3 object
something like
-(void) uploadAgendatoAmazon
{
//pass string in memory to nsdictionary
NSData * data = [_uploadPlistString dataUsingEncoding:NSUTF8StringEncoding];
NSString *uploadName= [NSString stringWithFormat:#"%#/%#/agenda.plist",[[NSUserDefaults standardUserDefaults] valueForKey:#"email"],self.textEventName.text];
self.s3.maxRetries = 10;
self.s3.timeout = 60;
self.tm = [S3TransferManager new];
self.tm.s3 = self.s3;
self.tm.operationQueue.maxConcurrentOperationCount = 2;
S3PutObjectRequest *por = [[S3PutObjectRequest alloc] initWithKey:uploadName
inBucket:[Constants userEventBucket]];
por.contentType = nil;
por.data = data;
por.delegate = self;
por.requestTag = #"uploadAgendatoAmazon";
// Put the image data into the specified s3 bucket and object.
[self.tm upload:por];
}
Here is the blog post for full details;
http://mobile.awsblog.com/post/TxIRFEQTW9XU8G/S3TransferManager-for-iOS-Part-I-Asynchronous-Uploads