Why does adding a top border to a subview does not resize with auto layout/device rotation? - objective-c

(using Xcode 6 and iOS 8)
How can I create a top border in a sub UIView that sizes correctly when the view changes due to auto layout or device rotation?
I have code draws a line as the top border but it uses the size of what I have in Interface Builder. It does not size correctly after auto layout resizes the view nor after device rotation.
In the UIViewController, I tried [self.view layoutSubviews], [self.view layoutIfNeeded], [self.myCustomView layoutIfNeeded], and so on, in following method because when this method is called, the view has been resized by auto layout. But doesn't change the size of the line.
UIViewController:
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.myCustomView layoutIfNeeded];
}
Hierarchy is:
UIViewController -> ViewController's View -> my custom UIView
Tried 2 different methods to draw the border which do draw the lines in my-custom-uiview. Neither one is recalled after the view is resized by auto layout or device rotation. This really shouldn't be that hard!
myCustomUIView:
First method:
// in initWithCoder, initWithFrame, awakeFromNib methods
CALayer *border = [CALayer layer];
border.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, 2.0f);
border.backgroundColor = [UIColor blackColor].CGColor;
[self.contentView.layer addSublayer:border];
Second method:
- (void) drawRect:(CGRect)rect {
UIBezierPath *line = [UIBezierPath bezierPath];
[line moveToPoint:CGPointMake(0, 0)];
[line addLineToPoint:CGPointMake(self.bounds.size.width, 0)];
[line setLineWidth:2.0f];
[[UIColor blackColor] setStroke];
[line stroke];
}

Related

Drawing issues after subclassing NSCollectionView

OK, here's what I have done:
I have an NSCollectionView
I wanted to be able to enable "selecting" items, and drawing a custom border when an items is selected
I subclassed NSCollectionViewItem (to enable selection)
I subclassed NSView for the NSCollectionViewItem view, in order to draw the border
The code
The view item
#implementation MSLibraryCollectionViewItem
- (void)setSelected:(BOOL)flag
{
[super setSelected:flag];
[(MSLibraryCollectionViewView*)[self view] setSelected:flag];
[(MSLibraryCollectionViewView*)[self view] setNeedsDisplay:YES];
}
The custom view
#implementation MSLibraryCollectionViewView
/***************************************
Initialisation
***************************************/
- (MSLibraryCollectionViewView*)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
/***************************************
Drawing
***************************************/
- (void)drawRect:(NSRect)rect
{
if ([self selected]) {
//[[NSColor redColor] setFill];
//NSRectFill(rect);
//[super drawRect:rect];
NSColor* gS = [NSColor colorWithCalibratedRed:0.06 green:0.45 blue:0.86 alpha:1.0];
NSColor* gE = [NSColor colorWithCalibratedRed:0.12 green:0.64 blue:0.94 alpha:1.0];
NSGradient* g = [[NSGradient alloc] initWithStartingColor:gE endingColor:gS];
NSColor *borderColor = [NSColor colorFromGradient:g];
NSRect frameRect = [self bounds];
if(rect.size.height < frameRect.size.height)
return;
NSRect newRect = NSMakeRect(rect.origin.x+5, rect.origin.y+5, rect.size.width-10, rect.size.height-10);
NSBezierPath *textViewSurround = [NSBezierPath bezierPathWithRoundedRect:newRect xRadius:7 yRadius:7];
[textViewSurround setLineWidth:2.0];
[borderColor set];
[textViewSurround stroke];
}
}
However, the seems to be something wrong with drawing. For example:
When resizing the Collection View's container, a weird line appears at the outer box
When an Collection View item is not 100% visible (e.g. because it's been scrolled down), the selection border doesn't appear at all (while I would expect it to draw just the visible portion).
Some Examples
What's going on?
P.S. I'm not a guru with drawing and custom views in Cocoa - so any ideas/help is more than welcome!
You switched from asking about a collection view to talking about an outline view, but I assume that was just a mental hiccup.
When an Outline View item is not 100% visible (e.g. because it's been scrolled down), the selection border doesn't appear at all
(while I would expect it to draw just the visible portion).
That's because of this code in your -drawRect:.
if(rect.size.height < frameRect.size.height)
return;
It's specifically avoiding drawing a partial selection outline.
Regarding the weird line, I doubt that has to do with your collection item view's custom drawing. Does it stop happening if you disable the custom drawing? You could experiment with using an ordinary color rather than using the third-party +colorFromGradient: code you're using.
By the way, this line:
NSRect newRect = NSMakeRect(rect.origin.x+5, rect.origin.y+5, rect.size.width-10, rect.size.height-10);
could be written more simply as:
NSRect newRect = NSInsetRect(rect, 5, 5);

Rounded NSView in a Transparent Window

I'm trying to make a transparent NSWindow with a rounded view in there.
I'm trying to have a rounded view with a transparent window.
This is what it looks like now: (see the little dots in the corners)
Here's another example with the border radius set to 10px (set in NSView drawRect):
I am using code from this Apple sample: https://developer.apple.com/library/mac/#samplecode/RoundTransparentWindow/Introduction/Intro.html
Specifically this method in my NSWindow subclass:
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)aStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)flag {
// Using NSBorderlessWindowMask results in a window without a title bar.
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self != nil) {
// Start with no transparency for all drawing into the window
[self setAlphaValue:1.0];
// Turn off opacity so that the parts of the window that are not drawn into are transparent.
[self setOpaque:NO];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
And this in my NSView subclass:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSBezierPath* thePath = [NSBezierPath bezierPath];
[thePath appendBezierPathWithRoundedRect:dirtyRect xRadius:3 yRadius:3];
[thePath fill];
}
Can anyone tell me what I'm missing here?
Thanks.
Are you looking for something like the following, where there's a red outline (stroke), but the center area is transparent?
If so, to achieve that, I used the following code:
- (void)drawRect:(NSRect)frame {
frame = NSInsetRect(self.frame, 3.0, 3.0);
[NSBezierPath setDefaultLineWidth:6.0];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame
xRadius:6.0 yRadius:6.0];
[[NSColor redColor] set];
[path stroke];
}
If that's what you're looking for, you can probably use that as a starting point. You'll want to make sure that you inset the frame rect one half of the stroke line width, so as to avoid the problem with clipping the corners like you were seeing.
Not sure if this is what you are looking for but there is a great class by Matt Gemmell called MAAttachedWindow and can be found here: http://mattgemmell.com/2007/10/03/maattachedwindow-nswindow-subclass/
It's a little older but still works great for me when I need to do a 'floating' popup window and configure transparency, border radii, and even add a small arrow for context if desired. I use it all the time.

How to add an NSView to NSWindow in a Cocoa app?

Since the template of an OS X app in Xcode seems to be similar to an empty app template, the following is used to add a view and a button (trying not to use Interface builder for now):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 100, 100)];
view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:view];
NSRect frame = NSMakeRect(10, 40, 90, 40);
NSButton* pushButton = [[NSButton alloc] initWithFrame: frame];
pushButton.bezelStyle = NSRoundedBezelStyle;
[self.window.contentView addSubview:pushButton];
NSLog(#"subviews are %#", [self.window.contentView subviews]);
}
Similar code on iOS should have produced a yellow box and a button, but the code above only produce a button, but the view won't show. Is there something wrong with the code above, and how to make it show the view with a yellow background?
Use setWantsLayer: method of NSView class.
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 100, 100)];
[view setWantsLayer:YES];
view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:view];
NSRect frame = NSMakeRect(10, 40, 90, 40);
NSButton* pushButton = [[NSButton alloc] initWithFrame: frame];
pushButton.bezelStyle = NSRoundedBezelStyle;
[self.window.contentView addSubview:pushButton];
NSLog(#"subviews are %#", [self.window.contentView subviews]);
To expand on the suggestion by Kevin Ballard, the classic way to do this is to subclass NSView and override the -drawRect: method. NSRectFill is a very convenient function for filling a rectangle without having to create a bezier path:
- (void)drawRect:(NSRect)rect
{
[[NSColor yellowColor] set];
NSRectFill(rect);
}
NSViews in Cocoa are, by default, not layer-backed. I suspect that if you type
NSLog(#"%#", view.layer);
you will see that it is nil.
In iOS, all views have layers. But on OS X, views don't have layers. In addition, there's 2 "modes" of layer-backed views on OS X. There's what's called a "layer-backed views" and a "layer-hosting view". A layer-backed view uses a CoreAnimation layer to cache drawn data, but you are not allowed to interact with the layer in any way. A layer-hosting view uses a CALayer that you explicitly provide, and you may mess with that layer all you want. However, with a layer-hosting view you may not add any subviews, or use the built-in NSView drawing mechanism. A layer-hosting view must only be used as the root of a CoreAnimation layer hierarchy.
Given all this, you should probably avoid using CoreAnimation at all for your view.
It's possible that an NSBox will do what you want. You can certainly set a fill color there, turn off the border, and set the style to custom. I'm just not 100% certain it will draw as a simple filled rectangle of color. Alternatively you can define your own NSView subclass that draws a color in -drawRect:.

NSView with fill ( pattern image) scrolls when window changes size

I have an NSView with a drawRect
- (void)drawRect:(CGRect)rect {
// Drawing code
NSPoint origin = [self visibleRect].origin;
[[NSGraphicsContext currentContext]
setPatternPhase:NSMakePoint(origin.x, origin.y)];
[[NSColor colorWithPatternImage: self.image] set];
[NSBezierPath fillRect: [self bounds]];
}
It draws my pattern perfectly, but i can see the pattern scroll when i change the the size of my window.
i have tried to set the view isFlipped to YES but that doesn't change anything.
You need to do some off-screen drawing first and then draw that result onto the view. For example you can use a blank NSImage of the exact same size as the view, draw the pattern on that image and then draw that image on the view.
Your code may look something like that:
- (void)drawRect:(NSRect)dirtyRect
{
// call super
[super drawRect:dirtyRect];
// create blank image and lock drawing on it
NSImage* bigImage = [[[NSImage alloc] initWithSize:self.bounds.size] autorelease];
[bigImage lockFocus];
// draw your image patter on the new blank image
NSColor* backgroundColor = [NSColor colorWithPatternImage:bgImage];
[backgroundColor set];
NSRectFill(self.bounds);
[bigImage unlockFocus];
// draw your new image
[bigImage drawInRect:self.bounds
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0f];
}
// I think you may also need to flip your view
- (BOOL)isFlipped
{
return YES;
}
Swift
A lot has changed, now things are easier, unfortunately part of objective-C's patrimony is lost and when it comes to Cocoa, Swift is like an orphan child. Anyways, based on Neovibrant's we can deduct the solution.
Subclass NSView
Override draw method
Call parent method (this is important)
Set a fill on buffer within the bounds of the view
Draw fill on buffer
code
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let bgimage : NSImage = /* Set the image you want */
let background = NSColor.init(patternImage: bgimage)
background.setFill()
bgimage.draw(in: self.bounds, from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
}

How to draw over a subview of NSView

I'm trying to draw at the top of my NSView which has some subviews.
In fact I'm trying to reproduce the connection line style of Interface Builder. Here is the code I'm using for the moment:
- (void)drawRect:(CGRect)dirtyRect
{
// Background color
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
// Draw line
if(_connecting)
{
CGContextRef c = [[NSGraphicsContext currentContext] graphicsPort];
[[NSColor redColor] setStroke];
CGContextMoveToPoint(c, _start.x, _start.y);
CGContextAddLineToPoint(c, _end.x, _end.y);
CGContextSetLineWidth(c, LINE_WIDTH);
CGContextClosePath(c);
CGContextStrokePath(c);
}
}
The first part is to color my NSView (if you know an other way, tell me please 'cause I come from iPhone development and I miss the backgroundColor property of UIView)
Then if a connection if detected, I draw it with 2 NSPoints. This code works but I didn't get it to draw over subviews, only on the first NSView.
A parent view cannot draw over its subviews. You would have to place another view over the subviews and draw the line there.