Populating UITextView as it scrolls - objective-c

Is there anyway to load text from a file into a UITextView in parts yet keep scrolling smooth (Like loading text into a UITableView and splitting it up among multiple cells)?
For Example: I have a RTF (rich-text file) with over 120,000 words in it. Due to attributes and the amount of words, scrolling is choppy.
I want to try a free up memory. Instead of loading the entire text-file at once, I would like to load just enough text to fit on the screen and to make scrolling looks smooth.
I've seen this answer: Reading Specific Part Of A Text File that deals with reading specific parts of a file but my file isn't broken up by special characters in anyway.

Instead of using a UITextView and RTF, you can use a UIWebView and HTML (use a conversion tool to turn RTF into HTML). Here is how to hook it all up and change the text size. You can call the resizeWebViewToSize at any time to resize the text.
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:#"HtmlFile" ofType:#"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
[self.webView loadHTMLString:htmlString baseURL: [[NSBundle mainBundle] bundleURL]];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[self resizeWebViewToSize:#"1000"];
}
- (void)resizeWebViewToSize:(NSString *)size
{
NSString *fontSize=size;
NSString *jsString = [[NSString alloc] initWithFormat:#"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'",[fontSize intValue]];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
}

Related

Populate UICollectionView Images From Document Directory AND Update Custom NSMutableArray object?

In my data controller I have book objects and each object contains these properties (among others):
novel.title = #"Book One";
novel.imageArray = [[NSMutableArray alloc] initWithObjects: [UIImage imageNamed: #"book1image1"], nil];
In the app, users can add a book image using UIImagePickerController like this:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(nonnull NSDictionary<NSString *,id> *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
_addedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
_addedImage = [self scaleImage:_addedImage toSize:CGSizeMake(120, 168)];
[_book.imageArray addObject:_addedImage];
NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:_book.imageArrayID];//folder name
NSError *error = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:stringPath])
[[NSFileManager defaultManager] createDirectoryAtPath:stringPath withIntermediateDirectories:NO attributes:nil error:&error];
int i = 0;
for(_addedImage in _book.imageArray)
{
_book.bookAddedToArray = YES;
NSString *fileName = [stringPath stringByAppendingFormat:#"/image%i.jpg", i++];//image name
NSData *data = UIImageJPEGRepresentation(_addedImage, 1.0);
[data writeToFile:fileName atomically:YES];
}
[self.collectionView reloadData];
}
This works perfectly fine. All images are saved in the right place as expected.
As long as the app stays open, the new images can be viewed in the collectionView. You can navigate anywhere in the app, come back, and view the collectionView some more. When the app is completely exited out of and then reopened, the collectionView is reset and only shows the initial image that was set in the dataController (no matter what code I've implemented so far. It just always resets). All the user generated images are still in their respective folders in the documents directory but I cannot seem to update the cellForItemAtIndexPath: with the stored images presumably because the images are not being saved to the _book.imageArray. Right now I have the cell being populated by the default imagearray so, of course, that is what will show up. How do I update the array and pull from documents directory to show user images?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if(_book.bookAddedToArray)
{
//*** HOW TO UPDATE _BOOK.IMAGEARRAY ????
cell.bookImageView.image = [_book.imageArray objectAtIndex:indexPath.row];
}
else
{
cell.bookImageView.image = [_book.imageArray objectAtIndex:indexPath.row];
}
return cell;
}
I've read that you can store an array of directory paths in nsuserdefaults and retrieve it to populate an imagearray but I have yet to find a solution that works for me. None of the answers seem to address loading into a collectionview or tableview. Can anyone point me in the right direction? Please? Anyone? Lol. Let me know if you need to see more code. All suggestions are much appreciated!
Eventually the app will need to be rewritten as it has grown beyond the scope of my original code, but at this time, this is what I'm working with. I'll probably eventually use CoreData but I've even seen where people on S.O. recommended against storing images in CoreData and to use the documents directory. That still leaves me in this same situation then.
Yes! I got it figured out and it works like a charm. It seems that I could not actually update my custom NSMutableArray. I could make it read another array but could not actually change the array itself. (I'm referring to the _book.imageArray.)
Everything stayed the same in the didFinishPickingMediaWithInfo.
I didn't need to add anything to the cellForItemAtIndexPath. It is just simply:
if(_book.bookAddedToArray)
{
cell.bookImageView.image = [_book.imageArray objectAtIndex: indexPath.row];
}
I created a property for my new array:
#property (nonatomic, strong) NSMutableArray *imagePathArray;
All the magic happens in ViewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
//initialize new array here
_imagePathArray = [[NSMutableArray alloc] init];
if (_book.bookAddedToArray)
{
int i = 0;
//this is the path to the folder.
NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:_book.imageArrayID];
int z;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:stringPath error:NULL];
for (z = 0; z < (int)[directoryContent count]; z++)
{
//this is the path to each image file
NSString *fileName = [stringPath stringByAppendingFormat:#"/image%i.jpg", i++];
NSLog(#"imagefile = %#", fileName);
UIImage *fileNameImage = [UIImage imageWithContentsOfFile:fileName];
//add each image to a new array
[_imagePathArray addObject:fileNameImage];
}
//set my array to the new array
_book.imageArray = _imagePathArray;
}
}
I can thank this post for helping to read the images within each folder:
go to link.
//these are all properties in my Data Class.
_book.imageArrayID
_book.bookAddedToArray
_book.imageArray
I hope this helps someone out!

UIImageView shows image on simulator but not on iPhone

I've been getting a very weird bug with one of my UIImageViews. I have a UITableView that lists items, and each cell is an item. Each item cell has a thumbnail image, a title, and a subtitle. I am able to show the thumbnail in every cell perfectly fine on the simulator and on the iPhone.
I also have a detail VC that, when a certain cell gets tapped, it segue's to that VC and shows the large image, along with some more info about the item. I've been able to load the image up in the simulator, but on the iPhone, all I get is a blank UIImageView.
This is how it looks in the simulator.
And my detail VC:
The blue view at the bottom is where I will add labels to show more details about the item, and the orange is a UIImageView that will show a custom image just saying 'item details'. One can tap on that, or my big UImageView and it will animate up (and down to dismiss).
Now, the problem is - when I segue from my table VC to my detail VC, the UIImageView is just blank on the iPhone. It works perfectly fine on the simulator, though, so I'm passing the right image and such.
Here is the code to segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showItemDetails"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
ItemDetailViewController *itemDetailVC = segue.destinationViewController;
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
if(item)
{
itemDetailVC.pictureImage = [UIImage imageWithContentsOfFile:item.photoURL];
}
}
}
My items gets saved using Core Data. photoURL is the URL of the photo in the documents directory of my app, as I do not save the entire image to Core Data, just a reference to it for performance purposes.
Here is part of the code that gets data from another VC in an "unwind" segue.
- (IBAction)saveItem:(UIStoryboardSegue *)segue
{
ManageItemViewController *manageItemVC = (ManageItemViewController *)segue.sourceViewController;
NSMutableDictionary *itemInfo = [[NSMutableDictionary alloc] init];
...
// write the original photo to a file and keep an URL to it that we pass to Core Data
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(manageItemVC.pictureImage)];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
NSString *fileName = [NSString stringWithFormat:#"%lf.png", [[NSDate date] timeIntervalSince1970]];
NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName]; //Add the file name
[imageData writeToFile:filePath atomically:YES]; //Write the file
[itemInfo setObject:filePath forKey:CD_ITEM_PHOTOURL_PROPERTY];
...
[self saveItemToCoreData:[itemInfo copy]];
}
In my detail VC, this is all I do to load the UIImage I send to the big UIImageView.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(self.pictureImage) {
[self.pictureImageView setImage:self.pictureImage];
}
}
And when the user delete an item (delete row), I also remove the file from my documents directory, as such (this code is in my table VC):
- (void)deleteItemImageFromDevice:(NSString *)filePath
{
if(filePath)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
if([fileManager removeItemAtPath:filePath error:&error]) {
NSLog(#"Image deleted from documents directory!");
} else {
NSLog(#"%#", error);
}
}
}
I logged this and I do not get any errors in the simulator.
Anyone know what the issue is here? Thanks.
I've managed to fix this issue.
For future reference, here are the fixes I made to my code.
Instead of a time interval, I now get a guaranteed unique string (no need for long float conversions) using NSProcessInfo, as so:
NSString *fileName = [NSString stringWithFormat:#"%#.png", [[NSProcessInfo processInfo] globallyUniqueString]];
Then, in my prepareForSegue: method, I get an UIImage from data that exists in our documents folder (a file). The item.photoURL is our file path.
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
if(item.photoURL)
{
itemDetailVC.pictureImage = [UIImage imageWithData:[NSData dataWithContentsOfFile:item.photoURL]];
}
Lastly, in our detail VC header file, our UIImage (not UIImageView!) needs to be strong reference, not a weak one, as we need to access it even before the VC is on screen:
#property (nonatomic, strong) UIImage *pictureImage;
The UIImageView we assign this UIImage to can be weak because it only needs to be shown when the view is on screen, and that holds a strong pointer to all of its subviews. For an excellent analogy of strong vs weak, check this.

Cocoa PDF page splitting

In the application I'm creating, I load a long page of HTML into a webView and then print it to a PDF using the following:
-(void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
if ([frame isEqual:[[self doc] mainFrame]])
{
NSMutableData *newData = [[NSMutableData alloc] init];
NSPrintInfo *newInfo = [NSPrintInfo sharedPrintInfo];
NSView *docView = [[[[self doc] mainFrame] frameView] documentView];
NSPrintOperation *newPrintOp = [NSPrintOperation PDFOperationWithView:docView insideRect:docView.bounds toData:newData printInfo:newInfo];
BOOL runPrint = [newPrintOp runOperation];
if (!runPrint)
{
NSLog(#"Print Failed");
}
PDFDocument *newDoc = [[PDFDocument alloc] initWithData:newData];
[newData release];
[self setPdf:newDoc];
//Other code here
}
}
The problem is that when I look at newDoc, it is a huge PDF of a single page. What I would prefer would be the printing acting the same as it does from the "save as PDF..." dialog - that is, splitting the PDF into multiple reasonably-sized pages.
Does anyone know how to accomplish this?
I attempted inserting the following after NSPrintInfo *newInfo = [NSPrintInfo sharedPrintInfo];
[newInfo setVerticalPagination:NSAutoPagination];
[newInfo setHorizontalPagination:NSAutoPagination];
NSAutoPagination is described in the docs as the following:
NSAutoPagination
The image is divided into equal-sized rectangles and placed in one column of pages.
Available in Mac OS X v10.0 and later.
Declared in NSPrintInfo.h.
This had no effect on the printed PDF.
You get a file with one large page because + PDFOperationWithView: method doesn't support pagination at all. For that reason calling - setVerticalPagination: or - setHoriziontalPagination: doesn't change anything.
You could try use "classical" + printOperationWithView:printInfo: method, configure it to save PDF to temporary location and then create PDFDocument with contents of obtained file. I hope that fragment of code below will help.
NSMutableDictionary *dict = [[NSPrintInfo sharedPrintInfo] dictionary];
[dict setObject:NSPrintSaveJob forKey:NSPrintJobDisposition];
[dict setObject:temporaryFilePath forKey:NSPrintSavePath];
NSPrintInfo *pi = [[NSPrintInfo alloc] initWithDictionary:dict];
[pi setHorizontalPagination:NSAutoPagination];
[pi setVerticalPagination:NSAutoPagination];
NSPrintOperation *op = [NSPrintOperation printOperationWithView:[[[webView mainFrame] frameView] documentView] printInfo:pi];
[pi release];
[op setShowsPrintPanel:NO];
[op setShowsProgressPanel:NO];
if ([op runOperation] ){
PDFDocument *doc = [[[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath: temporaryFilePath]] autorelease];
// do with doc what you want, remove file, etc.
}

Print Local HTML using Cocoa

I have a simple list of items that needs to be printed using Cocoa. I have a half-baked solution that uses an NSView with a custom drawRect: method, but it's fairly complex and not very easy to maintain.
What I would like to have is an HTML string (which could be easily constructed from the list) that can be embedded in a one-off WebView, then printed.
Assuming I have a simple NSString like:
NSString *htmlString = #"<b>Test</b>";
What's the easiest method for creating a WebView displaying this content? I've tried the below code, but it results in a single blank page:
WebView *webView = [[WebView alloc] init];
NSString *dir = #"/Users/Me/Desktop/";
NSString *fileUrl = [dir stringByAppendingPathComponent:#"Temp_Print.html"];
NSString *htmlString = #"<b>Hi!</b>";
[[htmlString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:fileUrl atomically:YES];
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:fileUrl]]];
[webView setFrame:NSMakeRect(0, 0, 500, 500)];
NSPrintOperation *po = [NSPrintOperation printOperationWithView:webView printInfo:pi];
[pi release];
[po runOperation];
Another one of those questions you solve right after asking it!
The run loop needs to iterate in order for the content to actually load. I simply finished running the actual print operation in the frame load delegate method:
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
...
}
Source

Can you set the tableView datasource values in "connectionDidFinishLoading"?

I'm working on my first JSON example in objective-c and came across this great tutorial that I'm trying to reproduce. Along the way I decided to push the JSON returned into my already working tableView (just to ensure I could do something w/ the data in the view).
- (void)viewDidLoad {
[super viewDidLoad];
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.unpossible.com/misc/lucky_numbers.json"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSArray *luckyNumbers = [responseString JSONValue];
NSMutableString *text = [NSMutableString stringWithString:#"Nums "];
for (int i = 0; i < [luckyNumbers count]; i++)
[text appendFormat:#"%#", [luckyNumbers objectAtIndex:i]];
self.movies = [[NSArray alloc] initWithObjects:#"First", text, #"Last", nil];
}
What I've found is that when I set the array in "connectionDidFinishLoading" it shows up as nothing in the running application - yet if I set this directly in the "viewDidLoad" method with 3 simple string values it shows up fine.
When I debug the running application I see the JSON response and the string looks valid (no issues that I can see).
Is the datasource for my tableView already set in stone before this "connectionDidFinishLoading" method or did I miss something?
Your UITableView will call upon its DataSource for data once initially, presumably sometime after viewDidLoad. After that first load, it will only request data as it needs it (i.e. as you scroll to different cells.) If you want to make it refresh its contents when your data is ready (like after you've received your URL data), call [tableView reloadData].
My initial question was solved by this solution:
At the end of my "connectionDidFinishLoading" method I call a method on the appDelegate called "jsonFinished".
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//do all the json work and set the array that I'm using as my datasource
self.movies = [[NSArray alloc] initWithObjects:#"First", "Last", nil];
[appDelegate jsonFinished]; //have the app delegate do the refresh call back
}
Then inside the appDelegate I simply provide an implementation for the "jsonFinished" method that does a refresh of the UITableView
- (void)jsonFinished
{
moviesController.refreshDisplay;
}
And in the "refreshDisplay" method I do the reloadData on the tableView
- (void)refreshDisplay
{
[moviesTableView reloadData];
}
And now after the data is loaded the appDelegate fires off the method that reloads the data for tableView