I am making an iPad app where you can download files (like PDF, doc, etc) and view them offline.
I already have the view part and you can download a file to the document directory.
As it is now you need to wait for the download to be finished to move on.
This can be solved by putting it in a thread, but what happens when the user downloads multiple files or even download the same file multiple times?
My idea is to make a download queue, with a view for the progress.
Workflow:
The user opens a document and press download, the user gets a message that the download is started and can be viewed in the offline documents view.
The user downloads 3 more documents.
When the user goes to the offline document view the user sees a table view with 4 filled cells. 2 documents are done loading and 2 other are still downloading because there is a download/status bar shown in the table view cell.
The downloaded documents can be viewed or deleted.
The downloads in progress can not be watched (yet) but can be cancelled.
I want to make a threaded download class where you can add urls to be downloaded. the class has methods to cancel and delete document-downloads, but also has methods to return the progress.
If possible the class can handle simultaneous downloads.
The problem is, I don't know where to start?
NSURLConnection is already asynchronous. All you need to do is to create NSURLConnection instances, associate them with your data structures, and have at it.
Here's an example where I assume you have one UIView per item. If you use a table view you can't count on view instances, but instead associate a download with an NSIndexPath, or something else.
#implementation MyDownloadView
- (void)startDownload {
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
[req setHTTPMethod:#"GET"];
// Set headers etc. if you need
[[[NSURLConnection alloc] initWithRequest:req delegate:self] autorelease];
[req release];
self.responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Alternatively, store to a file so you don't run out of memory
[self.responseData appendData:data];
}
#end
Then implement the other NSURLConnection delegate methods to do what you need.
I've written an open source example that has pretty much all features you want, canceling a download is currently only available in code, but it's pretty easy to add a button for that.
I'm using asi-http-request for managing the downloads, and they are displayed in a grid view (AQGridView) instead of a UITableView, but i think you get the idea.
Download progress is managed via KVO.
See PSPDFDownload.m for a start. Download the full demo here
Full disclosure: This demo uses PSPDFKit for faster pdf display. But the Kiosk example is exactly what you need, and you don't need to use PSPDFKit for pdf display. There's even an example code path that uses Apple's QuickLook.
Related
Right now I am developing a macOS WebApp, this includes a WKWebview.
However, when I want to upload or download a file, it doesn't work. When I click, nothing happens! Like, nothing. I click on for example on “choose file” or any other upload button, and the window that should appear to select a file or directory doesn’t show up.
I have been struggling all day long, checking stackoverflow and many other resources and I found many posts related to my problem, but they are all out-dated.
My code is really simple;
- (void)viewDidLoad {
[super viewDidLoad];
// Load the url into the webview
NSURL *url = [NSURL URLWithString:#"https://www.my-website-url.com"];
[self.myView loadRequest:[NSURLRequest requestWithURL:url]];
}
So basically all I want is that I can download and upload files to and from my WKWebView.
It is NOT a duplicate question because I still don't get how to implement the code that Willeke is referring to in the comments.
The title pretty much says it all. What follows is my best guess, but the event just doesn't appear at all.
NSArray *items = #[string, URL, (EKEvent *)[self createCalendarEvent]];
UIActivityViewController* activity = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:activities];
[self presentViewController:activity animated:YES completion:NULL];
Does anyone know of a way to accomplish this? Or do I have to create an ics file and send that?
UIActivityViewController:
The system provides several standard services, such as copying items
to the pasteboard, posting content to social media sites, sending
items via email or SMS, and more.
Apps can also define custom services.
It's not (yet) possible to share an EKEvent-object nor its parent EKCalendarItem as none of the standard services provide a way to interact with this type of activity.
You should indeed create an .ics-file to share your event. It's a bit strange that iOS doesn't convert the object by itself as it do this for strings (into .txt-files).
You could however fill in a request to get this option.
I'm loading a rather simple XML file from a URL. I wanted to show the network activity indicator while the parser is loading the file, but obviously not while it is parsing.
Question: Does the initWithContentsOfUrl: method lock program execution while the document is loaded from the url? In other words, is the code below correct?
It seems obvious to me that this is okay, but I wanted to make 100% sure.
NSString* const urlString = #"...";
NSURL* url = [NSURL URLWithString:urlString];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSXMLParser* parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
This article explains it pretty well http://akosma.com/2010/05/28/initwithcontentsofurl-methods-considered-harmful/:
The main problem with these methods, of course, is the fact that they
are synchronous; this means that the thread executing them (usually
the UI thread) will block completely until they return, and in most
applications this means that you are de-facto blocking the whole
application for an unknown amount of time. This means that no buttons
or UI widgets will react to input, no navigation will be possible, no
touch events will be delivered or executed, nothing will happen at all
until the network operation completes.
Even worse; when using initWithContentsOfURL:, there is no timeout,
there is no meaningful feedback for network failures, and no way for
the user to cancel the current network operation. This last factor
justifies by itself not using initWithContentsOfURL: at all; you must
never ship code that leads to a bad user experience. Your users will
resent this and will complain!
If you want to display download progress, you will need to download the file yourself using something like NSURLConnection, then pass the local file path to the XML parser.
Yes, it does block. So your activity indocator won't be displayed.
I have a simple UITableView, where each cell has an thumbnail picture that the user may have taken with its iOS device camera.
If iCloud is enabled the image is saved in it. However I was wondering if some sort of caching happens when loading the image, because I have notice a slowness on first loading then, even if the piece of code is called again when cell display on screen, the image show quite fast.
This is the relevant code fragment, I have omitted the logic for building the cell, I think it is not relevant, because the question is about other aspects:
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// logic for retrieve data and build cell
NSURL *ubiquityUrl = [fm URLForUbiquityContainerIdentifier:nil];
NSURL *docURL = [ubiquityUrl
URLByAppendingPathComponent:[NSString stringWithFormat:#"P_%#_%#.jpg",imgId,#"thumbnail"]
isDirectory:NO];
// this a custom object extending UIDocument
IP2DataDocument *dataDocument = [[IP2DataDocument alloc] initWithFileURL:docURL];
[dataDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"iCloud document opened");
// logic for filling table cell picture
} else {
NSLog(#"failed opening document from iCloud");
}
}];
// returning cell
}
I can see "iCloud document opened" in Xcode, each time the cell display.
If some sort of caching occurs, can you point out where and how ?
The Document-based App Programming Guide for iOS says:
When you run a metadata query to learn about an application’s iCloud documents, the query results are placeholder items (NSMetadataItem objects) for document files. The items contain metadata about the file, such as its URL and its modification date. The document file is not in the iCloud container directory.
The actual data for a document is not downloaded until one of the following happens:
Your application attempts to open or access the file, such as by calling openWithCompletionHandler:.
Your application calls the NSFileManager method startDownloadingUbiquitousItemAtURL:error: to download the data explicitly.
To sum it up: The first time you open your document, it may not have been downloaded from iCloud yet, which is why it'll take longer. Afterwards, there is a local copy of the file that is obviously faster to read.
It is your docURL or specially
NSURL *ubiquityUrl = [fm URLForUbiquityContainerIdentifier:nil];
You don't actual access directly to iCloud. You access an equivalent local location (ubiquityUrl) that is iCloud enabled. If the file that you try to get has not been downloaded locally yet, then the local iCloud daemon will download it at the time you are accessing. Once this occurs, it is stored in your device locally. So the next time, you try to access it again using the docURL, it is much quicker because of this. Also the iCloud daemon is responsible for syncing version your document. So you don't need to worry about that if someone else has updated a newer version of the same document from another device.
I am using iOS 4.3 & was wondering if there is any way that I can access the Safari's "Reader" feature through which webpages are removed of ads & other riff raff & the content takes the center stage.
If one opens any article in Safari (on say Wikipedia website), then a "Reader" button appears on the URL bar. Clicking on it presents a new window presenting the content beautifully.
How can I leverage this this functionality in iOS through UIWebView ?
PS: I know there is something called Readability Project. But I have no idea how to use this through UIWebView. Also for some websites Safari's Reader takes a call not to enable "Reader" feature, maybe it has no sufficient confidence?
Important: THIS ANSWER NO LONGER WORKS!
Readability shut down on September 30, 2016.
Here is something they recommend as a replacement:
https://mercury.postlight.com/web-parser/
Keeping the answer as a historical reference
--- Original answer ---
You can use Readability mobilizer for this. You will get a cleaned up version of any article, in the Readability styling:
http://www.readability.com/m?url=http://{URLOFTHEARTICLE}
Just prepare the URL and load it in your UIWebView. Here is how it looks in action:
http://www.readability.com/m?url=http%3A%2F%2Fwww.cnn.com%2F2013%2F01%2F11%2Fshowbiz%2Ftv%2Fgolden-globes-tv-vineyard%2Findex.html%3Fhpt%3Dhp_abar
Apple is making a pretty big deal about the inclusion of "Reader" in iOS 5. I'm assuming by the noise it's not available in 4.3
re: How to use through UIWebView
I can't find any mention of it in the Web Content Guide.
There's nothing about it in the UIWebView class reference.
And there's nothing in QA1630.
Dont parse HTML natively on iOS, I have done it before and its a messy business. Either create your own web service to do all the nasty work or look into using readability (readability.com) they provide an API.
There is also an open source ruby, python and php readability port that you can find here
https://github.com/iterationlabs/ruby-readability
https://github.com/gfxmonk/python-readability
http://code.fivefilters.org/p/php-readability/source/tree/master/
For you ruby enthusiasts, readability is also available as a gem, just google it.
Actually reader button do a bit of analysis where it parse the HTML Page and then it sees a clear body tag to parse. If that plugin is able to extract the exact body it will enable the reader button (My understanding from the readability source code). Now to implement the same for webview you just need to embed java script in your code (this java script is already available in the readability source code) and then you can achieve the same effect.
But I suspect the future plan from apple for the same. Because they can not just let anyone else do this content extraction with the huge business opportunity associated with iCloud with the combination of readability.
If you want you can simple extract the HTML from UIWebView and then extract the body and use it for your purpose. It's not a very rocket science to extract.
For analysis point of view, just have randomly some 10 HTML pages with Reader button enabled, you will see the core cotent belongs to body only and rest of the add, header, footer are separated.
I believe this is the time to re-invent the web content we use, and this is the perfect example of doing the same.
You can even do this by injecting javascript.
#define readJS #"(function(){window.baseUrl='https://www.readability.com';window.readabilityToken='';var s=document.createElement('script');s.setAttribute('type','text/javascript');s.setAttribute('charset','UTF-8');s.setAttribute('src',baseUrl+'/bookmarklet/read.js');document.documentElement.appendChild(s);})()"
And then when your webpage finishes loading
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
[webview stringByEvaluatingJavaScriptFromString:readJS];
You can do it in iOS9.
first import SafariServices:
#import <SafariServices/SafariServices.h>
Afterwards we are instantiating SFSafariViewController and adding it as a subview. We have two options doing so:
Creating with only base URL
Creating with bas URL as well as entering 'Reading Mode' in case it is available
NSString *sURL = #"http://google.com";
NSURL *URL = [NSURL URLWithString:sURL];
SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:URL]; // 1.
SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:URL entersReaderIfAvailable:YES]; // 2.
[self presentViewController:safari animated:YES completion:nil];