NSView containing NSOpenGLView doesn't redraw after a subview is removed - objective-c

I have a Mac OS X (10.9) application with a custom NSView for my main NSWindow's contentView property. The only thing the custom view does is override drawRect: so that it's transparent. Transparency is required so that my NSOpenGLView is visible (see below):
/* Transparent NSView Subclass */
- (void)drawRect:(NSRect)dirtyRect
{
NSLog(#"Drawing Rect");
[[NSColor clearColor] set];
NSRectFillUsingOperation(dirtyRect, NSCompositeClear);
}
The contentView has an NSOpenGLView as a subview, with its surface order set to -1 (it's 'below' the main NSWindow, which is also transparent):
/* NSOpenGLView subclass: */
GLint order = -1;
[self.openGLContext setValues:&order forParameter:NSOpenGLCPSurfaceOrder];
I then instantiate a WebView and place it as a subview of this custom view:
_webview = [[WebView alloc] initWithFrame:frame];
[_webview setMaintainsBackForwardList:NO];
[_webview setTranslatesAutoresizingMaskIntoConstraints:NO];
[_webview setDrawsBackground:NO];
[_window.contentView addSubview:_webview];
The WebView is a small box in the window (on top of the NSOpenGLView) which is used to login to a service. Once login happens it should disappear, showing only the NSOpenGLView.
Here's the problem: when I call [_webview removeFromSuperview]; the contentView does not redraw; therefore, the WebView is still drawn on the screen (although not responsive to mouse or keyboard). If I use my mouse to resize the window that everything is in, the contentView redraws (I see the message in the logs) and the WebView's content disappears.
Also, if I don't add the NSOpenGLView as a subview, the WebView goes away as it should.
Shouldn't the contentView fire a drawRect after a subview is removed? Even when I used setNeedsDisplay: or setNeedsLayout: after removing the WebView the contentView still didn't redraw.

Apple's advice these days is to use Core Animation layers to put normal views on top of OpenGL views.
I suspect that the content view has redrawn itself. It draws clear. What you're seeing is the OpenGL surface behind it. The nearly-raw-VRAM nature of OpenGL surfaces may mean that the web view that was drawn on top of it actually modified the contents of the surface. So, I recommend that you force the OpenGL view to redraw itself. If it draws in its -drawRect:, then it should be enough to send it -setNeedsDisplay:. If you do rendering outside of -drawRect: by manually making the context current, issuing rendering commands, and then calling -flushBuffer, then you'll need to do that.

Related

NSScrollView doesn't call drawRect for documentView

I have NSScrollView with drawable content.
It works fine with scrolling until I press Cmd+Tab or hide window in any other way.
When I open window with scrollView second time it doesn't redraw content on scrolling.
so drawRect function for documentView doesn't work anymore.
setting content view (I need to redraw it on scrolling because self.documentView is much wider than window's width)
[self.documentScroll setDocumentView:self.documentView];
in NSClipView
-(void) drawRect:(NSRect)dirtyRect {
[self setCopiesOnScroll: NO];
[super drawRect:dirtyRect];
NSArray* ar = self.subviews;
NSView* docView = ar.firstObject;
BOOL b = docView.needsDisplay;
}
docView.needsDisplay is always NO after window was hidden and shown second time. On application's launch it's always YES and drawRect method calls for documentView on every scrolling event

Why NSView leaves an image on superview on where it was when I move it?

I am working on a small application on Mac that I need to create customed cursor and move it. I used NSImageView to implement it. However when I call setFrameOrigin (the same to setFrame) it will leaves images on the previous place.
Here is my code:
#property (nonatomic, strong) NSImageView *eraserView;
this is the define
_eraserView = [[NSImageView alloc] initWithFrame:CGRectMake(100, 100, 32, 32)];
_eraserView.image = [NSImage imageNamed:#"EraserCursor"];
[self.view addSubview:_eraserView];
[_eraserView setHidden:YES];
here is the initialization. Everything goes well until now but:
- (void)setImageatPoint:(NSPoint)point
{
[_eraserView setFrameOrigin:point];
}
- (void)hidePenImage
{
[_eraserView setHidden:YES];
}
- (void)unhidePenImage: (BOOL)isEraser
{
[_eraserView setHidden:NO];
}
These are methods I use to change the state of the NSImageView. They will be called by another class using delegate when corresponding events of trackpad occurs.
However every time I change the state of the NSImageView, it seems like it is drawn on the superview.
I debugged it and found there was no extra subviews. And when I use setHidden it has no effect on those tracks. I think it somehow did something to the CALayer, but I have no idea how to fix it.
Screenshots would help but in general if you move a view or change the area of the view that is drawn, you need to redraw.
To do this it kind of depends on how your drawing happens. Calling setNeedsDisplay may not be enough if your implementation of drawRect only draws a sub rect of the view bounds. Cocoa only draws what it is told to draw.
You can erase sections of the view that should be empty by drawing (filling) where it should be empty. That means drawing a color ( NSColor clearColor if nothing else) in the area that was previously drawn.

How to dynamically reposition UIButton as subview of UIImageView when rotating

I'm working on an iPad app that lets you control different things in a prototype of an intelligent house. For example it lets you turn lights on and off. For this, I have made a UIImageView that shows the floor plan of the house and added UIButtons as subviews for each lamp that can be toggled.
As you can see the buttons are placed perfectly on the floor plan using the setFrame method of each UIButton. However, when I rotate the iPad to portrait orientation, the following happens:
The buttons obviously still have the same origin, however it is not relative to the repositioning of the image.
The floor plan image has the following settings for struts and springs:
and has its content mode set to Aspect Fit.
My question is
how do I dynamically reposition each UIButton, such that it has the same relative position. I figure I have to handle this in the {did/should}AutorotateToInterfaceOrientation delegate method.
It should be noted that the UIImageView is zoomable and to handle this I have implemented the scrollViewDidZoom delegate method as follows:
for (UIView *button in _floorPlanImage.subviews) {
CGRect oldFrame = button.frame;
[button.layer setAnchorPoint:CGPointMake(0.5, 1)];
button.frame = oldFrame;
button.transform = CGAffineTransformMakeScale(1.0/scrollView.zoomScale, 1.0/scrollView.zoomScale);
}
Thank you in advance!
I find the best way to layout subviews is in the - (void) layoutSubviews method. You will have to subclass your UIImageView and override the method.
This method will automatically get called whenever your frame changes and also gets called the first time your view gets presented.
If you put all your layout code in this method, it prevents layout fragmentation and repetition, keeps your view code in your views, and most things just work by default.

drawRect cropping in UIScrollView

I am trying to draw an image into a UIView subclass overriding drawRect. This image drawing class needs moving and zooming capabilities, so I made it a subview of a scrollview.
My problem is that my image seems to get cropped, either by the screen or the scrollView bounds (the image is larger than the screen). I don't have a clue why, this seems to e pretty basic stuff.
The view hierarchy in my ViewController looks like this:
View Controller
View
ScrollView
MapView
I've created outlets for the scroll view and the map view (which is the view implementing drawRect).
My ViewController initial setup code for configuring the scroll view looks like this:
- (void)viewDidLoad
{
[super viewDidLoad];
scrollView.delegate = self;
scrollView.contentSize = CGSizeMake(450, 476);
scrollView.minimumZoomScale = 0.6;
scrollView.maximumZoomScale = 6.0;
}
My drawRect implementation:
- (void)drawRect:(CGRect)rect
{
UIImage *myImage = [UIImage imageNamed:#"map.jpg"];
CGRect rectangle = CGRectMake(0,0,450,476);
[myImage drawInRect:rectangle];
}
The image is draggable and zoomable within the scroll view, but it's cropped at what seems to be the screen bounds.
Any help would be appreciated.
drawRect can't draw outside the rect passed as a parameter to the drawRect method, which is determined by the view frame. Make your view 1200,1200 and make sure that clipToBounds is disabled on all the containing views.
Your problem is probably in drawInRect. From documentation of UIImage class:
drawInRect:
Draws the entire image in the specified rectangle, scaling it as needed to fit.
Make that rectangle bigger as you wish and center it in UIImageView. This question is answered here UIScrollView image/photo viewer with paging enabled and zooming

Custom NSView Fill Paints Over Bottom Bar

I have a window which has a custom NSView and has a bottom bar with controls on it, one of which is an NSColorWheel.
For simplicity sake the Window is 332px high, with the custom NSView being 300px high and the bottom bar being 32px high.
The bottom bar is created as part of my awakeFromNib when the app loads the window using the following code:
[[self window] setAutorecalculatesContentBorderThickness:YES forEdge:NSMinYEdge];
[[self window] setContentBorderThickness: 32.0 forEdge: NSMinYEdge];
In my custom NSView class I fill the rectangle with color. Everything works fine when the app loads using the following in my NSView class:
- (void)drawRect:(NSRect)dirtyRect
{
dirtyRect = [self bounds];
NSColor * mNewColor = [NSColor blackColor];
[mNewColor set];
[NSBezierPath fillRect:dirtyRect];
}
However, if I subsequently call a method that changes the color of the custom NSView when a color wheel in the bottom bar is changed, the bottom bar gets overwritten with the color. The following code illustrates this method (this code is in the custom NSView class:
- (void)changeBackgroundColor:(NSNotification *)notification
{
NSLog(#"Changed background color");
NSRect mRect = [self bounds];
NSColor * mNewColor = [theColorWell color];
[mNewColor set];
[NSBezierPath fillRect:mRect];
[self setNeedsDisplay:YES];
}
Resizing the window instantly corrects the problem, but obviously I don't want the user to have to resize the window for an obvious bug!
What I don't understand is why my bounds appear to be mapping to the parent window and not the custom NSView when I call setNeedsDisplay and yet the bound correctly adjust when I resize the window using the mouse (even if just by 1 pixel).
Do I somehow need to account for the bottom bar on the redraw?
Any and all help much appreciated.
You should do all your drawing in the drawRect: method of your custom NSView. Cocoa automatically sets up the graphics context for you when it calls this method - things may not draw correctly if you perform drawing operations in other methods.
Your code in drawRect: could set the colour to the the current background colour as specified by your NSColorWell and fill the dirtyRect rectangle with this.
Then in the other method just call [self setNeedsDisplay:YES]; and then drawRect: will automatically be called to redraw the view.
See here for more information: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CocoaViewsGuide/SubclassingNSView/SubclassingNSView.html#//apple_ref/doc/uid/TP40002978-CH7-SW4 (in particular the Drawing View Content section)