I have an NSTableView that gets reloaded. While new data is loading, I want to add a subview ontop of it with a spinner. I would like the view ontop to be semi-transparent and reveal the view beneath it, to be blurred. How would I go about doing this?
The easiest solution—significantly more so than the -bitmapImageRepEtc: one, and more applicable to Mac OS than the rasterization-scale method—is to set your overlay view to use a Core Animation backing layer, then give that layer a Core Image blur filter. It's a technique used all over the Mac OS, from the Dock menus to the menu bar itself. Interface Builder makes it trivially easy to set up, but you can do it in code as well, like this:
CALayer *backgroundLayer = [CALayer layer];
[backgroundView setLayer:backgroundLayer];
[backgroundView setWantsLayer:YES];
CIFilter *blurFilter = [CIFilter filterWithName:#"CIGaussianBlur"];
[blurFilter setDefaults];
[backgroundView layer].backgroundFilters = [NSArray arrayWithObject:blurFilter];
You should check out RMBlurredView on guthub: https://github.com/raffael/RMBlurredView
It's an easy to use subclass of NSView that does all that for you. Be sure to set setWantsLayer:YES on your parent view!
For details, check out Cocoanetics article: http://www.cocoanetics.com/2013/10/blurring-views-on-mac/
The basic technique would be to snap an image of your view, using something like the ‑bitmapImageRepForCachingDisplayInRect: method of NSView, processing that image to make it blurred (Core Image is your friend here) and then overlay your view with an NSImageView containing the blurred image.
This is fakery, of course, but that's what showmanship is about :-)
Have you tried changing the Alpha attribute for the view (for transparency)?
Also here's a link on blurring a view:
Blur Effect for UIView
Related
I'm trying to draw shadow under an NSImageView. I'm accessing the CALayer of the view and setting its shadow properties there:
[self.originalImageView setWantsLayer:YES];
CALayer *imageLayer = self.originalImageView.layer;
[imageLayer setShadowRadius:5.f];
[imageLayer setShadowOffset:CGSizeZero];
[imageLayer setShadowOpacity:0.5f];
[imageLayer setShadowColor:CGColorCreateGenericGray(0, 1)];
imageLayer.masksToBounds = NO;
But even though I have set masks to bounds as NO, I'm getting this:
Look carefully at the shadow. It is displayed perfectly horizontally, but vertically, it's clipped. The image is aspect-fitted, and both the image and the image view can be arbitrary size. If I try it with NSShadow instead of layer's shadow, I'm getting exactly the same results. I could use myView.clipsToBounds = NO in iOS and it used to solve this problem, however I can't find that property on Mac.
How can I draw shadow under arbitrary-sized NSImageView without clipping?
With the latest Xcode, I've discovered that I can apply CI filters to views (I don't know if that feature was also available before or not.) and I went and applied a shadow on my image view on the container view's layer. It works perfectly. Bottomline: If I apply the filter on image view's own layer, the same thing happens, but if I apply it on the container view's layer, it works!
I'm working on some drawing code. I have that portion working great.
I want to draw over an image, but I want to still be able to see the detail of the image, the black lines, etc.
What I am working on is making a transparent UIImageView that holds the image.
I'm not sure how to get this set up properly though.
Should this be added above the other UIImageView that I color on or below it?
Here's what I have so far:
- (void)viewDidLoad
{
[super viewDidLoad];
topImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 46, 320, 370)];
[topImageView setImage:[UIImage imageNamed:#"imagesmall.png"]];
topImageView.alpha = 1.0;
topImageView.layer.opacity = 1.0;
topImageView.layer.opaque = NO;
[self.view addSubview:topImageView];
[topImageView release];
}
Thoughts anyone?
Yes, you can draw views over other views. They are drawn in the order that they're added as subviews, unless you reorder them after that.
You may need to set the opaque property for some views (this is distinct from and overrides their layer opacity), and set their backgroundColor to nil. UIImageView seems to be transparent by default, as long as its image is; some other UIView subclasses are not.
So, just what is your overlay going to be? If you just need to display one image over another, what you have here seems to work already. If you need to draw some lines programmatically, you'll need to do this:
Create a subclass of UIView.
Implement its drawRect method to display the content you need.
When you add your custom view on top of the background image, make sure it is not opaque and has no backgroundColor.
A common problem here is to find that your foreground is working, but the background isn't being loaded properly. To make sure the background is there, set the alpha of the foreground view to 0.5. You won't want to do that in production, but it will allow you to verify that both views exist.
I've been looking around but I can't find an answer on this. I'm trying to make a view that will display it's contents normally, but anything that's underneath it (in the z axis) would be blurred.
I can't seem to manage to do this right off the bat. Anything I try (at best) blurs the contents of the view, not what's underneath.
Check out this article on Cocoanetics, it gives you some instructions how to properly set up a NSView with blurred background: http://www.cocoanetics.com/2013/10/blurring-views-on-mac/
There's also a RMBlurredView class on Github: https://github.com/raffael/RMBlurredView
The idea behind it is to use a layer-backed NSView with a CIGaussianBlur CIFilter applied on the backgroundFilters property. All important flags are set correctly in the mentioned class.
If you use an image that is significantly smaller than the view's frame and scale the image up, it will be blurred.
You could use CALayers. Declare a root layer and add the background image as a sublayer. Put the "contents" in a subview with a transparent background. (Otherwise the "contents" would probably be obscured by the blurred sublayer.)
Here's partial (and untested) code for setting up the sublayer:
// Set up the root layer.
[[self.aViewController view] setLayer:[CALayer layer]];
[[self.aViewController view] setWantsLayer:YES];
// Set up a sublayer.
CALayer *blurredSublayer = [CALayer layer];
NSRect rectOfView = [self.aViewController view] frame];
blurredSublayer.bounds = rectOfView;
// Views are usually positioned from their lower left corner, but CALayers are positioned from their center point.
blurredSublayer.position = CGPointMake(rectOfView.size.width/2, rectOfView.size.height/2);
// Set the sublayer's contents property to the image you've chosen. You might need to do the scaling when you set up the CGImageRef used by the contents property. (I haven't done this; I leave it to you.)
// Add the sublayer to the view's root layer.
[self.aViewController.view.layer addSublayer:blurredSublayer];
// You'll probably want to save expense by calling setWantsLayer:NO for the contents-bearing subview, since it will have been turned on when you set up the superview's root layer.
Or it might be easier to use a CGContext. But with the CALayer you have the the zPosition property you mentioned.
P.S. The use of "contents" and "superview" and "sublayer" make the structure confusing. Here's a descriptive hierarchy. The first-named item is on the bottom and the last-named on top, as it would be in IB:
superview with root layer
superview's blurred sublayer (sublayer's contents property is scaled-up image)
subview/s (textfields or whatever you had in mind as "contents")
I am designing a user interface containing several labels and text fields. I would like to style the UI like this:
setting a background pattern for the content view of my NSWindow
adding a custom icon to the background in the upper left corner
I solved the first problem by making the content view a layer-backed view as described in Apple's documentation of NSView:
A layer-backed view is a view that is backed by a Core Animation layer. Any drawing done by the view is the cached in the backing layer. You configured a layer-backed view by simply invoking setWantsLayer: with a value of YES. The view class will automatically create the a backing layer for you, and you use the view class’s drawing mechanisms. When using layer-backed views you should never interact directly with the layer.
A layer-hosting view is a view that contains a Core Animation layer that you intend to manipulate directly. You create a layer-hosting view by instantiating an instance of a Core Animation layer class and setting that layer using the view’s setLayer: method. After doing so, you then invoke setWantsLayer: with a value of YES. When using a layer-hosting view you should not rely on the view for drawing, nor should you add subviews to the layer-hosting view.
and then generating a CGColorRef out of a CGPattern which draws my CGImage:
NSView *mainView = [[self window]contentView];
[mainView setWantsLayer:YES];
To set the background image as a pattern I used the answer from How to tile the contents of a CALayer here on SO to get the first task done.
However for the second task, adding the icon I used the code below:
CGImageRef iconImage = NULL;
NSString *path = [[NSBundle mainBundle] pathForResource:#"icon_128" ofType:#"png"];
if(path != nil) {
NSURL *imageURL = [NSURL fileURLWithPath:path];
provider = CGDataProviderCreateWithURL((CFURLRef)imageURL);
iconImage = CGImageCreateWithPNGDataProvider(provider,NULL,FALSE,kCGRenderingIntentDefault);
CFRelease(provider);
}
CALayer *iconLayer = [[CALayer alloc] init];
// layer is the mainView's layer
CGRect layerFrame = layer.frame;
CGFloat iconWidth = 128.f;
iconLayer.frame = CGRectMake(0.f, CGRectGetHeight(layerFrame)-iconWidth, 128.f, 128.f);
iconLayer.contents = (id)iconImage;
CGImageRelease(iconImage);
[layer insertSublayer:iconLayer atIndex:0];
[iconLayer release];
The Questions
I am not sure if I am violating Apple's restrictions concerning layer-backed views that you should never interact directly with the layer. When setting the layer's background color I am interacting directly with the layer or am I mistaken here?
I have a bad feeling about interacting with the layer hierarchy of a layer-backed view directly and inserting a new layer like I did for my second task. Is this possible or also violating Apple's guidelines? I want to point out that this content view of course has several subviews such as labels, a text view and buttons.
It seems to me that just using one single layer-hosting NSView seems to be the cleanest solution. All the text labels could then be added as CATextLayers etc. However if I understand Apple's documentation correctly I cannot add any controls to the view anymore. Would I have to code all the controls myself in custom CALayers to get it working? Sounds like reinventing the wheel de luxe. I also have no idea how one would code a NSTextField solely in CoreAnimation.
Any advice on how split designing user interfaces with CoreAnimation and standard controls is appreciated.
Please note that I am talking about the Mac here.
no layer backing needed IMHO:
for 1. I do a pattern image
NSImage *patternImage = [NSImage imageNamed:#"pattern"];
[window setBackgroungdColor:[NSColor colorWithPatternImage:patternImage]];
for 2. add an NSImageView as a subview of the contentview
NSImageView *v = ...
[[window contentView] addSubview:v];
on mac some views dont respond nicely IF layer backed
:: e.g. pdfview
Make a superview container A. Add a subview B to A for all your NSView needs (buttons, etc.). Add a subview C to A for all your Core Animation needs.
Edit:
Even better: use superview A for all your NSView needs and one subview C for your Core Animation needs, ignoring view B altogether.
I am looking for a perfect solution to set a background image for a window in a cocoa application. I haven't found a solution to this, I am new in objective c, so please anyone help me...
A window in Cocoa has a root-level view called the "content view". This is the view that contains all the others in a window. By default, it's just a plain, blank NSView. But you could easily create your own custom NSView subclass, override the drawRect: method to draw your background image, and use that for your custom view.
However, it might just be easier to use a plain old NSImageView. The advantage of this is that you can set, for example, autosizing behavior to keep the image pinned to one corner (try this with Installer.app by resizing the installer window). You would also be able to make it semi-opaque so that the background shows through a bit. (Again, I'm thinking of Installer.app; your app could be totally different)
Hope that gets you going in the right direction!
Michael Vannorsdel suggests sublassing NSView for the purpose, and I quote:
You'd really be better off making an
NSView subclass and having it draw
the image you want in drawRect:.
- (void)awakeFromNib
{
myImage = [[NSImage alloc] init....
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
NSSize isize = [myImage size];
[myImage drawInRect:[self bounds] fromRect:NSMakeRect(0.0, 0.0,
isize.width, isize.height) operation: NSCompositeCopy fraction:1.0];
}
Read that whole thread on cocoabuilder, it's quite instructive.