I have created an iCarousel View and am trying to display a video preview in each "cell"/view.
I have the videos stored in Parse and am trying to
Query from the cloud
Retrieve the data from the PFFile
Convert the data to URL
Play URL using AVPlayer
Here my code so far.
-(void)getPast{
dataArray = [[NSMutableArray alloc]init];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
PFQuery *query = [PFQuery queryWithClassName:#"History"];
[query whereKey:#"Location" containsString:[defaults objectForKey:#"location"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
pastArray = [NSMutableArray arrayWithArray:objects];
for (PFObject *files in objects){
PFFile *file = [files objectForKey:#"File"];
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[dataArray addObject:data];
}];
}
[self.carouselView reloadData];
}];
}
Im getting an error saying that my dataArray is empty,
I think the problem here could be that since I'm querying in the background, the For loop is finishing before I have received the data and therefore the array is empty, although I could be wrong and I don't know how to fix this even if I was right.
Code for displaying preview
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view {
PFObject *object = [pastArray objectAtIndex:index];
NSData *data = [dataArray objectAtIndex:index];
NSString *dataString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSURL *URL = [NSURL URLWithString:dataString];
NSLog(#"URL %#",URL);
view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 275)];
self.playerViewController = [[AVPlayerViewController alloc]init];
self.playerViewController.player = [AVPlayer playerWithURL:URL];
self.playerViewController.view.frame = view.bounds;
self.playerViewController.showsPlaybackControls = NO;
[view addSubview:self.playerViewController.view];
view.autoresizesSubviews = YES;
self.playerViewController.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerViewController.player.currentItem];
[self.playerViewController.player play];
return view;
}
How can I fix my code so that each view autoplays the PFFile video corresponding to its index in the Array.
My problems:
Array is empty
Playing content for each view isn't working
Ps. Im aware that I'm not using PFObject *object.
As you guessed the for cycle finishes it's execution way before the blocks are called, you have to make sure the data is loaded before you call reloadData
The first thing that comes to my mind on how to handle this will be something like
for (PFObject *files in objects){
PFFile *file = [files objectForKey:#"File"];
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[dataArray addObject:data];
[self checkData];
}];
}
- (void)checkData {
//Check the if the data is completed
if(dataArray.count == numberOfFiles) { //Maybe a more complex if is required here but you get the idea
//All files are downloaded
dispatch_async(dispatch_get_main_queue(), ^{
//We are sure the data is ready so we reload it
[self.carouselView reloadData];
});
}
}
Also you should always check if NSData is valid before loading it
Related
I am trying to download a PFFile which is a tfss file(?). It gets opened as a text file. This is the code I use for retrieveing the data:
-(void) queryVideoWithDictionary:(NSDictionary *)dictionary {
PFQuery *videoQuery = [PFQuery queryWithClassName:#"EventVideos"];
[videoQuery whereKey:#"objectId" equalTo:[dictionary objectForKey:#"videoId"]];
[videoQuery findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
if (!error) {
PFFile *videoFile = [results[0] objectForKey:#"video"];
[videoFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// NSURL *movieURL = [NSURL fileURLWithPath:dataString];
NSMutableDictionary *videoDictionary = [NSMutableDictionary dictionaryWithObject:data forKey:[dictionary objectForKey:#"videoId"]];
[self.videoArray addObject:videoDictionary];
NSLog(#"video array count: %ld", self.videoArray.count);
NSLog(#"dataString: %#", dataString);
// NSLog(#"movieURL: %#", movieURL);
[self.player setItemByStringPath:dataString];
[self.player play];
});
}];
} else {
NSLog(#"error");
}
}];
}
When dataString gets logged, it returns null, so I don't know how to get the data and play it as a video. It is saved as a QuickTimeMovie by the way.
I use SCRecorder to play and record videos.
Edit: data is definitely not nil, I still don't know how to solve this issue.
I have a json that contains two images' urls, and I use SDwebimage to load the two images, please look at following code
NSDictionary *dic = [_arrayDB objectAtIndex:[indexPath row]];
NSURL *imageUrl = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",imageURL, [dic objectForKey:#"oneimageurl"]]];
__block UIActivityIndicatorView *activityIndicator;
__weak UIImageView *weakImageView = cell.oneimage;
[cell.oneimage sd_setImageWithURL:imageUrl placeholderImage:nil options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize){
if (!activityIndicator) {
[weakImageView addSubview:activityIndicator = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]];
activityIndicator.center = weakImageView.center;
[activityIndicator startAnimating];
}
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}];
NSURL *imageUrl1 = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",imageURL, [dic objectForKey:#"twoimageurl"]]];
__block UIActivityIndicatorView *activityIndicator1;
__weak UIImageView *weakImageView1 = cell.twoimage;
[cell.twoimage sd_setImageWithURL:imageUrl1 placeholderImage:[UIImage imageNamed:#"avatar"] options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize1, NSInteger expectedSize1) {
if (!activityIndicator1) {
[weakImageView1 addSubview:activityIndicator1 = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]];
activityIndicator1.center = weakImageView1.center;
[activityIndicator1 startAnimating];
}
} completed:^(UIImage *image1, NSError *error1, SDImageCacheType cacheType1, NSURL *imageURL1) {
[activityIndicator1 removeFromSuperview];
activityIndicator1 = nil;
}];
this code runs well, but i think it's so more code, and I want to optimize this code, so can you help me? Many thanks.
I think SDwebImage not do resizing - you may use this - https://github.com/mustangostang/UIImage-ResizeMagick
you can use easily like -
[cell.twoimage sd_setImageWithURL:imageUrl1 placeholderImage:[UIImage imageNamed:#"avatar"]];
for image2 -
[cell.twoimage sd_setImageWithURL:imageUrl2 placeholderImage:[UIImage imageNamed:#"avatar"]];
just put these line of code in between for speedy download as -
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[cell.twoimage1 sd_setImageWithURL:imageUrl1 placeholderImage:[UIImage imageNamed:#"avatar"]];
[cell.twoimage2 sd_setImageWithURL:imageUrl2 placeholderImage:[UIImage imageNamed:#"avatar"]];
});
Since you have two UIImageView's you need to populate with a unique UIImage you will have to use separate code for each UIImageView
I have a class for get xml and parse it.
+ (instancetype)sharedRssNewsLoader
{
static BGMRssNewsLoader *sharedRssNewsLoader = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedRssNewsLoader = [[self alloc] initPrivate];
});
return sharedRssNewsLoader;
}
- (instancetype)initPrivate
{
self = [super init];
//did the superclass's designated initializer succeed?
if (self)
{
self.rssNewsItems = [[NSMutableArray alloc] init];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:nil];
//RSS call
[self fetchFeed];
}
return self;
}
- (void)fetchFeed
{
NSString *requestString = #"http://xxx...";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
self.rssXmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
DDLogVerbose(#"rssXmlString: %#", self.rssXmlString);
dispatch_sync(dispatch_get_main_queue(), ^{
[self parseXML:self.rssXmlString];
});}
];
[dataTask resume];
}
//parse XML
- (void)parseXML:(NSString *)source
{
NSError *error = nil;
DDXMLDocument *theDocument = [[DDXMLDocument alloc] initWithXMLString:source
options:0
error:&error];
NSArray *xmlItems = [theDocument nodesForXPath:#"//item"
error:&error];
for(DDXMLElement *itemElement in xmlItems)
{
NSString *itemTitleValue = [[[itemElement elementsForName:#"title"] lastObject] stringValue];
NSString *itemDescriptionValue = [[[itemElement elementsForName:#"title"] lastObject] stringValue];
BGMRssNewsEntry *rssEntry = [[BGMRssNewsEntry alloc] initRssNewsEntryWithTitle:itemTitleValue
rssText:itemDescriptionValue
rssUrl:nil
rssDate:nil
isRssRead:NO];
[self.rssNewsItems addObject:rssEntry];
}
}
//if a programmer calls [[BGMItemsStore alloc] init], let him know the error of his ways
- (instancetype) init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"You are wrong! Use +[BBGMRssNewsLoader sharedRssNewsLoader]"
userInfo:nil];
return nil;
}
I would like to show result in the table. So in the other class I do:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self)
{
//custom initialization
//initialization sharedRssNewsLoader with rssItems array and RSS call
[BGMRssNewsLoader sharedRssNewsLoader];
}
return self;
}
But my table is absolutely clear. I know that I need to switch to main_queue when I am working with NSURLSessionDataTask, I did it. I think that I mess with thread :(
That BGMRssNewsLoader is asynchronously loading the data and in parseXML you are adding entries to the self.rssNewsItems object. But I don't see where it tells the table to reload itself when this asynchronous request done. If your table view's data source tries to immediately retrieve the rssNewsItems array, it will likely be empty until the asynchronous request is complete.
Generally, when you're done loading the data, you'd call [self.tableView reloadData] (either by supplying the tableview as a parameter during the instantiation of the BGMRssNewsLoader, or by having it post a notification that the table view controller is observing, or by supplying a completion block parameter in which you'd perform reloadData).
I'd also suggest temporarily adding a breakpoint or log statement where you add objects to the rssNewsItems array, just to make sure that that the XML feed was successfully retrieved and parsed.
For example, I might retire fetch and define a method like so:
- (void)fetchFeedWithCompletionBlock:(void (^)(NSArray *items, NSError *error))completion
{
NSString *requestString = #"http://xxx...";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
self.rssXmlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
DDLogVerbose(#"rssXmlString: %#", self.rssXmlString);
dispatch_sync(dispatch_get_main_queue(), ^{
[self parseXML:self.rssXmlString];
if (completion) {
completion(self.rssNewsItems, nil);
}
});
}];
[dataTask resume];
}
I would, by the way, not have the initPrivate method initiate the fetch. You want the caller to be able to supply whatever completion block it wants.
I'd then have the tableview's controller do something like:
[[BGMRssNewsLoader sharedRssNewsLoader] fetchFeedWithCompletionBlock:^(NSArray *items, NSError *error) {
// use the items array and/or error
[self.tableView reloadData];
}];
Clearly, the fetchFeedWithCompletionBlock should actually detect errors and call the completion block with the appropriate NSError (so the table view can decide how it wants to present the error ... the singleton shouldn't be doing anything with the UI itself), but hopefully this illustrates the idea of a network object that provides a completion block to allow the caller to specify what it wants to do when the asynchronous request is complete.
How can I check if the NSData dataWithContentsOfURLparsing in my secondary thread are finished? When every image is finished I want to open my view controller. Not before. Now I can open my view controller directly, and sometimes if I'm to quick my table view has no images, because they're not finished yet. Any ideas?
The following code happens in didFinishLaunchingWithOptions in AppDelegate. Im using the SBJSON framework for parsing.
(Im using the storyboard in this project so there's no code for opening the first view controller)
Code:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"json_template" ofType:#"json"];
NSString *contents = [NSString stringWithContentsOfFile: filePath encoding: NSUTF8StringEncoding error: nil];
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *json = [jsonParser objectWithString: contents];
tabs = [[NSMutableArray alloc] init];
jsonParser = nil;
//parsing json into model objects
for (NSString *tab in json)
{
Tab *tabObj = [[Tab alloc] init];
tabObj.title = tab;
NSDictionary *categoryDict = [[json valueForKey: tabObj.title] objectAtIndex: 0];
for (NSString *key in categoryDict)
{
Category *catObj = [[Category alloc] init];
catObj.name = key;
NSArray *items = [categoryDict objectForKey:key];
for (NSDictionary *dict in items)
{
Item *item = [[Item alloc] init];
item.title = [dict objectForKey: #"title"];
item.desc = [dict objectForKey: #"description"];
item.url = [dict objectForKey: #"url"];
if([dict objectForKey: #"image"] != [NSNull null])
{
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^(void)
{
NSURL *imgUrl = [NSURL URLWithString: [dict objectForKey: #"image"]];
NSData *imageData = [NSData dataWithContentsOfURL: imgUrl];
dispatch_async( dispatch_get_main_queue(), ^(void)
{
item.image = [UIImage imageWithData: imageData];
});
});
}
else
{
UIImage *image = [UIImage imageNamed: #"standard3.png"];
item.image = image;
}
[catObj.items addObject: item];
}
[tabObj.categories addObject: catObj];
}
[tabs addObject: tabObj];
}
//sort array
[tabs sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
Tab *r1 = (Tab*) obj1;
Tab *r2 = (Tab*) obj2;
return [r1.title caseInsensitiveCompare: r2.title];
}];
/***** END PARSING JSON *****/
[[UINavigationBar appearance] setTitleTextAttributes: #{
UITextAttributeTextShadowOffset: [NSValue valueWithUIOffset:UIOffsetMake(0.0f, 0.0f)],
UITextAttributeFont: [UIFont fontWithName:#"GreatLakesNF" size:20.0f]
}];
UIImage *navBackgroundImage = [UIImage imageNamed:#"navbar.png"];
[[UINavigationBar appearance] setBackgroundImage:navBackgroundImage forBarMetrics:UIBarMetricsDefault];
UIImage *backButtonImage = [[UIImage imageNamed:#"backBtn.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
UIImage *backButtonSelectedImage = [[UIImage imageNamed:#"backBtn_selected.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonSelectedImage forState: UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
return YES;
Also, if this way of parsing is bad, please tell me!
First of all, you shouldn't use such way of downloading any content from remote host.
There are lots of libraries like AFNetworking, ASIHTTPRequest
which work around CFNetwork or NSURLConnection to handle such things as redirects, error handling etc.
So you should definitely move to one of those (or implement your own based on NSURLConnection).
As a direct answer to your question:
You should use some kind of identifier for counting downloaded images (i.e. for-loop iteration counter) and pass it via +[UINotificationCenter defaultCenter] as a parameter of some custom notification.
Example (assuming that you are blocking current thread by +[NSData dataWithContentsOfURL:]):
for (int i = 0; i < 10; i++) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"someCustomNotificationClassName" object:nil userInfo:#{ #"counter" : #(i) }];
}
More expanded example of NSNotification-based approach:
- (id)init {
self = [super init];
if (self) {
// subscribing for notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDataDownload:) name:#"someCustomNotificationClassName" object:nil];
}
return self;
}
- (void)dealloc {
// unsubscribing from notification on -dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - downloading delegation
- (void)handleDataDownload:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
int counter = [userInfo[#"counter"] intValue];
if (counter == 10) {
// do some work afterwards
// assuming that last item was downloaded
}
}
Also you can use callback technique to manage handling of download state:
void (^callback)(id result, int identifier) = ^(id result, int identifier) {
if (identifier == 10) {
// do some work afterwards
}
};
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions), ^{
// some downloading stuff which blocks thread
id data = nil;
callback(data, i);
});
}
I'm trying to send a PDF using a UIActivityViewController. So far everything works fine using a fairly basic approach but the one issue I have is that when I select the send by mail option, the PDF's file name is Attachment-1 rather than Calculation.PDF which is the name that I give the file.
I don't mind too much the change in title, but the lack of a .pdf extension does seem to cause a problem when sending the file to people with Windows PC's and I'd like to fix that.
I've had a look at:
Control file name of UIImage send with UIActivityViewController
But can't see an equivalent method to:
[mailComposer addAttachmentData: UIImagePNGRepresentation(viewImage) mimeType:#"" fileName:#"myImage.png"];
that will work with a PDF file. Is this something that is not fixable without customization or is there a simple solution to this problem?
try this
NSData *pdfData = [NSData dataWithContentsOfFile:pdfFilePath];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[#"Test", pdfData] applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];
and also
NSString *str = [[NSBundle mainBundle] pathForResource:#"AppDistributionGuide" ofType:#"pdf"];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[#"Test", [NSURL fileURLWithPath:str]] applicationActivities:nil];
Swift
let url = NSURL.fileURLWithPath(fileName)
let activityViewController = UIActivityViewController(activityItems: [url] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
Swift 4.0
Here I attached code.I just added thread handling in to present "activityViewController" because of this viewcontroller present before load actual data.
let url = NSURLfileURL(withPath:fileName)
let activityViewController = UIActivityViewController(activityItems: [url] , applicationActivities: nil)
DispatchQueue.main.async {
self.present(activityViewController, animated: true, completion: nil)
}
Above listing about Swift is deprecated in Swift 3
let url = NSURL.fileURL(withPath: fileName)
let activityViewController = UIActivityViewController(activityItems: [url] , applicationActivities: nil)
present(activityViewController,
animated: true,
completion: nil)
For Objective-C tested code to share PDF
- (void)downloadPDFfile:(NSString *)fileName withFileURL:(NSString *)shareURL {
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/%#",[self generateName:fileName withFiletype:#"pdf"]]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:shareURL]];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Download Error:%#",error.description);
} else if (data && error == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[data writeToFile:filePath atomically:YES];
[self shareFile:fileName withFilepath:filePath];
});
}
}];
[task resume];
});
}
-(void)shareFile:(NSString*)withfileName withFilepath:(NSString*)filePath {
NSMutableArray *items = [NSMutableArray array];
if (filePath) {
[items addObject:[NSURL fileURLWithPath:filePath]];
}
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
[activityViewController setValue:withfileName forKey:#"subject"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
activityViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = activityViewController.popoverPresentationController;
popPC.sourceView = self.view;
CGRect sourceRext = CGRectZero;
sourceRext.origin = CGPointMake(self.view.frame.size.width-30, 0);
popPC.sourceRect = sourceRext;
popPC.permittedArrowDirections = UIPopoverArrowDirectionDown;
}
[activityViewController setCompletionWithItemsHandler:
^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
}];
[self presentViewController:activityViewController animated:YES completion:nil];
}
-(NSString*)generateName:(NSString*)title withFiletype:(NSString*)type {
NSString *subject = [title stringByReplacingOccurrencesOfString:#" " withString:#"_"];
subject = [NSString stringWithFormat:#"%#.%#",subject,type];
return subject;
}
call function like below
[self downloadPDFfile:#"yourFileName" withFileURL:shareURL];
For Swift 3
You have to have a URL array with the path of the PDF you want to send.
let urlArray = [pdfPath1, pdfPath2]
Then create an UIActivityViewController:
let activityController = UIActivityViewController(activityItems: urlArray, applicationActivities: nil)
If you are using a UIBarButtonItem to make that action, you can implement this to prevent an error on iPad:
if let popover = activityController.popoverPresentationController {
popover.barButtonItem = self.barButtonItem
}
Finally you have to present the activityController:
self.present(activityController, animated: true, completion: nil)
The reply by Muruganandham K is simple and quite elegant. However, it doesn't work in iOS 9. In order to make it work, if you remove the #[#"Test" and just pass the pdfData, an attachment is made.
NSData *pdfData = [NSData dataWithContentsOfFile:pdfFilePath];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:pdfData applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];
May be try this..
#define IS_IPAD UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
// Validate PDF using NSData
- (BOOL)isValidePDF:(NSData *)pdfData {
BOOL isPDF = false;
if (pdfData.length >= 1024 ) {
int startMetaCount = 4, endMetaCount = 5;
// check pdf data is the NSData with embedded %PDF & %%EOF
NSData *startPDFData = [NSData dataWithBytes:"%PDF" length:startMetaCount];
NSData *endPDFData = [NSData dataWithBytes:"%%EOF" length:endMetaCount];
// startPDFData, endPDFData data are the NSData with embedded in pdfData
NSRange startRange = [pdfData rangeOfData:startPDFData options:0 range:NSMakeRange(0, 1024)];
NSRange endRange = [pdfData rangeOfData:endPDFData options:0 range:NSMakeRange(0, pdfData.length)];
if (startRange.location != NSNotFound && startRange.length == startMetaCount && endRange.location != NSNotFound && endRange.length == endMetaCount ) {
// This assumes the checkstartPDFData doesn't have a specific range in file pdf data
isPDF = true;
} else {
isPDF = false;
}
}
return isPDF;
}
// Download PDF file in asynchronous way and validate pdf file formate.
- (void)downloadPDFfile:(NSString *) fileName withFileURL:(NSString *) url {
NSString *filePath = #"";
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
filePath = [documentDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/Attachments/%#",[self generateName:fileName withFiletype:#"pdf"]]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
NSLog(#"Download Error:%#",error.description);
} else if (data && error == nil) {
bool checkPdfFormat = [self isValidePDF:data];
if (checkPdfFormat) {
//saving is done on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[data writeToFile:filePath atomically:YES];
NSURL *url = [NSURL fileURLWithPath:filePath];
[self triggerShare:fileName withFilepath:filePath];
});
}
}
}];
});
}
// Trigger default share and print functionality using UIActivityViewController
-(void) triggerShare:(NSString*)fileName withFilepath:(NSString*)filePath {
* Set this available field on the activity controller */
NSMutableArray *items = [NSMutableArray array];
if (filePath) {
[items addObject:[NSURL fileURLWithPath:filePath]];
}
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
[activityViewController setValue:fileName forKey:#"subject"]; // Set the mail subject.
if (IS_IPAD) {
activityViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = activityViewController.popoverPresentationController;
popPC.sourceView = self.view;
CGRect sourceRext = CGRectZero;
sourceRext.origin = CGPointMake(self.view.frame.size.width-30, 0 ); // I have share button in navigation bar. ;)
popPC.sourceRect = sourceRext;
popPC.permittedArrowDirections = UIPopoverArrowDirectionUp;
}
[activityViewController setCompletionWithItemsHandler:
^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
}];
[self presentViewController:activityViewController animated:YES completion:nil];
}
-(NSString*) generateName:(NSString*)title withFiletype:(NSString*)type {
NSString *subject = [title stringByReplacingOccurrencesOfString:#" " withString:#"_"];
subject = [NSString stringWithFormat:#"%#.%#",subject,type];
return subject;
}