Link to pdf within app - iphone

I've got an iOS app that at one point opens a link to a website in a webview. These links are kept in a plist file (so it is easy to maintain them as the app evolves). What I want to do next is to also link to PDF's (or any picture of text file format, of even a html format, this is flexible) that are kept within the app. And I would like to do this as much as possible from within the existing app structure. So, is it possible to create a link that can be put in the plist as a web-link, but instead opens a file on the device itself (possibly in the webview)? And how would I go about that? Any ideas?
Thanx in advance for your help!

You will need to create the links at runtime. I would suggest having a certain prefix to a local url, such as mylocalfile:filename. Then, in the code that loads the plist, check for the prefix and create the link when necessary. You could also just create these links once and store them in a separate file, then load that instead of the original.
NSArray *links = nil; //I assumed your plist is an array. Change to dictionary if required
NSString *pathToStoredFile; //Get the path for the file you create with the updated links
if([[NSFileManager defaultManager] fileExistsAtPath:pathToStoredFile]) {
links = [NSArray arrayWithContentsOfFile:pathToStoredFile];
} else {
NSArray *tmp = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ListOfLinks" ofType:#"plist"]];
if(!tmp) {
//handle error
}
NSMutableArray *tmpLinks = [[NSMutableArray alloc] initWithCapacity:[tmp count]];
for(NSString *link in tmp) {
if([link hasPrefix:#"mylocalfile:"]) {
link = [link substringFromIndex:12]; //12 is the length of mylocalfile:
NSURL *url = [[NSBundle mainBundle] urlForResource:[link stringByDeletingPathExtension] withExtension:[link pathExtension]];
[tmpLinks addObject:[url absoluteString]];
} else [tmpLinks addObject:link];
}
links = [tmpLinks copy];
[tmpLinks release];
[links writeToFile:pathToStoredFile atomically:NO];
}

Yes, I would go with a UIWebView. iOS should be able automatically handle certain URL handlers and your app can register to handle the rest, as necessary.
iOS knows how to handle certain file types already. For example, if Safari (or a UIWebView) encounters http://somesite.com/afile.pdf, it know which apps can handle the file type. Another example is a phone number: skype://8005555555. iOS knows to open Skype and pass the number to it. iOS also knows that iBooks can handle PDf files.
Register your app for the appropriate file handlers and types. Then, users can tap and hold on the link to see a menu of available apps to handle the link. If it's a link that's only used by one app, the user doesn't even need to hold, a tap will suffice.
As far as making a link pointing to a local file, you can, and you would use the C function NSDocumentsDirectory() and append that to a url handler. (Example: http://NSDocumentsDirectory()/filename.pdf)

Related

Writing values to a .plist more than once?

I have an iPhone App in the works, and I have a settings page within the app. I use a .plist to store the settings that the user picks, and then I read the data from the .plist later. This is not the -Info.plist that comes with it when you create the project, it is another plist file. When I tested the settings after coding it, it worked. The setting was able to be read, and it was the correct setting that I used. However, I went back to the settings in the same app-session, and changed the same setting. When I went to the app to see if it read the new setting, it only used the old (first) one. I tried again, but it yielded the same result. I am only able to write the setting once, and I cannot 'rewrite' or 'overwrite' the same setting. I cannot figure out what is wrong for the life of me.
You can't overwrite or write to files in the app bundle itself. If you want to write to a file, you should first copy it to the app's Library or Documents directory, something like this:
NSString *docsDir = [NSSearchPathsForDirectoriesInDomains(NSDocumentsDirectory, NSUserDomainMask) objectAtIndex:0];
NSString *plistFile = #"myplist.plist";
NSString *plistInBundle = [[NSBundle mainBundle] pathForResource:plistFile ofType:nil];
NSString *plistInDocsDir = [docsDir stringByAppendingPathComponent:plistFile];
[[NSFileManager defaultManager] copyItemAtPath:plistInBundle toPath:plistInDocsDir error:NULL];
// now `plistInDocsDir` is (over)writeable
However, for storing preferences of your app, it's better practice to use the NSUserDefaults class:
[[NSUserDefaults standardUserDefaults] setObject:#"Some string" forKey:#"MySettingKey"];
and/or create a Preference Bundle for your app.

Display encrypted file using QuickLook framework or UiDocumentInteractionController

I have an encrypted word/excel/pdf file locally stored which I need to preview in my iPad app. I understand that QLPreviewController or UiDocumentInteractionController could be used to preview these files. I can very well use this
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index {
return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[documents objectAtIndex:index] ofType:nil]];
}
But the file is encrypted and when I decrypt it I would get hold of NSData object. How do I go about loading NSData in either of these.
Also I understand that I can very well store the NSData back as a local file and load it in Preview. But there is a constraint of not storing the unencrypted file locally.
If someone has already accomplished this and can help me out here it will be greatly appreciated.
Thanks
AJ
Since you are using Quick Look, your options are limited. You must give Quick Look an NSURL, which means it must be on the file system (or the Internet). Fortunately, this shouldn't be much of a problem. iOS devices use hardware-level encryption. When your file is encrypted, only your app has the key to decrypt it. So, your file will still be encrypted, but it will also be readable by your app and only your app.
Here's what you do:
Decrypt your file into an NSData object, which you've already done.
Write the file to a location that will not get uploaded to iCloud nor backed up by iTunes. The tmp directory is probably the best choice. The code looks something like this:
NSData * data = // Your decrypted file data.
NSString * fileName = // Whatever you want to name your file.
NSString * path = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
NSURL * url = [NSURL URLWithString:path];
NSError * error = nil;
BOOL success = [data writeToURL:url
options:NSDataWritingFileProtectionComplete
error:&error];
if (success) {
// Give the URL to Quick Look.
}
else {
// An error happened. See the 'error' object for the details.
}
At this point you have an NSURL which you can use with Quick Look. Don't forget to delete the decrypted file when you are done with it.
There are a few things to note about on-disk encryption:
It is only supported on iOS 4.0+.
It may not work on "older" devices.
The user must have an active passcode.
If you use NSDataWritingFileProtectionComplete, the file is not accessible while the device is locked. If you need to access the file while the app is locked, then you should use NSDataWritingFileProtectionCompleteUnlessOpen or NSFileProtectionCompleteUntilFirstUserAuthentication instead. This will still give you great protection, even if the device is stolen and jailbroken. Be aware, though, that these encryption options are only available on iOS 5.0+
For more details for on-disk encryption, check out the iOS App Programming Guide
After doing some digging, I found out that QLPreviewController is using UIWebView underneath, and calls the loadRequest: to load the requested file.
Another way to accomplish what you desire is to make a private Category on UIWebView,
and use method swizzling to override the loadRequest: method, and call instead the loadData:MIMEType:textEncodingName:baseURL: method.
Beware that:
1) In low-memory scenarios (i.e. large files) a black screen with
"Error to load the document" appears, if that concerns you. (The
unhacked QLPreviewController knows how to handle these scenarios
very well and present the document).
2) I'm not sure Apple are going
to approve this kind of hack, although no private APIs are used
here.
code:
#implementation UIWebView (QLHack)
- (void)MyloadRequest:(NSURLRequest *)request
{
// Check somehow that it's the call of your QLPreviewController
// If not, just call the original method.
if (!insideQLPreviewController)
{
// Call original implementation
[self MyloadRequest:request];
}
else
{
// Load the real data you want
[self loadData:data MIMEType:mimeType textEncodingName:nil baseURL:someURL];
}
}
+ (void)load
{
method_exchangeImplementations(class_getInstanceMethod(self, #selector(loadRequest:)), class_getInstanceMethod(self, #selector(MyloadRequest:)));
}
#end
Actually, writing a file to a tmp directory is still insecure. The other alternative is to use UIWebView with NSURLProtocol and allow decrypting this data on the fly.
One way could be.
use Temp Dir , Save File in Temp , Make NSURL From that Temp File and Display and then Delete that temp Dir after that.
Thanks.

Create documents folder iPhone SDK

I am developing an App that displays several pages of Text when the correct buttons are pressed. It is Static proprietary information. There is a different Text File for each of six buttons.
I am new to the ios SDK. Does creating a project in XCODE automatically create a Documents Folder? Is the "Documents Folder" what Apple is calling the "Sandbox"?
Can I simply write my Text, (that part which will display on the screen, LOTS of Text), drop it into the "Documents Folder", then display it in "scrolling mode" on the iPhone when a certain button is pressed?
I would prefer the Text to be part of the compile, since the information is proprietary, not simply a Text File, if there is a way to store and display large Text Files efficiently.
Yes Ken, the documents directory is there by default when you create an app, and yes, you can certainly write and read text data from it if you want to.
You cannot directly drop data into the Documents folder however, you need to do so programmatically.
Assume that one of your files are 'TextFile1.txt'. You should add this file to your project firstly, and somewhere in the appDelegate, write the following code;
NSString *fileBundlePath = [[NSBundle mainBundle] pathForResource:#"TextFile1" ofType:#"txt"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileDocumentDirectorySavePath = [documentsDirectory stringByAppendingPathComponent:#"TextFile1.txt"];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:fileDocumentDirectorySavePath])
[fm copyItemAtPath:fileBundlePath toPath:fileDocumentDirectorySavePath error:nil];
This will copy the TextFile1.txt into your apps Documents folder from which you can read it anytime you need with the following code;
// You can get the fileDocumentDirectorySavePath same way as in the above code
NSString *stringToDisplay = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:fileDocumentDirectorySavePath] encoding:NSUTF8StringEncoding];
NSLog(#"String : %#", stringToDisplay);
You can do this for any number of text files you need to work with.
If you don't want to change the text dynamically (i.e., only when you submit an update), just add the files directly to your Xcode project and don't even worry about the sandbox/Documents folder. You can drag the files into the sidebar in Xcode (creating a custom folder for it would be very organized of you) and check the "Copy files to project folder?" when asked. As is stated, they're now copied and part of the compiled app. Then you can query the files and display them in a UITextView, automatically supporting scrolling of text.
Alternatively, you could do what I think is the easier method and include the files directly in your code. In a class file that loads the text, in the .h file (Header), add a UITextView as a property and a variable. In the .m file (Implementation), do yourTextView = [[UITextView alloc] init];, then set yourTextView.text to an NSString containing your text. Sounds confusing, but it will be quicker and easier to update in the end. That is, unless your text is formatted... Anyway, you could also just create a UITextView in your XIB/NIB file and add your text directly.
I'd suggest you do it in code. That will be the easiest to change.
Adding text to your app is one thing - making it secure is more difficult. I had to deal with a similar issue and decided to use unformatted text that I include encrypted in my app and only decrpt the part that is being shown. Really depends how "secret" you want to keep the text. Remember, anyone can read it anyway and copy it right from the screen with a screenshot. Also unencrypted text in apps can be read and extracted quite easily using a HexEditor!
Alternatively you can prepare the text in *.txt (unformatted) or html (formatted as you like) file format and just include it in your app. However, this is the easies way for others to just copy the file.

Can't write on plist file after deploying on iPhone

Dear all.
I am facing a problem.
I can read and write on plist while working on simulator of xcode. but when I deploy the app in iPhone, i can't write on plists.
I have created a sample project having 2 button on it. By one button, I can display the text from plist. By second button, I try to write on that plist. But the writing doesn't happen. The app doesn't crash while clicking on the second button. I can't understand the problem in my code.
/*code is given below*/
-(void)writePlist:(NSString *)fname withArray:(NSMutableArray *) myArray
{
NSString * path = nil;
path = [(NSString *) [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fname];
[myArray writeToFile:path atomically:NO];
}
It could be that myArray variable contains non-serializable object values (probably custom objects or something like that).
Does the directory you are trying to write to exist? You may want to verify its existence (and create it if it isn't already there) before trying to write a file into it.

Loading a local HTML file in Navigation based application

I am new to iOS development.
I have been trying to find an answer to my question but I could not, I have found some related answers but I could not fix the problem.
My problem is, I have made an application which has a table, and for every row there should be a URL which loads a UIWebView, and should be loaded when you click on the row, but it loads an online URL such as #"http://www.google.com". But I want it to load a local HTML file.
I have used this for the URL:
azkarDetailViewController.detailURL=
[[NSURL alloc] initWithString:
[[[azkarData objectAtIndex:indexPath.section] objectAtIndex:
indexPath.row] objectForKey:#"url"]];
And for every row in the table I use:
[AZKAR addObject:[[NSMutableDictionary alloc] initWithObjectsAndKeys:#"THE NAME OF THE ROW",#"name",#"DayandNight.png",#"picture",#"http://www.google.com",#"url",nil]];
So I want the application to load a local file from the Resources (say for example the name of the HTML file is "index.html").
Can you please help me solving this problem ?
Thank you very much..
When you compile your app, the output is a “bundle” of files and directories. You can actually access the bundle through the NSBundle class; the static method +mainBundle will return a pointer to an instance of NSBundle that represents your app's main bundle.
You can then use the -URLForResource:withExtension: and -URLForResource:withExtension:subdirectory methods of NSBundle (these require iOS 4.0 or higher—there are older equivalents as well). If the HTML file is stored in your main bundle directory (that will be the case unless you created a an actual directory—different from a group—in your bundle), you can find its URL this way:
NSString *myDocumentName = #"index.html";
NSURL *documentURL = [[NSBundle mainBundle] URLForResource:myDocumentName extension:Nil];
Note that you do not have to specify the extension separately if it's already in the filename.
Note: my explanation is a little oversimplified (I'm assuming you don't need to deal with localizations, otherwise there are other issues you should be aware of explained in the docs).
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 40000
// code for iOS below 4.0
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"WindowsTest" ofType:#"html"]];
#else
// code for iOS 4.0 ++
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"WindowsTest" withExtension:#"html"];
#endif