Changing the background color of an NSCollectionView - cocoa

Is it a chance to programatically change the background color of NSCollectionView?
I was trying subclassing.. but not working..
#interface CollectionViewBg : NSCollectionView
in .m
[self setBackgroundColors:[NSArray arrayWithObjects:[NSColor blueColor], nil]];

In .m, remove this line :
[self setBackgroundColors:[NSArray arrayWithObjects:[NSColor blueColor], nil]];
And use this code:
- (void)drawRect:(NSRect)dirtyRect{
[[NSColor blueColor] setFill];
NSRectFill(dirtyRect);
}
Also don't forget to change class of NSCollectionView object in IB to CollectionViewBg.
Hope this helps :)

Guess subclassing isn't a great idea, because most of Cocoa controls provide various techniques to avoid subclassing in order to customize appearance and behavior.
In this particular case, you can set backgroundView property to your NSCollectionView and provide a custom view that is dedicated for drawing a custom background.
If you need to change only background color, then you can consider take advantage of the fact that collection view usually resists inside scroll view. Just set backgroundColor and drawsBackground properties of NSScrollView.

Here's a more modern solution. First, make yourself an NSView subclass that draws the effect you want to achieve. Here's a simple one that paints a block colour:
class ColouredView: NSView {
#IBInspectable var color: NSColor = .windowBackgroundColor
override func draw(_ dirtyRect: NSRect) {
color.setFill()
dirtyRect.fill()
}
}
Now go to your nib or storyboard and drag a custom view onto the left bar. We don't want it to be part of the view hierarchy, but we do want it to be listed under the view controller containing the collection view. Just drag it in below 'First Responder'. You can then set up any important properties or relationships - in my case, the background colour.
Finally, right-click your collection view and connect its 'background view' outlet to your custom view. This lets you do all the setup in your storyboard. If you like you can make your custom view #IBDesignable but I find that usually causes more trouble than it's worth.

Related

Make compiler aware of NSView subclass methods when building ViewController's view programmatically

I have created a subclass of NSView that allows me to easily change the background color via the method - (void)setBackgroundColor:(NSColor *)aBackgroundColor.
I want my view controller's main view to be this subclass, so I initiate it with self.view = [[BetterNSView alloc] initWithFrame....
On the next line, I try to set its background color: [self.view setBackgroundColor:[NSColor greenColor]]. But the compiler complains, saying that NSView doesn't have the method setBackgroundColor.
To solve this, I need to use typecasting: [(BetterNSView *)self.view setBackgroundColor:[NSColor greenColor]];. That works. But I'd like to not need the typecasting.
How do I tell the compiler that the view property of the view controller is the BetterNSView subclass? Remember, I'm not using IB.
You have two reasonable choices.
Make an accessor on your view controller with the right type that just passes through to the view property.
Deal with the typecast.
Really, this is one of this situations where typecasting is okay.

nssplitview programmatically add nsview on top

I am trying to programmatically add a NSView over a NSSplitView (to cover it). Every attempt to do this has resulted in it being added into the NSSplitview as an extra subview.
Can anyone please help?
Codes:
InfoTrainView *myView = [[[InfoTrainView alloc] initWithFrame: aFrame] autorelease];
[NSBundle loadNibNamed:#"InfoTrainView" owner:myView];
[self.windowController.splitViewBase addSubview:myView];
I believe you need to add your new view as a child to the NSSplitView's superview (ie. parent). This way it becomes a sibling to the NSSplitView and can cover it. Your current method makes the new view a child of the split view, which then sets itself up as splitting four ways.
NSView* parentView = [self.windowController.splitViewBase superview];
if (parentView)
[parentView addSubview:myView];
parentView above should get you the "content view" that is the default NSView inside of an NSWindow, which IB placed the NSSplitView inside. If parentView == nil, you can try manually adding an NSView to the window first, then putting your NSSplitView inside of that.
On a side note, your question answered my own question - how to programmatically add to the views split inside of an NSSplitView! Thanks :)

NSView inside NSScrollview won't change size

I've made an NSWindow in Interface Builder. Inside this window is an NSScrollView and inside that is a custom NSView.
The NSScrollview fills the NSWindow and the custom NSView fills the NSScrollview.
When the custom NSView is sent the awakeFromNib method its bounds are 0,0 and 256x373 as I'd expect, filling the scrollview.
However later I change the size of the NSView to be larger than 373high but it never changes size in the scrollview.
I've tried setting the frame, I've tried setting the bounds, but nothing makes it change.
Except, when I tried changing the intrinsicSize of the custom NSView it did change, but it made the NSWindow and NSScrollview change sizes as well to fit the new size of 256x1452
Can anyone tell me where I might be going wrong?
Is it something to do with the constraints set on the Scrollview or the NSView? I haven't set any but when I added the items in Interface Builder they were automatically added for me
[EDIT]
I've changed it so that the custom NSView is created programmatically and added to the NSScrollView with setDocumentView: and everything works as I expect. So I guess technically I've solved the problem, but I'd still like an explanation on why it's not working via Interface Builder if anyone knows.
I have a partial solution, which also causes me to pose an additional question. I had a similar issue, I needed to programatically change the size of a view embedded in a NSScrolView.
This code works, need both methods
-(void)markViewSizeChanged /* Works correctly */
{
[self setFrameSize:currentViewSize];
[self setBoundsSize:currentViewSize];
[self setNeedsDisplay:YES];
}
-(NSSize)intrinsicContentSize // Override of class method
{
return currentViewSize;
}
Note: MUST set currentViewSize in awakeFromNib
Now for the curious part. If I reverse the order of the two calls setting the frame and bounds, the size of the embedded view is correct, but the scaling factor of objects drawn is off.
-(void)markViewSizeChanged /* DOES NOT work correctly, scaling in drawing off */
{
[self setBoundsSize:currentViewSize];
[self setFrameSize:currentViewSize];
[self setNeedsDisplay:YES];
}

How to change the color of a NSToolbarItem's label

I'd like to change the color of all of my NSToolbarItem's labels.
I need to set it to white because it suits better to the background color of my NSWindow, but it defaults to black and I haven't found a way to change it neither in Interface Builder nor directly by code (NSToolbarItem implements setLabel, but it just sets the text string).
If possible, I'd like to avoid:
Replacing the whole NSToolbar by a custom NSView. Would feel like reinventing the wheel to me.
Having to create custom NSViews inside NSToolbarItem. It would imply having to leave blank all of its labels and adding the white-colored label inside the custom view.
In case anyone is interested, I solved it by:
Using custom views inside NSToolbarItems containing both a button and a label.
Displaying icon only instead of icon + label in NSToolbar in order to hide the default label.
I had to deal with another problem related to a bug with Interface Builder: the custom view was not showing at all. I was able to fix it thanks to this answer.
You can change it using NSMutableAttributeString.
For example:
-(void) awakeFromNib{
NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:self.label];
NSRange titleRange = NSMakeRange(0, title.length);
[title addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:titleRange];
[self setLabel:title]; }
You have to subclass NSBarButtonItem and override the drawRect method for that. Otherwise it will use [NSColor controlTextColor] or [NSColor disabledControlTextColor].

Cocoa: NSView drawRect painting over IBOutlets

I have an NSView in IB which sits above the app window. I have a subclass of NSView (AddSource) which I assign to the NSView.
On awakeFromNib I instantiate the view:
//add a new Add Source class
addSourceView = [[AddSource alloc] initWithFrame:NSMakeRect(0.0, 959.0, 307.0, 118.0)];
[[winMain contentView] addSubview:addSourceView];
in addSourceView's drawRect method I am adding a white background to the view:
[[NSColor whiteColor] set];
NSRectFill(rect);
[self setNeedsDisplay:YES];//added this to see if it might solve the problem
In winMain's contentView I have a NSButton that when clicked slides the addSourceView onto the window:
NSRect addSourceViewFrame = [addSourceView frame];
addSourceViewFrame.origin.y = 841.0;
[[addSourceView animator] setFrame:addSourceViewFrame];
But it seems as if the app is painting over the IBOutlets I placed on the NSView in IB. If, in IB, I repoistion the NSView so that it is on screen when the app launches everything works fine, the IBOutlets are there as well as the background color.
I'm not sure why this is happening. I've done this before with no problems. I must be doing something different this time.
Thanks for any help.
*note - on the 3rd screen capture, when I say this is what the app looks like when opened, that's when I hard code the Y position of the NSView. When it is functioning correctly it should open as screen capture 1.
Most likely your buttons and custom view are siblings, i.e. they are both subviews of your window's content view. Since siblings are "Stacked" depending on the order in which they are added, when you add the view in code it is being added on top of the buttons. You should be able to fix it by explicitly specifying where the view should be positioned relative to its new siblings like so:
[[winMain contentView] addSubview:addSourceView positioned:NSWindowBelow relativeTo:nil];
which should place it below any existing subviews of your window's content view. Also, remove the setNeedsDisplay: line in drawRect, that leads to unncessary, possibly infinite, redrawing.
EDIT: OK I see what you're doing.
I would suggest creating a standalove view in the NIB by dragging a "Custom View" object into the left hand side (the vertically-aligned archived objects section) and adding your controls there, that should ensure the controls are actualy subviews of the view, then you can just create a reference to the archived view in code, and add/remove it dynamically as needed.
Honestly though, you should probably be using a sheet for these kinds of modal dialogs. Why reinvent the wheel, and make your app uglier in the process?
You added TWO AddSource views to the window. You added one in IB - this view contains your textFields and buttons that are connected to the IBOutlets and it is positioned outside the window.
Then in -awakeFromNib you create another, blank AddSource view (containing nothing) and animate it into the window.
I can't recommend highly enough the Hillegass as the best introduction to IB and the correct way to build Cocoa Apps.
Also, Assertions can be useful to make sure what you think is happening is actually what is happening.
If you are certain you added a button to your view in IB, assert it is so:-
- (void)awakeFromNib {
NSAssert( myButton, #"did i hook up the outlet?");
}
NSAssert is a macro that has zero overhead in a release build.
Calling [self setNeedsDisplay:YES] from -drawRect just causes the same -drawRect to be called again. This will give you big problems.

Resources