MFMailComposeViewController Large Pdfs attachments get stuck - ios

So I am using MFMailComposeViewController to attach pdf files to an email and send it out using the default iOS mail app set up with an exchange account.
If I send a file with size 4863K it works perfectly. Everything goes off as expected.
If I send a file with size 5688K it seems like its working and gets MFMailComposeResultSent as the result sent back to the delegate.
However, the mail never arrives and when I look in the mail app, I see that it is stuck in the Outbox with an error of:
The server rejected the message
What's super weird is that when I try to resent the message from the outbox, it sends out perfectly fine and gives no further error message.
I am sort of at my wits end and have not the first clue what could be going wrong. Clearly I cannot screw around with apple's code, but there don't seem to be many options with the MFMailComposeViewController that I could possibly have screwed up.
The code is fairly straightforward:
- (void) setAttachments:(NSArray*)attatchments
ofDraft:(MFMailComposeViewController*)draft
{
if (attatchments)
{
for (NSString* path in attatchments)
{
NSData* data = [self getDataForAttachmentPath:path];
NSString* basename = [self getBasenameFromAttachmentPath:path];
NSString* pathExt = [basename pathExtension];
NSString* fileName = [basename pathComponents].lastObject;
NSString* mimeType = [self getMimeTypeFromFileExtension:pathExt];
// Couldn't find mimeType, must be some type of binary data
if (mimeType == nil) mimeType = #"application/octet-stream";
[draft addAttachmentData:data mimeType:mimeType fileName:fileName];
}
}
}
The fact that this works for smaller files implies to me that there is nothing wrong with this code and it is copied out of a cordova plugin that sees pretty wide usage. But for whatever reason, files that are over ~5MB just fail for no apparent reason.
Thanks for your help.
Update
I captured the http traffic and discovered that in the situation where the mail is sent using the MFMailComposeViewController, the POST request has a content-length of 0, which is not allowed, so the server returns a 400 error. When I try to resend the same message from the mail app directly, the content-length is the size expected.
This appears to be a limitation of the MessageUI framework, but I cannot verify that there is a universal limitation imposed by Apple.

Related

iOS Document Sharing: "Save to Dropbox" always fails

Posting after finding answer
After "rubber duck debugging" this answer a bunch, I finally came across the correct answer on a question that appears to me to be unrelated. I think this question (and its answer) are still relevant, so I'm posting the question and will post my own answer to hopefully help others like me.
I am creating a PDF in my iOS app that I would like to allow the user to export. For the purposes of this testing, I'm trying to save it to my personal Dropbox on a physical device.
I have turned on iTunes file sharing, and I can verify that the PDF file is being generated correctly, and when I copy it off of my device (iPad Pro Gen. 2 running iOS 11), I can open the PDF and it has the expected content and appearance.
I am able to get the document pop-up to display correctly, and I have options to share via:
Line 1: AirDrop
Line 2: Message, Mail, Add to Notes, (Facebook) Messenger, etc.
Line 3: Copy, Print, Save to Files, Save to Dropbox, etc.
No matter what I try to select (Save to Dropbox is the one I want to solve, but the issue seems universal), it fails. Of note, when I click Save to Dropbox, I do see the Dropbox panel display, but there is immediately a modal over top of the Save to Dropbox modal that says, "An unknown error occurred."
I have tried to look around and see how to get more information about this error, but I'm stumped. I'm not sure if it's correlated, but I get this message in the console:
[AXRun-PID] Client requesting unsuspension of PID:813 Name:<redacted>
Trying to google that error has proved unfruitful.
Here's the code where I generate the PDF and show the menu:
#pragma mark • Sharing Methods
- (void)showShareMenu {
NSArray *bookList = [BookManager bookList];
NSURL *pdfUrl = [PdfGenerator generatePdfFromBooks:bookList];
UIDocumentInteractionController *vc = [[UIDocumentInteractionController alloc] init];
vc.name = #"Booklet.pdf";
vc.URL = pdfUrl;
vc.UTI = #"com.adobe.pdf";
[vc presentOptionsMenuFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
}
I've tried using UIDocumentInteractionController *vc = [UIDocumentInteractionController interactionControllerWithURL:pdfUrl]; instead of the one above, but the results are the same.
I tried making self the delegate of vc and then tried to implement the following methods:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
willBeginSendingToApplication:(nullable NSString *)application;
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(nullable NSString *)application;
Neither of those methods ever fired.
Interestingly, though I think I've supplied the file name correctly based on what I've read, the name in the File textbook in the Save to Dropbox modal is a current timestamp (e.g., File Oct 28, 11 12 22 PM). The Dropbox modal stays up until I click "OK" on the "An unknown error occurred" modal, and then disappears immediately.
It seems like I'm somehow not providing the right information, but I'm not sure how. It seems like there ought to be a delegate method to indicate an error to me, but I don't see anything like that in the docs. (It is late, and I have been looking at this for hours, including reading several related tutorials, so I could have missed something obvious.)
I came across this answer as an example question while asking this current question.
It doesn't really ask the same question I have, nor did that user have the same error outputs I did. But, the linked answer did work for me, too.
The problem I had in the code above was that I was not keeping the UIDocumentInteractionController around after I created it. Adding a private property fixed this issue. So, the following code now works:
#pragma mark • Sharing Methods
- (void)showShareMenu {
NSArray *bookList = [BookManager bookList];
NSURL *pdfUrl = [PdfGenerator generatePdfFromBooks:bookList];
self.docController = [UIDocumentInteractionController interactionControllerWithURL:pdfUrl];
self.docController.name = #"Booklet.pdf";
self.docController.UTI = #"com.adobe.pdf";
[self.docController presentOptionsMenuFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
}

iOS Safari issue with Audio from Server

So currently working on a project and experiencing a strange issue with the ios version of Safari involving the playback of an audio file from a server.
I'm currently facing the following issue:
Person comes onto the page which has a standard html5 audio tag, and a direct link to the audio file for downloading purposes.
Person tries to listen to audio from audio tag, content plays for x number of minutes, cuts off then repeats (the x number of minutes is NOT the length of the recording, and is not consistent).
Person tries direct link of recording, rather then downloading the recording, Safari appears to go to a new page and wraps the download url in a video element, and the same issue as step 2 occurs.
Now the audio file is served up via a java scriptlet, which serves the file with the following code snippet:
String fn = saveTo + file_name;
f = new File(fn);
String fname = f.getName();
String contentType = "audio/wav";
if(fname.endsWith("mp3")){
contentType = "audio/mp3";
}
response.setContentType(contentType);
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Content-disposition", "attachment;filename="+f.getName());
response.setHeader("Content-Length", ""+f.length());
FileInputStream fin = null;
try{
fin = new FileInputStream(f.getCanonicalFile());
byte[] data = new byte[1024];
int x = 0;
while((x = fin.read(data, 0, 1024))>=0){
response.getOutputStream().write(data, 0, x);
Thread.sleep(1);
}
} finally {
if(fin != null) {
try{
fin.close();
}catch(Exception ex){}
}
}
Now I know the code isn't the best by any measure, it isn't my code, and we're obviously working on the assumption that the file is found.
I'm finding when debugging on the iPhone with debug mode on a mac, it doesn't seem to show a return status code. It shows no response headers but it obviously must be receiving something. The server log seems to think its returning a status 200, this showing in Chrome and Firefox.
The code above appears to work fine with Chrome, and Firefox, but not Safari.
The only thing I am guessing is it has something to do with how the file is being pushed to the output stream that Safari isn't liking, or maybe its getting confused and should have a different status code. I've been banging my head against this for a good few days, and reading as much as I can about Safari, though most of the documentation I'm finding is on its "unique" implementation of web audio, and the use of a single channel which seems to be irrelevant to this.
Any Help would be appreciated.
I experienced the same issue with Safari on iOS, and after a lot of debugging, I found the issue was related to the combination of headers applied to the response.
My application is C#-based, but this solution should be platform independent (because as previously stated, it is a response header issue).
Necessary Headers:
Content-Range: bytes 0-[content length]/[content length]
Content-Transfer-Encoding: binary
Content-Length: [content length]
Accept-Ranges: bytes
I devised this after inspecting a response from MP3s delivered via Akamai's content delivery service.

How to read the file after sending a file using "GDServiceClient sendTo:" method as attachment?

I'm Using two native iOS Apps to implement Good Dynamic Framework app kinetics, for now I'm successfully able to send a consumer request, received it at producer end and send a reply form producer , received at consumer end. using
sendTo (GDServiceClient) >>>>>>>>>>>>>>> GDServiceDidReceiveFrom
GDServiceClientDidReceiveFrom <<<<<<<<<<< replyTo (GDService).
problem arise when I try to send a pdf file from producer to consumer and read it at the consumer end.
I use the "GDServiceClient sendTo" method to send the file as its attachment. seems like file received by the consumer end successfully. I can see the good
console Xcode log
GTICCConnection: Number of attachments: 1
GTICCConnection: Finished reading data from all attachments 1/1. Last file size: 273459
I received the file path from attachment as "Inbox/BundleID/....../myfile.pdf"
But the Problem is GDFileManger contentsAtPath:fileInboxPath become nil and cannot read the file.
Questions
1.Can any one help me to resolve this issue. Is there a special way to read the file from GD Inbox?
Why GDFileManager always need a valid NSFilePath when create a new file using createFileAtPath: ? it says it encrypt the file and file won't be at the physical path.
I found it. I will reply with a detailed answer in few days. until that,
Before read the file content I use check for file existence like this.
if ([[GDFileManager defaultManager] fileExistsAtPath:fileInboxPath]){
NSData *fileData = [[GDFileManager defaultManager] contentsAtPath:fileInboxPath];
NSLog(#"Filedata: %#",fileData);
NSString *content = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding];
}
but This if condition fails. After I commented out the line
[[GDFileManager defaultManager] fileExistsAtPath:fileInboxPath]
it starts working.

iOS: sending .jpg image as base64 to tcp server

This one is going to kill me. I'm so close to getting this done except for this one stupid problem. And I am not sure I will be able to adequately describe the problem, but I'll try.
My enterprise app uses the iPhone camera to take pictures of receipts of purchases made by our field personnel. I used a real cool API for turning the jpeg data to base 64 (https://github.com/nicklockwood/Base64) to send via TCP connection to the VB 2010 server, which reads it as a text string and converts it back to a binary.
When the base64 file is created, it is first saved to disk on the phone, because there may be more images. Then when ready to send, the process will read each base64 file and send it one at a time.
The text string created by the base64 function is quite large, and at first it was only sending about 131,000 bytes, which would convert back to binary easily enough but would render about 1/4 to 1/3 of the image. I just figured that the data was being truncated because the app was trying to get ahead of itself.
So then I found a nice snippet that showed me how to use the NSStreamEventHasSpaceAvailable event to split the base64 string into several chunks and send them sequentially. (http://www.ios-developer.net/iphone-ipad-programmer/development/tcpip/tcp-client) That works great insofar as it sends the full file -- that is, the resulting file received by the server is the correct size, the same as the base64 file before it's sent.
The problem here is that at some point the file received by the server is corrupted because it seems to start all over at the beginning of the file... in other words, the data starts to repeat itself.
The odd part is that the repeating part starts at exactly the same spot in the received file every time: at position 131016. It doesn't start the repetition at the end of the file, it just interrupts the file at that point and jumps back to the beginning. And it happens that that was the size of the file that was sent before I started using the HasSpaceAvailable event. I can't figure out what the significance of the value 131,016 is. Maximum buffer size somewhere?
Using all kinds of NSLogs and breakpoints, I have pretty much determined that the data is leaving the phone that way, and not being scrambled by the server. I also wrote in an NSMailComposeViewer method that would email me the base64 file as an attachment, and it comes through perfectly.
Here is the code for when the file is read from disk and sent to the server:
int i;
for (i = 0; i < [imageList count];i++){
NSArray *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory= [documentsPath objectAtIndex:0]; //Get the docs directory
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageFileName];
imageFileName = [imageList objectAtIndex:i] ;
NSLog(#"Image index: %d - image file name: %#",i,imageFileName);
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
if(fileExists == YES){
NSString *imageReceipt = [NSString stringWithContentsOfFile:imagePath encoding:NSASCIIStringEncoding error:nil];
int32_t imageStringLen = [imageReceipt length];
NSString *imageSize = [NSString stringWithFormat: #"%d",imageStringLen];
NSString *currentImage = [NSString stringWithFormat:#"image,%#,%#,%#",imageFileName,imageSize,imageReceipt]; //creates a CSV string with a header string with the filename and file size, and then appends the image data as the final comma-separated string.
data = [[NSMutableData alloc] initWithData:[currentImage dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
And then here is the code that uses the HasSpaceAvailable event:
case NSStreamEventHasSpaceAvailable:
if (data != nil)
{
//Send rest of the packet
int ActualOutputBytes = [outputStream write:[data bytes] maxLength:[data length]];
int totalLength = [data length];
if (ActualOutputBytes >= totalLength)
{
//It was all sent
data = nil;
}
else
{
//Only partially sent
[data replaceBytesInRange:NSMakeRange(0, ActualOutputBytes) withBytes:NULL length:0]; //Remove sent bytes from the start
}
}
break;
(I especially like this code because it would allow placing a ProgressView control on the screen.)
The network stream event handler code is in the root view controller, but the image data in base64 is being sent from another view controller. My instinct tells me that this is not a problem because it has worked fine until now, but with much shorter strings.
Now, there's one other issue that may be related -- and probably is. I can't seem to complete the transfer of the data unless I close the app. The server doesn't see it until the connection is closed, I guess. I have tried placing [outputStream close] in various places in the code to no avail. I've also tried terminating the base64 string with a linefeed or carriage return or both.
The server is programmed to save the file when it has seen the correct number of bytes, but that never happens until the app is closed. I know by using WireShark on the server that some of the data is being received, but the remaining data, as I have said, doesn't arrive until the app is closed.
I suspect that this last part (completing the transfer) is the problem, but for the life of me, I can't find anything online that addresses this, unless I am just too ignorant to know what search terms to use.... which is highly likely.
I hope I have given enough information. Can anyone help me?
EDIT
The solution to the problem appeared to be different than what was suspected in the original answer. Through the discussion in the comments the QP has been led to a solution. Here is a summary:
The transmission of data through a raw TCP socket requires thorough handling of the enqueing logic, which was not properly taken into account by the QP. I recommended to use the socket library CocoaAsyncSocket which handles this part of the task and which in turn led the QP to a working solution.
Original Answer:
I'd guess NSString is not up to the task. It's made to hold, well, strings.
Try to read the file into an NSData directly instead and sending it as binary. (Its ascii in the end, isn't it?) Besides, this will be much more resource friendly than your current code.

How to specify localized property list?

I have an ios application which is calling a rest web service. I have created a property list file in my app which contains a dictionary of error codes that will be returned by the server and a corresponding message to show.
I am thinking of having multiple property list files for different languages.
How can i make the app to pick up a specific property list based on locale?
You say that the server is generating an error code and a message.
So I think there are more ways to do it.
First you could send a locale header in your request to the server, so that the server would do all localizations for you (which in my opinion is not as good as solution #2).
So I would prefer the way of letting the server just return error codes and handling the messages on client side.
You could create a language project in xcode, for each language you want to support:
http://www.ibabbleon.com/iphone_app_localization.html#extract
In the localizable.strings file I would do the following:
"RestServiceXYErrorTitle_1" = "Authentication failed";
"RestServiceXYErrorMessage_1" = "Your credentials were wrong";
"RestServiceXYErrorTitle_2" = "Resource not available";
"RestServiceXYErrorMessage_2" = "The resource you requested is no longer available";
....
Then I would take the error code returned by the server, e.g. 1 and put it together with my localization string:
NSString *localizedTitleKey = [NSString stringWithFormat:#"RestServiceXYErrorTitle_%#", errorCode];
NSString *localizedMessageKey = [NSString stringWithFormat:#"RestServiceXYErrorMessage_%#", errorCode];
NSString *errorTitle = NSLocalizedString(localizedTitleKey,#"");
NSString *errorMessage = NSLocalizedString(localizedMessageKey,#"");
I think this would be a good solution

Resources