I have some code in the drawRect: method that draws a series of circles from an array. I can verify that the points stay in memory long enough and get to the point where they should be drawn. However, it seems as if the fill method of NSBezierPath is not drawing the circles.
Here is my drawRect:
- (void)drawRect:(NSRect)dirtyRect
{
NSPoint aPoint;
NSRect aRect = NSMakeRect(0, 0, 6, 6);
// Erase the rectangle
[[NSColor clearColor] set];
NSRectFill([self bounds]);
// Draw the dots
[[NSColor colorWithCalibratedRed:(77.0/255.0) green:(11.0/255.0) blue:(11.0/255.0) alpha:1.0] set];
for(NSValue *aValuePoint in arrayOfPoints) {
aPoint = [aValuePoint pointValue];
aRect.origin.y = aPoint.y - aRect.size.height/2.0;
aRect.origin.x = aPoint.x - aRect.size.width/2.0;
// The code does get to this point, it does not draw however...
[[NSBezierPath bezierPathWithOvalInRect:aRect] fill];
}
}
I have another method drawDotAtPoint: where I add the points to an array
- (void)drawDotAtPoint:(NSPoint)aPoint
{
NSLog(#"drawDotAtPoint:(%f, %f)", aPoint.x, aPoint.y);
[arrayOfPoints addObject:[NSValue valueWithPoint:aPoint]];
// I've also tried using [self superview] instead of just self
[self setNeedsDisplay: YES];
}
And while I am able to verify that these methods are called at the correct times (or so it seems) no drawing after the initial drawRect: will take place
Sidenote:
My goal is to actually draw a point and have a ring appear around it, enlarge and fade out. Much similar to the GPS current location indicator on iOS, in the Maps app. Any insight on that would also be appreciated.
Check your points and make sure that you don't have an inverted rectangle and that your X/Y coords are what you expect. Make sure that your anchor point and coordinates are correctly relative.
You may be helped along a bit by using a tool called Quartz Debug that comes with apple's developer tools. It is in the /Developer/Applications/Graphics Tools/ folder. After starting this tool, go to the "tools" menu and turn on "Flash Screen Updates". This will give you an idea of when things are being redrawn or not.
Hope that helps some.
I've copy-pasted your view code. I stuck this in an app delegate for testing. BadView is the custom view here (no reflection on you -- "Bad" because it's misbehaving :) ) The windows are both created in IB, and the WebView is likewise placed in the parent window in IB.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// badWindow is an IBOutlet to the overlay window, parentWindow an IBOutlet
// to the window with the webview, myWebView an IBOutlet to the webview
BadView * myBadView = [[BadView alloc] initWithFrame:[[badWindow contentView] frame]];
// Create some random points for testing
[myBadView createArrayOfPoints];
[badWindow setStyleMask:NSBorderlessWindowMask];
[badWindow setOpaque:NO];
[badWindow setAlphaValue:0.9]; // Will still act opaque without this
[badWindow setHasShadow:NO];
[badWindow setContentView:myBadView];
[parentWindow addChildWindow:badWindow ordered:NSWindowAbove];
[[myWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:
[NSURL URLWithString:#"http://www.apple.com/"]]];
[myBadView release];
}
Related
I'm a iOS developer, and recently I'm programming a desktop APP for MAC OSX. I still don't have much experience with the View's components of OSX, so maybe it's a silly or easy question, but I have made a little research about this problem and haven't found any solution yet.
Here's the problem:
I have a custom specialization of a NSView, that is used as the view of a Content ViewController used in my NSPopover.
Inside this view, that I'm calling "PopoverBackgroundView", I painted inside the drawRect this red background, and calculated another minor rect and painted with this gray-like color. Here's the code:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor colorWithDeviceRed:174/255.0 green:72/255.0 blue:72/255.0 alpha:1.0] setFill];
NSRectFill(dirtyRect);
[[NSColor colorWithDeviceRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0] setFill];
NSRectFill(NSMakeRect(BORDER_WIDTH, BORDER_WIDTH, dirtyRect.size.width - 2*BORDER_WIDTH, dirtyRect.size.height - 2*BORDER_WIDTH));
}
So, inside the PopoverBackgroundView.m I'm programatically creating a NSComboBox. This comboBox will have the numbers 1 to 10. When I allocate it, everything seems just fine:
The problem is, after I select any options inside the combobox, it's background somehow "goes away" became transparent, I don't know, and become like this:
Please notice the red-like frame (background color of the view) around the NSComboBox, that appeared just AFTER I select something.
Here's the code where I'm allocation the comboBox and initializing it:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
(...)
self.comboBox = [[NSComboBox alloc] initWithFrame:CGRectMake(15, frame.size.height - 55, 90, 25)];
self.comboBox.delegate = self;
[self.comboBox setDrawsBackground:NO];
[self.comboBox setSelectable:YES];
[self.comboBox setEditable:NO];
for (int i = 1; i<=10; i++)
{
NSString *mystr = [NSString stringWithFormat:#"%d", i];
[self.comboBox addItemWithObjectValue:mystr];
}
[self addSubview:self.comboBox];
}
return self;
}
Any idea how can I 'fix' this "selected background"? All that I want it's the selected state to be equals to the normal state, i. e. ,the comboBox should be always like the first image, even after the selection.
Is there something wrong with the allocation code? Something mission? I'm really thinking that just some property that I'm not using or initializing, but I couldn't find yet.
Thanks in advance,
Just to give a feedback, I finally was able to resolve my problem.
I don't know exactly why, but the problem was all caused because the way I was drawing my border in the drawRect. Somehow, these code
[[NSColor colorWithDeviceRed:174/255.0 green:72/255.0 blue:72/255.0 alpha:1.0] setFill];
NSRectFill(dirtyRect);
were been propagated by the subviews, don't know if was the setFill or the NSRectFill. So, the "background" of the NSCombobox was been painted with this color.
After seeing this post :Adding border and Rounded Rect in the NSView I changed my draw rect to this:
- (void)drawRect:(NSRect)dirtyRect
{
NSBezierPath *background = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:10 yRadius:10];
[[NSColor colorWithDeviceRed:174/255.0 green:72/255.0 blue:72/255.0 alpha:1.0] set];
[background setLineWidth:10];
[background stroke];
}
and everything is working fine now, as I wanted. Now, after I select my combobox, no strange background is been drawing.
If someone knows whey this was happening with the previous code, please, let me know.
I'm customizing the UI for one of my apps, and the idea is that a text area is initially bordered gray when out of focus, and when it comes into focus, the border becomes bright white. My app uses a dark theme, and for a single-lined NSTextField, this works great.
I'm running into problems with a subclassed NSTextView, however. In order to alter the border properly, I ended up having to actually subclass the parent NSScrollView, but am still seeing strange behavior. (See screenshot below.) I want the red box to fill the entire scroll view, as this would allow me to stroke (instead of filling, which is just for testing) the path, producing a nice border. Instead, the red box seems to be only filling to the internal child view.
The following code snippet, which is for the NSScrollView subclass:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSRect borderRect = self.bounds;
borderRect.origin.y += 1;
borderRect.size.width -= 1;
borderRect.size.height -= 4;
BOOL inFocus = ([[self window] firstResponder] == self);
if (!inFocus) {
inFocus = [self anySubviewHasFocus:self];
}
if (inFocus) {
[[NSColor colorWithDeviceRed:.8 green:.2 blue:0 alpha:1] set];
} else {
[[NSColor colorWithDeviceRed:.1 green:.8 blue:0 alpha:1] set];
}
[NSGraphicsContext saveGraphicsState];
[[NSGraphicsContext currentContext] setShouldAntialias:NO];
[NSBezierPath fillRect:borderRect];
[NSGraphicsContext restoreGraphicsState];
NSLog(#"My bounds: %#", NSStringFromRect(borderRect));
NSLog(#"Super (%#) bounds: %#", [self superview], NSStringFromRect(borderRect));
}
Produces the screenshot as seen below. Also, see the output in the log, which suggests that the entire view should be filled. This is the only output that is ever shown, regardless of the size of the text inside. Entering carriage returns increases the height of the red box, but does not produce different output. (And I would like the red box to fill the entire bounds.)
2011-04-08 21:30:29.789 MyApp[6515:903] My bounds: {{0, 1}, {196, 87}}
2011-04-08 21:30:29.789 MyApp[6515:903] Super (<EditTaskView: 0x3a0b150>) bounds: {{0, 1}, {196, 87}}
Edit: Thanks to Josh Caswell for his answer. See below for the proper behavior when not focused, and when focused.
As ughoavgfhw noted, NSScrollView doesn't usually do any drawing, and probably has a weird interaction with its child views in that way. I'd suggest putting something like the following in your text view's drawing code to draw this custom focus ring that you want*:
// We're going to be modifying the state for this,
// so allow it to be restored later
[NSGraphicsContext saveGraphicsState];
// Choose the correct color; isFirstResponder is a custom
// ivar set in becomeFirstResponder and resignFirstResponder
if( isFirstResponder && [[self window] isKeyWindow]){
[myFocusedColor set];
}
else {
[myNotFocusedColor set];
}
// Create two rects, one slightly outset from the bounds,
// one slightly inset
NSRect bounds = [self bounds];
NSRect innerRect = NSInsetRect(bounds, 2, 2);
NSRect outerRect = NSMakeRect(bounds.origin.x - 2,
bounds.origin.y - 2,
bounds.size.width + 4,
bounds.size.height + 4);
// Create a bezier path using those two rects; this will
// become the clipping path of the context
NSBezierPath * clipPath = [NSBezierPath bezierPathWithRect:outerRect];
[clipPath appendBezierPath:[NSBezierPath bezierPathWithRect:innerRect]];
// Change the current clipping path of the context to
// the enclosed area of clipPath; "enclosed" defined by
// winding rule. Drawing will be restricted to this area.
// N.B. that the winding rule makes the order that the
// rects were added to the path important.
[clipPath setWindingRule:NSEvenOddWindingRule];
[clipPath setClip];
// Fill the rect; drawing is clipped and the inner rect
// is not drawn in
[[NSBezierPath bezierPathWithRect:outerRect] fill];
[NSGraphicsContext restoreGraphicsState];
This should be a reasonable approximation of what AppKit does when it draws a focus ring. Of course, AppKit is sort of allowed to draw outside a view's bounds -- I can't guarantee that this is completely safe, but you seem to get a margin of 3 px to play with. You could draw the ring entirely inside the bounds if you wanted. A true focus ring extends slightly (2 px) inside the view anyways (as I've done here).
Apple docs on Setting the Clipping Region.
EDIT: After re-reading your comments on the question, I realize I may have long-winded-ly buried the real answer. Try either subclassing NSClipView and switching your scroll view's clip view for that, or using a custom view for the document view.
*: You could also put this in the drawing code of a custom view subclass which is set as the document view of the NSScrollView; then your text view could be a subview of that. Or substitute a custom NSClipView subclass.
When subclassing an CALayer and implementing the drawInContext method, I would assume that any drawing I do within there is all that will show up, but instead if I set (for example) borderWidth/borderColor then CALayer will draw a border on it's own above all my custom drawing code.
This is a CALayer subclass:
#implementation MyCustomCALayer
- (id)init
{
self = [super init];
if(self)
{
[self setNeedsDisplayOnBoundsChange:YES];
}
return self;
}
- (void)drawInContext:(CGContextRef)context
{
CGRect rect = CGContextGetClipBoundingBox(context);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
#end
Created in a UIView something like:
- (void)ensureLayer
{
if(myLayer)
return;
myLayer = [[[MyCustomCALayer alloc] init] autorelease];
myLayer.borderColor = [UIColor greenColor].CGColor;
myLayer.borderWidth = 1;
myLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
[super layoutSublayersOfLayer:layer];
[self ensureLayer];
if(![[layer.sublayers objectAtIndex:0] isEqual:myLayer])
[layer insertSublayer:myLayer atIndex:0];
}
What happens, is the MyCustomCALayer fills a rectangle with red, this is what I would expect to see and nothing else, since i've implemented the drawInContext method, but instead I see a red rectangle with a green border on top, always on top, i've tried just about every combination I can think of to get rid of the green border being drawn and cannot figure it out.
My reasoning is I would like to use the borderWidth and borderColor and other properties of the CALayer instead of creating my own properties, because the code that I need to draw contains a border, a fill, etc... but the rendering I need to do is not a simple shape. So the only way i've found around this is to set the borderWidth to 0 and add my own property to my subclass, like myBorderWidth, which is ridiculous.
This is done with the latest iOS SDK, but i'd imagine it's the same for Mac.
Hope this makes sense, any thoughts?
Thanks!
You’re out of luck; CoreAnimation doesn’t support overriding its implementation of rendering for the basic layer properties. Please do file a bug.
Whats the best way to go about building an address field like the one in safari?
Needs to have editable text, and determinate progress indicator background.
You could just subclass NSTextField and override the -drawRect: method to "fill" the appropriate percentage of the entire width with some color or gradient (or whatever) for the progress. If I'm understanding your question right.
-(void)drawRect:(NSRect)dirtyRect {
CGFloat progress = 0.33;
NSRect progressRect = [self bounds];
progressRect.size.width *= progress;
[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:1.0 alpha:0.4] set];
NSRectFillUsingOperation(progressRect, NSCompositeSourceOver);
[super drawRect:dirtyRect];
}
Obviously "progress" would come from a property you declare and be updated according to the model. You'd need to make sure setDrawsBackground: is turned off, or the background is set to [NSColor clearColor]; in order for your custom drawing to be seen.
This is a shot from the code above.
Is there any way to overlay the NSScroller over the content of the scroll view (like in iOS)? I've already tried several approaches:
a) setting the frame of the scroll view content view (NSClipView) to extend into the bounds of the scroller
b) adding an NSScroller object as a subview of the scroll view (positioned where I want)
c) creating an entirely custom scroller view and placing it as a subview (this worked, but that would mean that I need to rewrite all the functionality of NSScroller)
Sparrow seems to successfully do this, and it seems to do it through a regular NSScroller subclass (seeing as it responds to the scroll settings set in System Preferences >> Appearance). It's not really drawing the scroller that's the issue, its just making it overlay the content.
Any advice is appreciated :-)
Here's where you can set the custom class of your scrollbars.
After that, by overriding the -tile method of NSScrollView, you'll get them placed properly.
Here is my solution :
Create a MyScroller class that extends NSScroller
In the MyScroller.m :
#import "MyScroller.h"
#implementation MyScroller
+(CGFloat) scrollerWidth{
return 10;
}
+(CGFloat) scrollerWidthForControlSize:(NSControlSize)controlSize{
return 10;
}
- (void) drawBackground:(NSRect) rect{
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:0 yRadius:0];
[[NSColor whiteColor] set];
[path fill];
}
- (void)drawKnob{
[self drawBackground:[self rectForPart:0]];
[self drawBackground:[self rectForPart:1]];
[self drawBackground:[self rectForPart:2]];
[self drawBackground:[self rectForPart:4]];
[self drawBackground:[self rectForPart:5]];
[self drawBackground:[self rectForPart:6]];
NSRect knobRect = [self rectForPart:NSScrollerKnob];
NSRect newRect = NSMakeRect((knobRect.size.width - [MyScroller scrollerWidth]) / 2, knobRect.origin.y, [MyScroller scrollerWidth], knobRect.size.height);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:newRect xRadius:5 yRadius:5];
[[NSColor grayColor] set];
[path fill];
}
#end
Then just set the custom class for the Scroller in Interface Builder.
I've recently released RFOverlayScrollView which should solve your problem:
Source: https://github.com/rheinfabrik/RFOverlayScrollView
Blog Post: http://blog.rheinfabrik.de/blog/2013/01/01/introducing-rfoverlayscrollview/
You can change the scrollerStyle over the NSScrollerView control to .overlay to get the desired overlay effect.
This may overrule the system wide setting in System Settings for scroll bar visibility. To 'survive' scroll bar setting changes while your app is running, you must observe the NSScroller.preferredScrollerStyleDidChangeNotification and re-apply the overlay effect.