How to change color of divider in NSSplitView? - cocoa

Can we change the color of the divider?
Apple documentations says, that we can override -dividerColor in subclass of NSSplitView for this, but it doesn't works for me, or my understanding isn't correct. Also I've try create color layer over divider, e.g.:
colorLayer = [CALayer layer];
NSRect dividerFrame = NSMakeRect([[self.subviews objectAtIndex:0] frame].size.width, [[self.subviews objectAtIndex:0] frame].origin.y, [self dividerThickness], self.frame.size.height);
[colorLayer setBackgroundColor:[color coreGraphicsColorWithAlfa:1]];
[colorLayer setFrame:NSRectToCGRect(dividerFrame)];
[self.layer addSublayer:colorLayer];
Not works.

This answer may be late but:
If you are using Interface Builder, it is possible to change the property by going to the Identity Inspector of the NSSplitView (cmd+alt+3) and adding a User Defined Runtime Attribute for dividerColor of the type Color.

Actually, simply subclassing NSSplitView and overriding -(void)dividerColor works, but works only for thin or thick divider.
I've created simple configurable split view like this:
#interface CustomSplitView : NSSplitView
#property NSColor* DividerColor
#end
#implementation CustomSplitView
- (NSColor*)dividerColor {
return (self.DividerColor == nil) ? [super dividerColor] : self.DividerColor;
}
#end
Then in Interface Builder specify custom class for your split view to be CustomSplitView and add new user defined runtime attribute with key path = DividerColor, type = Color and select desired splitter color.

I've tried subclassing - (void)dividerColor too and I'm not sure why it doesn't work even though I know it's being called (and it's in the documentation).
One way to change the color of the divider is to subclass - (void)drawDividerInRect:(NSRect)aRect. However, for some reason, this method isn't called and I've checked all over the web for answers, but couldn't find anything, so I ended up calling it from drawRect. Here is the code for the subclassed NSSplitView:
-(void) drawRect {
id topView = [[self subviews] objectAtIndex:0];
NSRect topViewFrameRect = [topView frame];
[self drawDividerInRect:NSMakeRect(topViewFrameRect.origin.x, topViewFrameRect.size.height, topViewFrameRect.size.width, [self dividerThickness] )];
}
-(void) drawDividerInRect:(NSRect)aRect {
[[NSColor redColor] set];
NSRectFill(aRect);
}

Based on Palle's answer, but with the possibility to change the color dynamically in code, I'm currently using this solution (Swift 4):
splitView.setValue(NSColor.red, forKey: "dividerColor")
If your splitview control is part of a NSSplitViewController, you should use something like this:
splitViewController?.splitView.setValue(NSColor.red, forKey: "dividerColor")

In Swift and on macOS 11 I was able to achieve this by simply subclassing the NSSPlitView and only override drawDivider()
import Foundation
import AppKit
class MainSplitView: NSSplitView {
override func drawDivider(in rect: NSRect) {
NSColor(named: "separatorLinesColor")?.setFill()
rect.fill()
}
}
I had previously tried some of the other way, listed in here and what used to work stopped working with macOS 11... but it seems that this works.

One important point I haven't seen mentioned anywhere is that if you are overriding drawRect in a split view then you must call super -- otherwise drawDividerInRect: is never called. So, it should go something like this:
- (void)drawRect:(NSRect)dirtyRect {
// your other custom drawing
// call super last to draw the divider on top
[super drawRect:dirtyRect];
}
- (void)drawDividerInRect:(NSRect)aRect {
[[NSColor blueColor] set];
NSRectFill(aRect);
}

Related

How to add subview in ListView?

I am developing my first MAC application, i downloaded one Example of PxListView
and i have to added one button and background image on cell xib and bind them with controller
and, when on button click i was set height of that cell is much bigger then other. that is done,
and work fine.
but now i want to develop like after is witch cell has open in that cell i want to add some extra contain (Controller) on it, so how it will possible using given example?
pls help me to give some suggest how it will be done.
for Ex like before click on button
after chick on button i want to develop like
You write
i have to added one button and background image on cell xib and bind them with controller
It sounds like you've subclassed PXListViewCell--for convenience, let's call your subclass TemplateListViewCell--and added a xib from which instances of TemplateListViewCell will be loaded in
+[PXListViewCell cellLoadedFromNibNamed:bundle:reusableIdentifier:]
In addition, there is a[t least one] button in TemplateListViewCell.xib.
You write
when on button click i was set height of that cell is much bigger then other. that is done, and work fine
It sounds like this button has as its action a method on TemplateListViewCell such as
- (IBAction)toggleDetail:(id)sender
{
//Code to grow or shrink the height of [self frame].
//...
}
In my approach to implementing -toggleDetail, two modifications to the PXListView files were necessary:
1. Adding a protocol method
- (void)listView:(PXListView *)aListView setHeight:(CGFloat)height ofRow:(NSUInteger)row;
to the PXListViewDelegate protocol.
2. Adding a property
#property (nonatomic, assign) BOOL expanded;
to PXListViewCell.
My implementation of -toggleDetail looks something like this:
- (IBAction)toggleDetail:(id)sender
{
BOOL wasExpanded = [self expanded];
NSRect oldFrame = [self frame];
CGFloat oldHeight = oldFrame.size.height;
CGFloat newHeight = oldHeight;
CGFloat heightIncrement = 0.0f;
if (wasExpanded) {
heightIncrement = -80.0f; //use whatever value is appropriate
} else {
heightIncrement = 80.0f; //use whatever value is appropriate
}
newHeight += heightIncrement;
[[[self listView] delegate] listView:[self listView] setHeight:newHeight ofRow:[self row]];
[[self listView] reloadData];
BOOL isExpanded = !wasExpanded;
[self setExpanded:isExpanded];
}
It might seem better to use [[self listView] reloadRowAtIndex:[self row]]; in place of [[self listView] reloadData], but unfortunately, this doesn't work: if the user hides the detail--shrinks the cell vertically--new cells which should appear on the screen do not.
You write
that is done, and work fine.
It sounds like you were able to implement successfully a method analogous to -[TemplateListViewCell toggleDetail:].
You write
but now i want to develop like after is witch cell has open in that cell i want to add some extra contain (Controller) on it, so how it will possible using given example? pls help me to give some suggest how it will be done.
It sounds like you want instances of TemplateListViewCell to contain extra views if they are expanded.
It might seem tempting to put this code into -[TemplateListViewCell toggleDetail], but this will not work out as we might hope. The trouble is, we need to handle cases where expanded cells have been scrolled out of view and scrolled back into view.
To get this right, we need to have a notion of expanded which persists beyond the usage of a PXListViewCell subclass instance: we either need to keep track of expansion in the PXListView itself or in its delegate.
The better--but less expedient--design seems to be to keep track of this information in the PXListView itself. For the sake of this question, however, I'll demonstrate how to keep track of cell expansion in the delegate. To do this, I'm expanding the PXListViewDelegate protocol and making other changes to the PXListView files:
1. Adding the methods
- (void)listView:(PXListView *)aListView setExpanded:(BOOL)expanded atRow:(NSUInteger)row;
- (BOOL)listView:(PXListView *)aListView expandedAtRow:(NSUInteger)row;
to PXListViewDelegate.
2. Adding the method
- (void)setCell:(PXListViewCell *)cell expandedAtRow:(NSUInteger)row
{
if ([[self delegate] respondsToSelector:#selector(listView:expandedAtRow:)]) {
[cell setExpanded:[[self delegate] listView:self expandedAtRow:row]];
}
}
to PXListView.
3. Calling -[PXListView setCell:expandedAtRow:] from -[PXListView layoutCells]
- (void)layoutCells
{
//Set the frames of the cells
for(id cell in _visibleCells)
{
NSInteger row = [cell row];
[cell setFrame:[self rectOfRow:row]];
[self setCell:cell expandedAtRow:row];
[cell layoutSubviews];
}
NSRect bounds = [self bounds];
CGFloat documentHeight = _totalHeight>NSHeight(bounds)?_totalHeight:(NSHeight(bounds) -2);
//Set the new height of the document view
[[self documentView] setFrame:NSMakeRect(0.0f, 0.0f, NSWidth([self contentViewRect]), documentHeight)];
}
and from -[PXListView layoutCell:atRow:]:
- (void)layoutCell:(PXListViewCell*)cell atRow:(NSUInteger)row
{
[[self documentView] addSubview:cell];
[cell setFrame:[self rectOfRow:row]];
[cell setListView:self];
[cell setRow:row];
[cell setHidden:NO];
[self setCell:cell expandedAtRow:row];
}
4. Setting _expanded to NO in -[PXListViewCell prepareForReuse]:
- (void)prepareForReuse
{
_dropHighlight = PXListViewDropNowhere;
_expanded = NO;
}
Note: In the sample PXListViewCell subclass, MyListViewCell, distributed with PXListView, the implementation of -[MyListViewCell prepareForReuse] fails to call [super prepareForReuse]. Make sure that this call is made in [TemplateListViewCell prepareForReuse]:
- (void)prepareForReuse
{
//...
[super prepareForReuse];
}
One change needs to be made to -[TemplateListViewCell toggleDetail:]. The line
[self setExpanded:isExpanded];
needs to be replaced by
[[[self listView] delegate] listView:[self listView] setExpanded:isExpanded atRow:[self row]];
Once you've set up your PXListView's delegate to properly handle the new delegate methods, you're ready to override [PXListViewCell setExpanded:] in your subclass TemplateListViewCell:
- (void)setExpanded:(BOOL)expanded
{
if (expanded) {
//add detail subviews
} else {
//remove detail subviews
}
[super setExpanded:expanded];
}
Replace //add detail subviews with your own code which programmatically adds the detail subviews that you want and replace //remove detail subviews with code to remove the detail subviews that you want, checking to see that they are present first.
You write
i want to add some extra contain (Controller) on it
It sounds like you want to add view controllers rather than views to your TemplateListViewCell. To do this, use an NSBox and set the box's contentView to your view controller's view. (For details on this, see this answer.)
If you plan on just showing a single view controller's view in an NSBox on the expanded TemplateListViewCell, you can just (1) add a property to TemplateListViewCell referencing your view controller and (2) add an NSBox to TemplateListViewCell xib and set its contentView to the appropriate view controller's view on [cell setExpanded:YES] and set its contentView to nil on [cell setExpanded:NO].

draw separately in 2 custom views in one class

In IB, put 2 custom views into one window. I don't see any way to give them separate names. In Inspector-Info I had to use the same name, the class name, for both of them in the drop-down menu. I tried
DrawRect: NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
which filled both custom views with green. But I would like to fill, draw, etc. independently in each custom view. How can I address each view separately? Multiple custom views, later. Or does Cocoa require only one view per class?
This is probably trivial, but Google and the similar questions list here didn't come up with anything close. There's a lot of about multiple view controllers, but I don't need to switch views.
IB will show you the names of the classes that you can assign to an object. If you have only one custom class (eg. "myCustomClass") then it will only show that one in the drop-down menu.
I think the best solution to your problem, if you want tu use only one class, is to put the drawing code in two separate functions and assign each view an IBOutlet, then call the function from the controller class.
//Add this to your interface
NSNumber *myColor;
//Add/Edit the following functions
- (void)drawRect:(NSRect)aRect
{
//Some code...
if ([myColor intValue]) [self drawGreen];
else [self drawRed];
//Some code...
}
- (void)drawGreen
{
NSRect bounds = [self bounds];
[[NSColor greenColor] set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRed
{
NSRect bounds = [self bounds];
[[NSColor redColor] set];
[NSBezierPath fillRect:bounds];
}
- (void)drawRedOrGreen:(int)aColor
{
myColor = [NSNumber numberWithInt:aColor];
}
You have to add the following two lines to your controller's interface
IBOutlet myCustomClass *customView1;
IBOutlet myCustomClass *customView2;
And you have to set each view's color.
This will set it when it loads for the first time.
- (void)awakeFromNib
{
[customView1 drawRedOrGreen:1]; //Green
[customView2 drawRedOrGreen:0]; //Red
}
This way each view will be coloured differently.
An alternative solution would be to create two separate custom classes (eg. "myCustomClass1" and "myCustomClass2") which would have their own drawing code...

Cocoa NSView not filling with color

What am I doing wrong?
I have an awakeFromNib method in which I am calling a class that is a subview (GameMap). The class exists and I am able to log in the awakeFromNib method as well as log in GameMap's initWithFrame method, but I cannot get GameMap to draw in the window. Here's my AppController.m file's awakeFromNib method:
-(void) awakeFromNib {
//make new game map
GameMap* newMap = [[GameMap alloc] initWithFrame:NSMakeRect(0.0, 0.0, 1000.0, 500.0)];
[[[NSApp mainWindow] contentView]addSubview:newMap];
[newMap release];
}
and in GameMap.m here's the drawRect method
- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFillUsingOperation(rect, NSCompositeSourceOver);
}
in this same app I am calling two other classes from AppController, all subviews of NSView, MakeCircle and MakeRoom that place either a circle (duh, : D) or a rect with a stroke in the window and they work fine, but they are running off of IBOutlet actions (button clicks). Any help would be appreciated.
*NOTE: I have NSRectFillUsingOperation(rect, NSCompositeSourceOver) but this was also failing wiht NSRectFill(rect).
**I can also log the origin.x/size.width, etc. of the passed rect to GameMap from the initWithFrame, so I know it's there.
(I'm away from my computer for a few hours so don't think I'm being rude for not replying, just wanted to get this question out there before I left.)
Just use NSRectFill(rect); instead of NSRectFillUsingOperation(rect, NSCompositeSourceOver);
Have you set the view's class to the GameMap class instead of NSView) in the Interface Builder? When you do this, the GameMap's drawRect() should get called automatically.

How to implement HUD-style window like Address Book's "Show in Large Type"

Several apps, including the built-in Address Book use a HUD window that is semi-transparent, with large shadowed text. I'd like to implement a similar window in my Cocoa Mac app.
Is there a free implementation of this kind of window somewhere?
If not, what is the best way to implement it?
Here's a sample project that shows how to do it:
http://github.com/NSGod/BlackBorderlessWindow
Basically, you need to create a borderless NSWindow subclass. The easiest way to do this is to set your window size and arrangement in the nib file, and then set its class to be your custom subclass. So while it will still look like a normal window in Interface Builder, at runtime it will appear as you need it to.
#implementation MDBorderlessWindow
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation {
if (self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered defer:deferCreation]) {
[self setAlphaValue:0.75];
[self setOpaque:NO];
[self setExcludedFromWindowsMenu:NO];
}
return self;
}
The alpha value will make the window semi-transparent.
Also, you can create a custom NSView subclass that will draw a round rectangle:
#implementation MDBlackTransparentView
- (id)initWithFrame:(NSRect)frame {
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(NSRect)frame {
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:frame
xRadius:6.0 yRadius:6.0];
[[NSColor blackColor] set];
[path fill];
}
#end
Like with the window, you simply set the class of the window's contentView to be your custom NSView subclass. (Use outline view mode and click the disclosure triangle to show the nested NSView inside the icon of the window in the nib file). Again, while the view will look ordinary in Interface Builder, it will look okay at runtime.
Then just place an NSTextField on top of view and set the text accordingly.
Note that, in general, borderless windows aren't easy to work with (for example, if you want to be able to drag the window around, you'll need to add that functionality back yourself). Apple has some sample code on how to allow dragging, for instance.
Thank you for sharing this code. Helped me a lot!
You may add the following line...
[self setBackgroundColor:[NSColor clearColor]];
to the init function of the window. This removes the white corners.

CoreAnimation doesn't work with -setAlphaValue on QTMovieView

I have some basic code for adding a QTMovieView. I want it to fade in, so I add an animation on setAlphaValue. Only issue is that it doesn't work. The alpha value is instantly set to whatever it was supposed to animate to. If I try animating e.g. setFrame: it works fine. You'll find my code below. This is on 10.5.7 with the Mac OS X 10.5 SDK.
- (void)addMovie:(NSString *)aFile
{
QTMovieView *aMovieView;
QTMovie *aMovie;
NSRect contentFrame;
contentFrame = [[self contentView] frame];
aMovieView = [[QTMovieView alloc]
initWithFrame:contentFrame];
[aMovieView setWantsLayer:YES];
[aMovieView setControllerVisible:NO];
[aMovieView setPreservesAspectRatio:YES];
aMovie = [[QTMovie alloc]
initWithFile:aFile error:nil];
[aMovieView setMovie:aMovie];
[aMovieView setAlphaValue:0.4];
[[self contentView] addSubview:aMovieView];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:2.0];
[[aMovieView animator] setAlphaValue:0.9];
[NSAnimationContext endGrouping];
}
Any ideas?
I don't know for sure but it sounds like QTMovieView may not support alpha compositing. One further test I would try would be to call setWantsLayer on the superview of the QTMovieView to see if that affects the QTMovieViews ability to composite correctly. Probably will not work.
You could try using QTMovieLayer instead. One way to incorporate QTMovieLayer would be to make your own NSView subclass that created and managed a QTMovieLayer that was added inside its root layer. Be aware though that when mixing layer backed views with non layer backed views in the same window you may get funny ordering if the layer backed views are not all in front or are behind and overlapping.
I suggest NSViewAnimation. It's a subclass of NSAnimation that will do the fading in and out for you.
I began on QTMovieLayer, but being less powerfull (of course) than QTMovieView it opened another box of issues. The solution was to use NSAnimation on the QTMovieView. I have a NSAnimation class looking somewhat like this:
AlphaAnimation.h
#import <Cocoa/Cocoa.h>
NSString * const AAFadeIn;
NSString * const AAFadeOut;
#interface AlphaAnimation : NSAnimation {
NSView *animatedObject;
NSString *effect;
}
- (id)initWithDuration:(NSTimeInterval)duration effect:(NSString *)effect object:(NSView *)object;
#end
AlphaAnimation.m
#import "AlphaAnimation.h"
NSString * const AAFadeIn = #"AAFadeIn";
NSString * const AAFadeOut = #"AAFadeOut";
#implementation AlphaAnimation
- (id)initWithDuration:(NSTimeInterval)aDuration effect:(NSString *)anEffect object:(NSView *)anObject
{
self = [super initWithDuration:aDuration animationCurve:0];
if (self) {
animatedObject = anObject;
effect = anEffect;
}
return self;
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
if ([effect isEqual:AAFadeIn])
[animatedObject setAlphaValue:progress];
else
[animatedObject setAlphaValue:1 - progress];
}
#end
Which can then be used like this:
animation = [[AlphaAnimation alloc] initWithDuration:0.5 effect:AAFadeIn object:movieView];
[animation setAnimationBlockingMode:NSAnimationNonblocking];
[animation startAnimation];
If your QTMovieViews are in full screen it isn't very smooth, though.

Resources