show quick look preview in a view - objective-c

I am trying to show the preview of a file in a View instead of in a panel. All examples I found are about QLPreviewPanel. :(
Thanks in advance for your help.

Starting in OS X 10.7, there is a public API for this: QLPreviewView.h, which is part of the QuickLookUI framework (which is part of the Quartz framework). There doesn't seem to be documentation on it, but the header file provides some basic info.

It seems like Apple really wants you to use the QLPreviewPanel; the only possibility I see is "scraping" the preview, by setting the panel to layer-backed and getting the contents of the correct sublayer. Something like this (although I haven't gotten it to work):
[[[QLPreviewPanel sharedPreviewPanel] contentView] setWantsLayer:YES];
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
NSLog(#"layer: %#", [[[QLPreviewPanel sharedPreviewPanel] contentView] layer]);
// I believe there are two sublayers
id QLcontents = [[[[[[QLPreviewPanel sharedPreviewPanel] contentView] layer] sublayers] objectAtIndex:0] contents];
NSLog(#"contents: %#", QLcontents);
[myView layer].contents = QLcontents;
[myView layer] setNeedsDisplay];
Not a final solution by any means (this doesn't work as is) but maybe it'll point you in a useful direction.
UPDATE: Just stumbled across a category on NSImage, written by a fellow named Matt Gemmell, which uses QLThumbnailImageCreate. Look for "NSImage+QuickLook" on his source code page. He seems to imply that the QuickLook Panel actually uses QLThumbnailImageCreate. I think that function may be the best way to go. The category might make your life a little easier.

I've made some headway with this. The sublayers' contents were nil. But the old-fashioned subviews work:
[[[QLPreviewPanel sharedPreviewPanel] contentView] setWantsLayer:YES];
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
NSArray* subviews = [[[QLPreviewPanel sharedPreviewPanel] contentView] subviews] ;
for (id subview in subviews) {
// The first view is the one we want, which is an unsubclassed NSView.
// The second is a QLPreviewTitleBarView. However, instead of relying
// on that order, we check for the class.
if ([subview isMemberOfClass:[NSView class]]) {
NSLog(#"frame of subview: %#", NSStringFromRect([subview frame])) ;
// The following statement will *remove* the desired subview from
// the QLPreviewPane and place it into myWindow instead.
[[myWindow contentView] addSubview:subview];
break ;
}
}
It even seems to update myWindow when I send -reloadData to the QLPreviewPane. I'm feeling slimy. Not sure what to do next, though. One problem is to deal with the arbitrary size of the subview. One of the reasons why I don't like QLPreviewPane is that there is no control over the window size; it gets a view from the generator at some arbitrary size and splats it on the screen. I guess I could put it into a scroll view. Another issue is how to deal with the QLPreviewPanel which is still on the screen. Maybe set its frame origin to off-screen But I need to go work on another task right now. Any further ideas would be appreciated.
Later. I think this approach is going to be to problematic. First, I tried to get rid of the QLPreviewPane window by sending it a setFrameOrigin:NSMakePoint(10000, 10000). Result: [QL] Assertion failure ([event window] == window) - Wrong window in event. Then I tried to omit the call to -makeKeyAndOrderFront: and instead skip up to -reloadData. Result: [QL] QLError(): -[QLPreviewPanel reloadData] called while the panel has no controller - Fix this or this will raise soon. See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
The second error is understandable, however it indicates that the QLPreviewPanel will not try to find its data source, as it does, until it is made key or ordered front. The first assertion, however, indicates that, besides not providing a proper API to get the preview data directly, maybe Apple has laid some traps for the casual hacker like me.
If I come back to this, next time I'll try the more serious hack proposed by Ken Apeslagh.

Related

How do I re-define size/origin of app window in code, overriding nib/xib file parameters, with Obj-C, Xcode 11.3.1 on Mac OS X 10.15.2 (Catalina)?

I'll try to keep it short. I want to create a 3D FPS game, just for myself, that can run on multiple platforms, but I figured that to keep it simple, perhaps it is best to start off with something that is exclusively for macOS. I opted for Objective-C because
(a) Window Application projects in Xcode can only be coded either in Obj-C or Swift (since we are dealing with Cocoa API) and
(b) Obj-C is closer to old-school then Swift.
But before I learn to draw/render 2D-shapes on the window's canvas by writing code, I have to learn to invoke an application window with its properties set to my liking. I've spent hours doing research and experimenting with chunks of code. This is what I've tried: I open with
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
Then I go with ...
1)
NSWindow *window = [[[NSApplication sharedApplication] windows] firstObject];
NSRect frame = [window frame];
frame.origin.x = 100;
frame.origin.y = 200;
frame.size.width = 100;
frame.size.height = 500;
[window setFrame: frame display: YES];
... and close with ...
NSApplicationMain(argc, argv); // runs the win display function.
}
return (0) ;
}
But no visible changes. Nothing really gets reset. So instead of (1) I tried ...
2)
NSWindow *window = [[[NSApplication sharedApplication] windows] firstObject];
NSPoint newOrigin;
newOrigin.x = 400;
newOrigin.y = 100;
[window setFrameOrigin : newOrigin];
Still nothing. Then instead of (2) I tried:
3)
NSWindowController* controller = [[NSWindowController alloc]
initWithWindowNibName:#"MainMenu"];
[controller showWindow:nil];
Great. Now it's spitting out something I don't understand, especially since I'm new to Obj-C:
2020-02-08 21:53:49.782197-0800
tryout_macApp2[14333:939233] [Nib Loading] Failed
to connect (delegate) outlet from
(NSWindowController) to (AppDelegate): missing
setter or instance variable
I remember dicing around with an ApplicationDelegate, with CGSizeMake(), etc., but it just made the experience really inundating and frustrating. Nothing happened. Then there are NSView, NSViewController, and other classes, which is really mindboggling and begs the question: why are there so many classes when all I want to do is override the preset origin of the window and the dimensions preset by the MainMenu.xib file? (By the way, this project is derived from a Window Application project provided by Xcode.)
I really can't think of anything else to add to give you the entire picture of my predicament, so if you feel that something is missing, please chime in.
[Edit:] Moving forward to phase 2 of my project here: How do I paint/draw/render a dot or color a pixel on the canvas of my window with only a few lines in Obj-C on Mac OS X using Xcode?.
The short answer is that main() is too early to be trying to do this. Instead, implement -applicationDidFinishLaunching: on your app delegate class, and do it there. Leave main() as it was originally created by Xcode's template.
After that, I would say to obtain the window (if there's only going to be one main one), it's better to add an outlet to your app delegate and then, in the NIB, connect that outlet to the window. Then, you can use that outlet whenever you want to refer to the window.
Also, make sure that Visible at Launch is disabled for the window in the NIB. That's so you configure it as you want before showing it.
For a more complex app, it's probably better to not put a window into the Main Menu NIB. Instead, make a separate NIB for the window. Then, load it using a window controller object and ask that for its window.
I love Objective-C but also feel your pain, it has this testy ability to frustrate you endlessly.
I have not really developed a game but let me try and point you in the right direction. I think you need a UIViewController.
Now each UIViewController has a built in UIView that sort of represents the visible portion of it. You can use this or add a UIView and use that, whichever depends on your implementation. For now I'd suggest add a separate UIView and use that rather. Once you're comfortable you can then move the implementation to the UIViewController's view if you need to.
Anyhow, for now, create a UIView subclass, say MyGame or something, as for now all your code will end up there.
To do all of the above is not easy, especially if its the first time. If you can follow some tutorial it will be great. Even if the tutorial just adds a button, you can use it and replace the button with your view.
Anyhow, now that you've got that running and the view you've added shows up in green or some other neon colour just to verify that you can indeed change its properties, you're good to go.
Now you start. In MyGame, implement the
-(void)drawRect:(CGRect)rect
message, grab the context through
UIGraphicsGetCurrentContext
and start drawing lines and stuff on it, basically the stuff I understand you are interested in doing. You can also, through the same context, change the origin of what you are doing.
Hope this helps.

Change the order on element

I'm now doing an application, where i need to put some elements in the top of the views sometimes.
For example, how could i do to make the entire CGRect red and not the green visible here :
Could i call addSubview method more than one time, and it won't be sad in memory management ?
Because i've tried a lot of things, like :
[myUIImageView removeFromSuperview];
[muUIVew addsubview:myUIImageView];
or :
[myUIImageView didMoveToSuperview];
But it gaves me an error...
Thank you !
No, you should call addSubview once. There are methods to manage subview position.
This is two methods you can use:
[muUIVew sendSubviewToBack: myUIImageView];
[muUIVew bringSubviewToFront: myUIImageView];
Should be pretty clear what they do.

Calling -setNeedsDisplay:YES from within -drawRect?

I am customizing my drawRect: method, which serves to draw a NSImage if it has been "loaded" (loading taking a few seconds worth of time because I'm grabbing it from a WebView), and putting off drawing the image till later if the image has not yet been loaded.
- (void)drawRect:(NSRect)dirtyRect
{
NSImage *imageToDraw = [self cachedImage];
if (imageToDraw != nil) {
[imageToDraw drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil];
} else {
//I need help here
[self setNeedsDisplay:YES];
}
}
My question is how to do the latter. [self cachedImage] returns nil if the image is unavailable, but anytime within the next few seconds it may become available and at that time I want to draw it because the custom view is already on screen.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn), but that doesn't work.
Any pointers as to where I can go from here?
EDIT:
I am very much aware of the delegate methods for WebView that fire when the loadRequest has been completely processed. Using these, however, will be very difficult due to the structure of the rest of the application, but I think I will try to somehow use them now given the current answers. (also note that my drawRect: method is relatively light weight, there being nothing except the code I already have above.)
I currently have about 10+ custom views each with custom data asking the same WebView to generate images for each of them. At the same time, I am grabbing the image from an NSCache (using an identifier corresponding to each custom view) and creating it if it doesn't exist or needs to be updated, and returning nil if it is not yet available. Hence, it's not as easy as calling [view setNeedsDisplay:YES] from - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame or another method.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn)
This would be incredibly inefficient, even if it worked.
anytime within the next few seconds it may become available and at that time I want to draw it
So, when that happens, call [view setNeedsDisplay:YES].
If you have no means of directly determining when the image becomes available, you'll have to poll. Set up a repeating NSTimer with an interval of something reasonable -- say 0.25 second or so. (This is also pretty inefficient, but at least it's running only 4 times per second instead of 60 or worse. It's a tradeoff between two factors: how much CPU and battery power you want to use, and how long the delay is between the time the image becomes available and the time you show it.)
my drawRect: method is relatively light weight, there being nothing except the code I already have above.
Even if you do nothing at all in -drawRect:, Cocoa still needs to do a lot of work behind the scenes -- it needs to manage dirty rects, clear the appropriate area of the window's backing store, flush it to the screen, etc. None of that is free.
Well, usually there is some delegate method that is called, when a download of something finishes. You should implement that method and call setNeedsDisplay:YES there.
The documentation for webkit:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/ResourceLoading.html#//apple_ref/doc/uid/20002028-CJBEHAAG
You have to implement the following method in your webview delegate:
- webView:resource:didFinishLoadingFromDataSource:
There you can call [view setNeedsDisplay:Yes]

dismissModalViewControllerAnimated mystery

I've seen a number of posts on this subject, but none that leave me with a clear understanding of what is happening.
I've set up a small test involving two UIViewControllers: MainController and ModalController.
MainController has a button on it that presents a modal view controller using the following simple code:
ModalController *myModal = [[ModalController alloc] init];
[self presentModalViewController:myModal animated:YES];
[myModal release];
Now, if I immediately dismiss this modal controller from within the same block of code, as per this next line:
[self dismissModalViewControllerAnimated: YES];
The modal view does not dismiss.
Following some suggestions on this site, I put the dismissModalViewControllerAnimated call in a separate method, which I then called with:
[self performSelector:#selector(delayedDismissal) withObject:nil
afterDelay:0.41];
This works - at least if I make the delay 0.41 or greater. .40 or less and it doesn't work.
At this point, I'm assuming I'm dealing here a run-loop that needs to catch up with itself, for lack of a better description. It's not very stable, unfortunately.
So, for the next test, I make the delayedDismissal do nothing - it only serves to provide a delay - and re-insert the dismissModalViewControllerAnimated call back in the original block, such that my code now looks like this:
ModalController *myModal = [[ModalController alloc] init];
[self presentModalViewController:myModal animated:YES];
[myModal release];
self performSelector:#selector(delayedDismissal) withObject:nil
afterDelay:0.41]; // to create the false delay
[self dismissModalViewControllerAnimated: YES];
...now the dismissModalViewControllerAnimated doesn't work again, no matter how long a delay I use.
So, what is happening here? I realize, like others, I can achieve my goal through assorted workarounds, including the use of a delegate, etc. But I really think it would be good for everyone who encounters this issue to walk away with a thorough understanding of both the problem and the proper solution for this scenario. Incidentally, one use case for this scenario is to present a loading screen modally where the user has no interaction with that screen; it's just being used to present information while blocking the user from taking actions.
The view is animating, thus as long as it is animating calling dismiss won't work.
Also in the second thing you tried, you are calling a "delay" but what you are actually doing is saying the following: "Ok, here is this cute method, can you execute that 0.41 seconds later? thanks, in the mean time, call this method.."
Dismissing a modal view controller should be done through the userinterface, by clicking a button, so why are you trying this in the first place?

Cannot set an NSWindow's position

I have a class that extends NSWindowController and I am trying to position the window it controls. The window displays all of the expected contents and functions correctly, but when I try and position its starting location on the screen in the initWithWindowNibName method, the position does not change. Here is the code:
NSPoint p = NSMakePoint(100, 50);
[[self window] setFrameTopLeftPoint:p];
This seems very straight forward and I'm not sure what the problem is.
Thanks for any ideas.
(Found the problem. I did not have the window wired up to the Class in IB.)
Wevah has the right idea, though I'll try to expand on it a bit.
If you were to try adding this line to your initWithWindowNibName: method:
NSLog(#"window == %#", [self window]);
You would likely see the following output to console:
window == (null)
In other words, the window is still nil, as init* methods are so early on in an object's lifetime that many IBOutlets or user interface items aren't quite "hooked up" yet.
Sending a message to nil is perfectly fine: it's simply ignored. So, basically your attempt to position the window has no effect because it basically equates to [nil doSomething];
The key then is to perform the positioning of the window later on in the controller object's lifetime, where the IBOutlets and other user interface objects are properly hooked up. As Wevah alluded to, one such method where things are properly hooked up is
- (void)awakeFromNib;
or in the case of NSWindowController, the following one as well:
- (void)windowDidLoad;
Hope this helps...
Try putting that code in awakeFromNib.