NSPrintOperation hangs application - objective-c

I have an application that hangs whenever I call NSPrintOperation.
I have a view that is creates a separate class (UIView) like this:
PBPrintImage *printImage = [[PBPrintImage alloc] init];
printImage.image = finalImage;
[printImage printWithNoPanel:self];
Then inside PBPrintImage I have the following method:
- (void)printWithNoPanel:(id)sender {
CGSize picSize = CGSizeMake(300, 446);
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
NSRect imageRect = NSRectFromCGRect(CGRectMake(0, 0, picSize.width, picSize.height));
NSImageView *imageView = [[NSImageView alloc] initWithFrame:imageRect];
[imageView setImage:image];
NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView printInfo:printInfo];
[op setCanSpawnSeparateThread:YES];
[op setShowsPrintPanel:NO];
[op runOperation];
}
If I don't call it the application works as suspected. And I've tried calling it with and without setCanSpawnSeparateThread:. How do I set it up so it has to be in a separate thread and therefore doesn't mess up the regular flow of the application?
It does print, but that is only half of the job.

The application should show a modal print dialog (and start a modal run loop), so I would not call it "hanged". It returns to the normal main thread flow as soon as you hit Ok or Cancel.
As for the setCanSpawnSeparateThread: issue, it only kicks in when the print dialog is displayed as a sheet, so you need to call it like this: `[op runOperationModalForWindow:window delegate:self didRunSelector:#selector(_printOperationDidRun:success:contextInfo:) contextInfo:nil]

Related

Stopping performSelectorInBackground

In objective-c, when a button is pressed, I load a processing animation while a file is uploaded using:
[self performSelectorInBackground:#selector(loadAnimation) withObject:self];
This works and a loadAnimation image displays.
How do I stop it once the file has uploaded? I have tried:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(loadAnimation) object:nil];
But this does not stop the animation.
The loadAnimation is:
- (void) loadAnimation
{
loadingPng.hidden=NO;
NSArray *imageArray = [[NSArray alloc] initWithObjects:[UIImage imageNamed:#"0.png"], [UIImage imageNamed:#"1.png"], [UIImage imageNamed:#"2.png"], [UIImage imageNamed:#"3.png"], [UIImage imageNamed:#"4.png"], [UIImage imageNamed:#"5.png"], nil];
loadingPng = [[UIImageView alloc] initWithFrame:CGRectMake(487, 500, 50, 50)];
[self.view addSubview:loadingPng];
loadingPng.animationImages = imageArray;
loadingPng.animationDuration = 1.5;
[loadingPng startAnimating];
}
Just to make things clear performSelectorInBackground creates another thread, which executes the method and dies (if not attached to any runloop).
If during this execution something on the screen appears and you want to hide it, just call a whatever routine needed to hide it. If you use UIActivityIndicatorView or UIImageView call -(void)stopAnimating (in this case [loadingPng stopAnimating]).
There is no need in starting animation in background, actually it's a thing that should not be done, because anything that involves UI manipulation is highly recommended to be done only on the main thread.
It's a loading that should go to background, and animation triggering stays on the main thread.

Program crashes when segueing back and dispatch queue has animation method

Ok so i have a uitableview and when an item is selected it segues to a new view controller to show an image (inside of a uiscrollview). The image begins downloading in a dispatch_queue from prepareforsegue. To be clear I am doing all UI updates in the main queue. The problem is that if i hit the back button quick enough then my program crashes with an exc_bad_address. I think the problem has to deal with zoomToRect animated:YES because when i set animated to NO i cant get it to crash. Plus the call stack below deals with animation. What is the best way to go about fixing this and is there a better way to get what i need done?
Also when debugging the problem print 'Block completed' and then crashes shortly after.
stack trace
Here is the method called. It is called in a setter of the destination controller in prepareForSegue.
-(void) updateDisplay {
dispatch_queue_t queue = dispatch_queue_create("Load Flickr Photo", NULL);
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:spinner];
[spinner startAnimating];
dispatch_async(queue, ^{
UIImage *image = [FlickrFetcher imageForPhoto:self.currentPhoto format:FlickrPhotoFormatLarge];
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.rightBarButtonItem = nil;
self.imageView.image = image;
self.title = [FlickrFetcher titleForPhoto:self.currentPhoto];
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.0);
self.imageView.transform = transform;
self.imageView.frame = CGRectMake(0, 0, self.imageView.image.size.width, self.imageView.image.size.height);
self.scrollView.maximumZoomScale = 4.0;
self.scrollView.minimumZoomScale = .2;
self.scrollView.zoomScale = 1;
self.scrollView.contentSize = self.imageView.bounds.size;
//i think problem is here
[self.scrollView zoomToRect:self.imageView.frame animated:YES];
NSLog(#"Block completed");
});
});
}
I think a potential problem could be that while your block retains your self the view controller while its active in queue...
If you send -zoomToRect:animated:NO, the block will still be active and blocking on the call which ensures that all the objects are still valid in memory.
If you send -zoomToRect:animated:YES, the block will exit potentially generating a race condition where your view controller would be released since storyboards will also release your view controller when you go back in a segue leaving it with an effective retain count of zero.

How to correctly display a "progress" sheet modally while using Grand Central Dispatch to process something?

I'm trying to display a sheet on a window containing a single progress bar, to show the progress of some long function running asynchronously using Grand Central Dispatch. I've almost got it, but can't get the sheet to appear to be in focus, probably because I haven't used runModalForWindow: or similar.
This is approximately what I'm doing at the moment, it happens as a result of a button press on the main window:
// Prepare sheet and show it...
[NSApp beginSheet:progressSheet modalForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
[progressSheet makeKeyAndOrderFront:self];
[progressBar setIndeterminate:NO];
[progressBar setDoubleValue:0.f];
[progressBar startAnimation:self];
// Start computation using GCD...
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 1000; i ++) {
// Do some large computation here
// ...
// Update the progress bar which is in the sheet:
dispatch_async(dispatch_get_main_queue(), ^{
[progressBar setDoubleValue:(double)i];
});
}
// Calculation finished, remove sheet on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[progressBar setIndeterminate:YES];
[NSApp endSheet:progressSheet];
[progressSheet orderOut:self];
});
});
This works, except the main window is still in focus, the sheet is out of focus, and the progress bar doesn't animate (unless I use setUsesThreadedAnimation:YES on it).
The problem I think I'm having is that I'm not sure how to run the sheet modally without blocking the main thread before I start the asynchronous computation?
As stated by Brad, it should work.
To do a quick test, I created a sheet programmatically (normally, you would probably use a nib file, but they are hard to paste into this text). If I call the code below from a button in a normal Cocoa window, it works as expected. Notice that the text field on the sheet is first responder, and if you type on the keyboard while it is open, it will accept the input.
#define maxloop 1000
- (IBAction)startTask:(id)sender
{
// Prepare sheet and show it...
breakLoop = NO;
NSRect sheetRect = NSMakeRect(0, 0, 400, 114);
NSWindow *progSheet = [[NSWindow alloc] initWithContentRect:sheetRect
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
NSView *contentView = [[NSView alloc] initWithFrame:sheetRect];
NSProgressIndicator *progInd = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(143, 74, 239, 20)];
NSTextField *inputField = [[NSTextField alloc] initWithFrame:NSMakeRect(145, 48, 235, 22)];
NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(304, 12, 82, 32)];
cancelButton.bezelStyle = NSRoundedBezelStyle;
cancelButton.title = #"Cancel";
cancelButton.action = #selector(cancelTask:);
cancelButton.target = self;
[contentView addSubview:progInd];
[contentView addSubview:inputField];
[contentView addSubview:cancelButton];
[progSheet setContentView:contentView];
[NSApp beginSheet:progSheet
modalForWindow:self.window
modalDelegate:nil
didEndSelector:NULL
contextInfo:NULL];
[progSheet makeKeyAndOrderFront:self];
[progInd setIndeterminate:NO];
[progInd setDoubleValue:0.f];
[progInd startAnimation:self];
// Start computation using GCD...
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < maxloop; i++) {
[NSThread sleepForTimeInterval:0.01];
if (breakLoop)
{
break;
}
// Update the progress bar which is in the sheet:
dispatch_async(dispatch_get_main_queue(), ^{
[progInd setDoubleValue: (double)i/maxloop * 100];
});
}
// Calculation finished, remove sheet on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[progInd setIndeterminate:YES];
[NSApp endSheet:progSheet];
[progSheet orderOut:self];
});
});
}
- (IBAction)cancelTask:(id)sender
{
NSLog(#"Cancelling");
breakLoop = YES;
}
Apologies for the ugly sheet, but apart from that this code works as expected, so the issue you are seeing is probably unrelated to GCD.
I had exactly the same problem. With some trial and error, I found the solution. Make sure your sheet's window is (a) an NSWindow not an NSPanel (this may not matter) and that the window has a Title Bar (which, as it's a sheet you're using) will not be displayed.
I turned the Title Bar off for that reason, but somehow it's required to correctly achieve focus. Ticking the Title Bar checkbox gives my progress bar sheet focus.

Printing multiple images each on a separate page using single NSPrintOperation

I have a application that intends to print raster image of each page of a document using NSPrintOperation. I am able to create a NSImage of a single page and print it using NSPrintOperation as follows
-void printPage:(NSImage)nsImage
{
NSImageView *nsImageView = [[NSImageView alloc] init];
NSSize imageSize = [nsImage size];
[nsImageView setImage:(NSImage *)nsImage];
[nsImageView setFrame:NSMakeRect(0, 0, imageSize.width, imageSize.height)];
[nsImageView setImageScaling:NSScaleToFit];
NSPrintOperation *mNSPrintOperation = [NSPrintOperation printOperationWithView:(NSView *)nsImageView];
NSPrintInfo *currentNSPrintInfo = [NSPrintInfo sharedPrintInfo];
[currentNSPrintInfo setHorizontalPagination:NSFitPagination];
[currentNSPrintInfo setVerticalPagination:NSFitPagination];
[mNSPrintOperation setPrintInfo:currentNSPrintInfo];
[mNSPrintOperation setShowsPrintPanel:NO];
[mNSPrintOperation setShowsProgressPanel:YES];
[mNSPrintOperation runOperation];
}
Now when I have multiple pages to print, I would like to print all of them using a single NSPrintOperation. So basically, I would like to insert NSImage/NSImageView of each page as a separate page into a single NSView and use this NSView to print finally using NSPrintOperation. The reason I want to print it using single NSPrintOperation is that I want to get the print progress bar that shows the current page being printed. Otherwise, I could have created a separate NSPrintOperation for each NSImageView and print using it.

NSOpenPanel above a fullscreen NSWindow?

I open a window with the following:
NSRect screenRect = [[NSScreen mainScreen] frame];
[super initWithContentRect:screenRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
int windowLevel = CGShieldingWindowLevel();
[self setLevel:windowLevel];
... so the window is fullscreen & above all other window levels (including modal windows). I later want to display an open panel, however the following opens the dialog below the window I created above (it seems that the runModal stuff overrides the requested window level I try to set):
NSOpenPanel *OP = [NSOpenPanel openPanel];
int windowLevel = CGShieldingWindowLevel();
[OP setLevel:windowLevel];
int returnCode = [OP runModal];
... and the following opens a sheet on the window created above (good), however it also winds up showing the menu bar, which I had previously hidden (not what I want):
NSOpenPanel *OP = [NSOpenPanel openPanel];
[OP beginSheetModalForWindow:[self window]
completionHandler:^(NSInteger returnCode) {
NSLog(#"completionHandler called with %d", returnCode);
}];
... so my questions are:
Does anyone know how to open a modal window above the CGShieldingWindowLevel ?
Is there any way to get the menu bar to not show up on the sheet solution I'm trying above ?
Thanks all :-)
OK, here's an even better option - missed this one completely when I was reviewing the docs:
NSOpenPanel *OP = [NSOpenPanel openPanel];
[OP setLevel:CGShieldingWindowLevel()];
[OP beginWithCompletionHandler:^(NSInteger returnCode) {
NSLog(#"completionHandler called with %d", returnCode);
}];
... ie: open the panel as it's own window, which was exactly what I wanted to do in the fist place (duh!)
You can create a category of NSSavePanel like this :
#implementation NSSavePanel (SavePanelSetLevel)
- (void)setLevel:(NSInteger)newLevel
{
[super setLevel:CGShieldingWindowLevel()] ; // NSWindow implementation call
}
#end
because runModal reset the level previously set !
OK, 5 years later, I sorta can make this work - the trick is to open up a second window, promote it to the CGShieldingWindowLevel, make it key & order front, then attach the open sheet to it - the sheet magically appears from wherever the second window is, and although not perfect, it looks a lot better than the solution I came up with originally. Here's the change:
NSOpenPanel *OP = [NSOpenPanel openPanel];
// this is the new bit - make the window 1x1 # the location of your liking
NSRect windowRect = NSMakeRect(0, 1000, 1, 1);
NSWindow *OPW = [[NSWindow alloc] initWithContentRect:windowRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
int windowLevel = CGShieldingWindowLevel();
[OPW setLevel:windowLevel];
[OPW makeKeyAndOrderFront:nil];
// end of new bit, apart from passing OPW for beginSheetModalForWindow
// instead of [self window]
[OP beginSheetModalForWindow:OPW
completionHandler:^(NSInteger returnCode) {
NSLog(#"completionHandler called with %d", returnCode);
}];
... the only think to watch out for is that with the below you can wind up opening up several open dialogs, since the sheet is modal for a window other than the main window - the main window can still accept mouse click events ...