PDF not shown correctly - objective-c

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.

Related

NSDocument-based app, open dialog with 'New Document' button on launch

TextEdit and Pixelmator both open an open-file-dialog on launch that has a 'New Document' button in the bottom left corner.
Is there any particular code available to achieve the very same effect?
The reason I ask is that Pixelmator's way of going about it, the 'New Document' button included, is exactly the same as TextEdit, and I figure there must be some simple way to achieve this but I must be missing it.
Turns out you need to enable iCloud.
I found that adding an empty accessoryView to the openPanel also makes the "New Document" button visible. To do this, in your NSDocumentController descendant override beginPanel:forTypes:completionHandler: for example like below
- (void)beginOpenPanel:(NSOpenPanel*)openPanel forTypes:(NSArray<NSString*>*)inTypes completionHandler:(void(^)(NSInteger result))completionHandler {
// adding any accessory view makes the "New Document" button visible.
NSView* workaround = [[NSView alloc] initWithFrame:NSZeroRect];
openPanel.accessoryView = workaround;
// for non-arc: [workaround release];
[super beginOpenPanel:openPanel forTypes:inTypes completionHandler:^(NSInteger result) {
openPanel.accessoryView = nil;
completionHandler(result);
}];
}
You'll get warnings like this in the log:
[Layout] The Open/Save panel was supplied an accessory view with bad layout constraints, resulting in a view that is zero width. ...
I have not found a way to avoid that, but at least the New Document button is visible and working OK.

PDFView doesn't update when adding PDFPage to the PDFDocument

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];

NSDocument based application and NSToolbar

I have application based on NSDocument (NSPersistentDocument), in application I can create (as usually) more than one document.
Main document window (based on NSPersistentDocument) has, added IB, toolbar. In code I add to toolbar item (NSToolbarItems) using methods insertItemWithItemIdentifier and - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag.
Code samples:
[_toolbar insertItemWithItemIdentifier:#"addTape" atIndex:2];
and
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {
NSToolbarItem *item =nil;
if ([itemIdentifier isEqual:#"addTape"]) {
item = [[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier];
item.label = NSLocalizedString(#"Add Tape",#"Add Tape");
item.paletteLabel = NSLocalizedString(#"Add Tape",#"Add Tape");
item.toolTip = NSLocalizedString(#"Adds new tape",#"Adds new tape");
item.image = [NSImage imageNamed:#"NSAddTemplate"];
item.target = self;
item.action = #selector(addTape:);
item.tag = 101;
}
}
Everything is correct until I have opened only one document. When I open second document (or create new document) on first document window toolbar items are doubled (after opening third document, items are tripled on first window and doubled on second, and so on).
Edit: I noticed, that itemForItemIdentifier is called on each window, everytime I try to add toolbar item. In example: if I have two windows (two opened document) and on one I try to add one button itemForItemIdentifier is called two times.
It is strange to me, because every document has own toolbar with delegate set only to this document.
I don't have any idea what I have done wrong. Maybe someone will point me where I made a mistake.
You don't typically insert toolbar items yourself; implement the delegate methods toolbarAllowedItemIdentifiers: and toolbarDefaultItemIdentifiers: and the toolbar will be initialized according to those lists.
I found solution: I cannot use tooolbar created from nib, because each created this method toolbar has this same identifier. When I created toolbars in code, using different identifiers, the problem has gone.

How to make a print dialog with preview for printing an image file

I have a mac cocoa image editing application. I would like to add print image functionality to my application. I basically always have a jpg/png file with me. Whenever the user goes to File -> Print menu I would like to give user a dialog with print options/settings to print this jpg/png file. I would like the print dialog to be customized for image printing such as one we get when printing an image from Preview application.
I don't want to use something like the following because this will give a plain not very customizable print dialog:
[NSPrintOperation printOperationWithView:viewToPrint printInfo:[self.document printInfo]]
Well, you can put everything inside a NSView and then use the print method. This method will print everything that's inside the view (including subviews). Now for the customizable printing you can make something like: User clicks a button labeled "print", it makes your custom made dialog appear where the user can choose options such as, show exemple date (which is in a subview, of the view that's going to be printed), you store those options and them when the user clicks the print at your dialog, you can remove the subview he didn't wanted to show. And after its printed, show it again. There's also other methods for printing views here: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html
Please use this code.
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
NSImage *pic = [[NSImage alloc] initWithContentsOfFile: #"/Users/Anne/Desktop/Sample.png"];
NSRect picRect = NSRectFromCGRect(CGRectMake(0, 0, pic.size.width, pic.size.height));
NSImageView *imageView = [[NSImageView alloc] initWithFrame:picRect];
[imageView setImage:pic];
NSPrintOperation * picPrint = [NSPrintOperation printOperationWithView:imageView printInfo:printInfo];
[picPrint setCanSpawnSeparateThread:YES];
[picPrint runOperation];

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*...