PDFView doesn't update when adding PDFPage to the PDFDocument - objective-c

I'm making an application to scan multiple page pdf files. I have a PDFView and a PDFThumbnailView that are linked. The first time a scan is completed, I create a new PDFDocument and set it to PDFView. Then whenever another scan is completed I add a PDFPage to [pdfView document].
Now the problem is whenever a page is added, neither the PDFView or PDFThumbnailView update to show the new document with the extra page. That is until I zoom in or out, then they both update to show the document with the new page.
The temporary solution I have now (zoom in and then autoscale) is certainly not the best one. Take for example when you have already zoomed in on the document, and you scan a new page, the view will then autoscale. I tried [pdfView setNeedsDisplay:YES] before but that doesn't seem to work.
This is the method where the scan arrives as NSData:
- (void)scannerDevice:(ICScannerDevice *)scanner didScanToURL:(NSURL *)url data:(NSData *)data {
//Hide the progress bar
[progressIndicator stopAnimation:nil];
//Create a pdf page from the data
NSImage *image = [[NSImage alloc] initWithData:data];
PDFPage *page = [[PDFPage alloc] initWithImage:image];
//If the pdf view has a document
if ([pdfView document]) {
//Set the page number and add it to the document
[page setValue:[NSString stringWithFormat:#"%d", [[pdfView document] pageCount] + 1] forKey:#"label"];
[[pdfView document] insertPage:page atIndex:[[pdfView document] pageCount]];
} else {
//Create a new document and add the page
[page setValue:#"1" forKey:#"label"];
PDFDocument *document = [[PDFDocument alloc] init];
[document insertPage:page atIndex:0];
[pdfView setDocument:document];
}
//Force a redraw for the pdf view so the pages are shown properly
[pdfView zoomIn:self];
[pdfView setAutoScales:YES];
}
Does anyone know of a way where I can add a PDFPage and have the PDFView update without messing with the zoom state of the PDFView?

You need to manually call:
- (void)layoutDocumentView
I imagine the reason that this is not called manually is to allow coalescing of multiple changes into one update.
This is documented:
The PDFView actually contains several subviews, such as the document
view (where the PDF is actually drawn) and a “matte view” (which may
appear as a gray area around the PDF content, depending on the
scaling). Changes to the PDF content may require changes to these
inner views, so you must call this method explicitly if you use PDF
Kit utility classes to add or remove a page, rotate a page, or perform
other operations affecting visible layout.
This method is called automatically from PDFView methods that affect
the visible layout (such as setDocument:, setDisplayBox: or zoomIn:).

The best solution I got so far to refresh manually the PDFView with annotation was to move the PDFView to another page and come back to the page you need to refresh because:
- (void)layoutDocumentView didn't work for me in my case.
Then:
[self.pdfView goToLastPage:nil];
[self.pdfView goToPage:destinationPage];

Related

How can I change page orientation within a multi page NSPrintOperation?

Let's say that I have a three page report that I want to print out, first page is portrait orientation, second page is landscape, and third page is again portrait.
I've subclassed a special NSView to be my printing operation, but what I get is three pages, all oriented portrait, cutting off the landscape orientation. I have been able to work around it, by separating each of the pages into a separate print job, but that's not acceptable, because it ignores the settings from the
NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView: printView printInfo: printInfo];
[printView setCurrentForm: [formSet objectAtIndex: 0]];
[printOperation setShowsPrintPanel:YES];
[printOperation runOperationModalForWindow: [self window] delegate: nil didRunSelector: nil contextInfo: nil];
[printOperation cleanUpOperation];
[NSPrintOperation setCurrentOperation: nil];
call from the first page (if, for example, they selected "PDF Open in Preview" from that dialog, I want the three pages to be joined as one job, which opens as a PDF in Preview.)
What do I need to do in the code that has the printer version of the NSView rendered, to force the NSPrintOperation to change the orientation?
Set the orientation in rectForPage:
NSPrintOperation.currentOperation.printInfo.orientation = NSPaperOrientationLandscape;
or
NSPrintOperation.currentOperation.printInfo.orientation = NSPaperOrientationPortrait;

PDF not shown correctly

In my app I use a window with a single PDF view for showing some information. When the window is shown first the display is fine:
But when showing it once again the PDF inside is shifted:
The code for opening the window is this:
- (IBAction)viewClicked:(id)sender {
NSData* data = [NSData dataWithContentsOfFile:#"my.pdf"];
pdf = [[PDFDocument alloc] initWithData:data];
[_pdfWindow makeKeyAndOrderFront:self];
_pdfView.document = pdf;
}
This is called right after the app starts. The window is closed with the red close button. Right after that the above viewClicked is repeated and shows the shifted picture.
The same happens in Swift:
#IBAction func viewClicked(_ sender: Any) {
let data = NSData(contentsOfFile:"my.pdf")
pdf = PDFDocument(data:data as! Data)
pdfWindow.makeKeyAndOrderFront(self)
pdfView.document = pdf
}
Edit One further observation: cursor right, which is propagated to the PDFView directly, when issued from the wrong display results in this:
which is only the 2nd page of the PDF.
Edit2 If anyone is interested, I attach the ObjC/Swift XCode projects. You just need to adapt the PDF file location in AppDelegate. Dropbox
Yeah, PDFView is annoying like that. set the PDFView's document to nil before assigning it a new document:
_pdfView.document = nil;
_pdfView.document = pdf;
This will reset it and solve your problem.
Also, you don't need to set needsDisplay on the window's view.

How to display tableview images as soon as they are downloaded rather than after reloading cells

I have a table view that displays a list of users. The avatar images in each cell are downloaded asynchronously using UIImageView+AFNetworking and displayed using UIImage+TPAdditions. Here is my code snippet, from cellForRowAtIndexPath:
if (cell==nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SimpleTableIdentifier];
}
cell.textLabel.text = [[global.people objectAtIndex:indexPath.row]name];
// setImageWithUrl:placeholderImage: is taken from UIImageView+AFNetworking
NSString* imgURL = [[global.people objectAtIndex:indexPath.row]avatar_url];
UIImageView* imgV = [[UIImageView alloc]init];
[imgV setImageWithURL:[NSURL URLWithString:imgURL] placeholderImage:[UIImage imageNamed:#"icon.png"]];
//These are just for formatting and are taken from UIImage+TPAdditions
cell.imageView.image = [imgV.image imageScaledToSize:CGSizeMake(43,43)];
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 5.0;
Now when the app loads we just see the placeholder images until the user scrolls the tableview up and down. Then the avatar images from the URLs are loaded into each cell's image view. I want to make it so that this scrolling is not required- I want the avatar images to "pop" into their respective cells' tableviews as soon as they are downloaded. I know NSNotification might help me, but I'm not really sure where/how to use that. I'm fairly new to iOS. Can anyone walk me through it?
Thanks
You'll want to call setNeedsDisplay on your view to force a refresh. You would call this in your asynchronous call's completion handler.

Another IKImageView Question: copying a region

I'm trying to use the select and copy feature of the IKImageView. If all you want to do is have an app with an image, select a portion and copy it to the clipboard, it's easy. You set the copy menu pick to the first responder's copy:(id) method and magically everything works.
However, if you want something more complicated, like you want to copy as part of some other operation, I can't seem to find the method to do this.
IKImageView doesn't seem to have a copy method, it doesn't seem to have a method that will even tell you the selected rectangle!
I have gone through Hillegass' book, so I understand how the clipboard works, just not how to get the portion of the image out of the view...
Now, I'm starting to think that I made a mistake in basing my project on IKImageView, but it's what Preview is built on (or so I've read), so I figured it had to be stable... and anyway, now it's too late, I'm too deep in this to start over...
So, other than not using IKImageView, any suggestions on how to copy the select region to the clipboard manually?
EDIT actually, I have found the copy(id) method, but when I call it, I get
<Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 16 bits/pixel; 1-component color space; kCGImageAlphaPremultipliedLast; 2624 bytes/row.
Which obviously doesn't happen when I do a normal copy through the first-responder... I understand the error message, but I'm not sure where it's getting those parameters from...
Is there any way to trace through this and see how this is happening? A debugger won't help for obvious reasons, as well as the fact that I'm doing this in Mozilla, so a debugger isn't an option anyway...
EDIT 2 It occurs to me that the copy:(id) method I found may be copying the VIEW rather than copying a chunk of the image to the clipboard, which is what I need.
The reason I thought it was the clipboard copy is that in another project, where I'm copying from an IKImageView to the clipboard straight from the edit menu, it just sends a copy:(id) to the firstResponder, but I'm not actually sure what the firstresponder does with it...
EDIT 3 It appears that the CGBitmapContextCreate error is coming from [imageView image] which, oddly enough, IS a documented method.
It's possible that this is happening because I'm putting the image in there with a setImage:(id) method, passing it an NSImage*... Is there some other, more clever way of getting an NSImage into an IKImageView?
The -copy: method in IKImageView does what every other -copy: method does: it copies the current selection to the clipboard. It is, however, implemented as a private method in IKImageView for some reason.
You can just call it directly:
[imageView copy:nil];
This will copy whatever is currently selected to the clipboard.
I don't think there's a way to directly access the image content of the current selection in IKImageView using public methods, this is a good candidate for a bug report/feature request.
You can, however, use the private method -selectionRect to get a CGRect of the current selection and use that to extract the selected portion of the image:
//stop the compiler from complaining when we call a private method
#interface IKImageView (CompilerSTFU)
- (CGRect)selectionRect
#end
#implementation YourController
//imageView is an IBOutlet connected to your IKImageView
- (NSImage*)selectedImage
{
//get the current selection
CGRect selection = [imageView selectionRect];
//get the portion of the image that the selection defines
CGImageRef selectedImage = CGImageCreateWithImageInRect([imageView image],(CGRect)selection);
//convert it to an NSBitmapImageRep
NSBitmapImageRep* bitmap = [[[NSBitmapImageRep alloc] initWithCGImage:selectedImage] autorelease];
CGImageRelease(selectedImage);
//create an image from the bitmap data
NSImage* image = [[[NSImage alloc] initWithData:[bitmap TIFFRepresentation]] autorelease];
//in 10.6 you can skip converting to an NSBitmapImageRep by doing this:
//NSImage* image = [[NSImage alloc] initWithCGImage:selectedImage size:NSZeroSize];
return image;
}
#end
Ok, so the copy: nil fails, and the [imageView image] fails, but it turns out that I have another copy of the NSImage from when I added it into the view in the first place, so I could that. Also, CGImageCreateWithImageInRect expects a CGImageRef not an NSImage*, so I had to do some conversions.
In addition, for some reason the selection rectangle is flipped, either it's bottom origined, and the image is top, or the other way around, so I had to flip it.
And for some reason, the compiler suddenly started complaining that NSRect isn't the same type as CGRect (Which implies that it suddenly went from 32 to 64 bit or something... not sure why...)
Anyway, here is my copy of selectedImage:
- (NSImage*)selectedImage
{
//get the current selection
CGRect selection = flipCGRect(imageView, [imageView selectionRect]);
//get the portion of the image that the selection defines
struct CGImage * full = [[doc currentImage] CGImageForProposedRect: NULL context: NULL hints: NULL];
CGImageRef selectedImage = CGImageCreateWithImageInRect( full, selection);
//convert it to an NSBitmapImageRep
NSBitmapImageRep* bitmap = [[[NSBitmapImageRep alloc] initWithCGImage:selectedImage] autorelease];
CGImageRelease(selectedImage);
// //create an image from the bitmap data
NSImage* image = [[[NSImage alloc] initWithData:[bitmap TIFFRepresentation]] autorelease];
// //in 10.6 you can skip converting to an NSBitmapImageRep by doing this:
//NSImage* image = [[NSImage alloc] initWithCGImage:selectedImage size:NSZeroSize];
return image;
}
I wrote flipCGRect, and [doc currentImage] returns an NSImage*...

Printing Off-screen PDFViews

I have a situation where I want to print a multi-page PDF. While I could use the PDFKit utility classes and/or quartz functions to get the information to manually write drawing/pagination code for a NSView subclass, I had thought that quicker alternative would be to create an off-screen PDFView and tell it to print itself. When I tried this solution, the print dialog didn't go away, all of the print settings controls on the right half of the print dialog disappeared, and the application froze.
I then wrote a tiny test application with the following method that illustrates the problem. When the test program is compiled without the USE_PDF_VIEW preprocessor macro defined, the blank view displays fine. If USE_PDF_VIEW is defined, the document doesn't print, most of the print dialog controls disappear, and the app freezes. While I have other ways of accomplishing my goal, I'm curious as to why this shortcut doesn't work. Is there something about Cocoa drawing I still don't understand? Am I banging into Apple Voodoo Magic(tm) behind the scenes that makes PDFView behave in a completely different way than other NSViews?
- (void)printMyStuff:(id)sender {
NSPrintInfo *currInfo = [NSPrintInfo sharedPrintInfo];
#ifdef USE_PDF_VIEW
PDFView *pdfView = [[PDFView alloc] init];
PDFDocument *pdfDoc = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:#"/Users/wls/Documents/my_document.pdf"]];
[pdfView setDocument: pdfDoc];
[pdfView printWithInfo:currInfo autoRotate:YES];
#else
NSView *myView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 500, 500)];
NSPrintOperation *myop = [NSPrintOperation printOperationWithView:myView printInfo:currInfo];
[myop runOperation];
#endif
}
Had the exact same problem.
The PDFView needs to be added to a NSWindow in order for printWithInfo:autoRotate: to work (atleast in my case), otherwise the printing controls go blank or won't work.
Here's the complete code:
PDFView *vDoc = [[PDFView alloc] init];
[vDoc setDocument:pdfDoc];
[vDoc setAutoScales: YES];
[vDoc setDisplaysPageBreaks: NO];
NSWindow *wnd = [[NSWindow alloc] init];
[wnd setContentSize:vDoc.frame.size];
[wnd setContentView:vDoc];
[vDoc printWithInfo:printInfo autoRotate:YES];
[wnd release];
[vDoc release];
Building on alex-i's excellent answer, I added the following lines so the print dialog showed up in a user-friendly location:
NSRect windowRect = self.window.frame;
NSPoint printTopLeftPoint = NSMakePoint(CGRectGetMidX(windowRect), CGRectGetMaxY(windowRect));
[wnd setFrameTopLeftPoint:printTopLeftPoint];
My self.window is for my current window controller, not the temporary window.
I like alex-i's answer because it does not use private APIs. But in my case, I already have a window (and I suppose in most cases you would!), so I figured I would use that window instead of creating one. Here is what I ended up doing, using swift:
func print(_ pdfDocument: PDFDocument, using window: NSWindow) {
// create a hidden pdf view with the document
let pdfView = PDFView()
pdfView.document = pdfDocument
pdfView.autoScales = true
pdfView.displaysPageBreaks = false
pdfView.frame = NSMakeRect(0.0, 0.0, 50.0, 50.0)
pdfView.isHidden = true
// add the view to the window and print
window.contentView.addSubview(pdfView)
pdfView.print(nil)
pdfView.removeFromSuperview()
}
PDFView is a subclass of NSView. The designated initializer for NSView is -initWithFrame: ... if you don't use -initWithFrame: strange things can happen. Since PDFView has no other designated initializers, -initWithFrame: is it. I'm guessing that's at least part of your problem.
Another part may be memory related. Are you using garbage collection or not? If you are, you're not keeping a reference to your PDFView anywhere, so may be getting deallocated. If you aren't using garbage collection, you're leaking your PDFView (also because you keep no reference to it, so you can release it when you're done). Same with your myView NSView instance ... you're leaking it if you're not using GC.