In my application I have a view based NSTableView with one column. The highlight color of the rows is set to regular (blue). I need to change that color to my custom color. In the interface builder I tried changing it but the only options are "None, regular and source list".
I tried this post solution with no success:
https://stackoverflow.com/a/9594543/3065901
I read that I have to use this delegate method but I dont know how to use this.
- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
I tried drawing the row in that method but i get invalid context warnings and the row still keeps with the same higlight.
Please post a simple example of how to use this delegate method:
Need help please. Thanks in advance.
From this link.
MyNSTableRowView.h
#import <Cocoa/Cocoa.h>
#interface MyNSTableRowView : NSTableRowView
#end
MyNSTableRowView.m
#import "MyNSTableRowView.h"
#implementation MyNSTableRowView
- (id)init
{
if (!(self = [super init])) return nil;
return self;
}
- (void)drawSelectionInRect:(NSRect)dirtyRect {
if (self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) {
NSRect selectionRect = NSInsetRect(self.bounds, 2.5, 2.5);
[[NSColor colorWithCalibratedWhite:.65 alpha:1.0] setStroke];
[[NSColor colorWithCalibratedWhite:.82 alpha:1.0] setFill];
NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRoundedRect:selectionRect
xRadius:6 yRadius:6];
[selectionPath fill];
[selectionPath stroke];
}
}
#end
AppDelegate.m
- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
{
MyNSTableRowView *rowView = [[MyNSTableRowView alloc]init];
return rowView;
}
Here is user3065901's answer in Swift 3:
class MyNSTableRowView: NSTableRowView {
override func drawSelection(in dirtyRect: NSRect) {
if self.selectionHighlightStyle != .none {
let selectionRect = NSInsetRect(self.bounds, 2.5, 2.5)
NSColor(calibratedWhite: 0.65, alpha: 1).setStroke()
NSColor(calibratedWhite: 0.82, alpha: 1).setFill()
let selectionPath = NSBezierPath.init(roundedRect: selectionRect, xRadius: 6, yRadius: 6)
selectionPath.fill()
selectionPath.stroke()
}
}
}
NSTableViewDelegate:
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
return MyNSTableRowView()
}
If you are using cell based tableview, set the NSTableView selectionHighLightStyle to None
NSTableView, and override drawRow sample below
- (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect {
NSColor* bgColor = Nil;
// Set the color only when its first responder and key window
if (self == [[self window] firstResponder] && [[self window] isMainWindow] && [[self window] isKeyWindow])
{
bgColor = [NSColor brownColor];
}
else
{
bgColor = [NSColor windowBackgroundColor];;
}
NSIndexSet* selectedRowIndexes = [self selectedRowIndexes];
if ([selectedRowIndexes containsIndex:row])
{
[bgColor setFill];
NSRectFill([self rectOfRow:row]);
}
[super drawRow:row clipRect:clipRect];
}
Add below line in your tableview method
ObjectiveC
[yourtableview setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
Swift
yourtableview.selectionHighlightStyle = .none
Related
i use NSCollectionView to display a horizontal list. but all of the cells overlap in the first cell position, like this.
i don't have enough reputation to post images. [cell1,cell2,cell3], they all overlap in the first cell position. so it look like just a cell, but actually it has 3 cells.
my main code:
ViewController.h
- (void)viewDidLoad {
[super viewDidLoad];
self.flowLayout.scrollDirection = NSCollectionViewScrollDirectionHorizontal;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
self.collectionView.selectable = YES;
self.collectionView.allowsMultipleSelection = YES;
[self.collectionView registerNib:[[NSNib alloc] initWithNibNamed:#"ScreenShotCellView" bundle:nil] forItemWithIdentifier:NSStringFromClass([ScreenShotCellView class])];
}
#pragma mark - NSCollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
[self.collectionView.collectionViewLayout invalidateLayout];
return 1;
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.screenshotItems.count;
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSCollectionViewItem *itemCell = [self.collectionView makeItemWithIdentifier:NSStringFromClass([ScreenShotCellView class]) forIndexPath:indexPath];
if ([itemCell isKindOfClass:[ScreenShotCellView class]]) {
ScreenShotCellView *cellView = (ScreenShotCellView *)itemCell;
ScreenshotItem *itemData = [self.screenshotItems objectAtIndex:indexPath.item];
cellView.item = itemData;
}
return itemCell;
}
#pragma mark - NSCollectionViewDelegateFlowLayout
- (NSSize)collectionView:(NSCollectionView *)collectionView layout:(NSCollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(100, self.collectionView.frame.size.height - 20);
}
- (CGFloat)collectionView:(NSCollectionView *)collectionView layout:(NSCollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
- (CGFloat)collectionView:(NSCollectionView *)collectionView layout:(NSCollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
How can i do something to fix it?
I have found the reason.
The problem is in my custom cell.
#interface ScreenShotCellView ()
#property (weak) IBOutlet NSView *containerView;
#end
#implementation ScreenShotCellView
- (void)loadView {
self.view = self.containerView;
}
#end
in the loadView method. self.view = self.containerView; make always display in the first cell.
after changed like this, it becomes normal.
- (void)loadView {
self.view = [[NSView alloc] init];
[self.view addSubview:self.containerView];
}
How do I generate indentation marker for NSOutlineView?
I am not sure if this is an inbuilt functionality because it appears in other apps like Instruments
Update
I tried solving the problem by iterating all the children of the item that the row represents and show the marker on all children rows based on indentation level, but I faced a few problems
How to handle the case where the item has thousands of children. One simply cannot draw marker to every row as NSOutlineView would draw rows as they are displayed
When I scroll the NSOutlineView, the mouse moves out of the specified row but mouseExited is not being called. Thus the user has to manually move the mouse to reload the highlighting.
I had solved this problem but my solution looks hacky hence wanted to know if there is a better solution. And hence the question
First to receive mouseEntered: and mouseExited: events you need to setup a tracking rect using NSTrackingArea.
I would start with a subclass of NSTableRowView thats overwrites setFrame: making sure the tracking rect gets updated when the view is resize:
#interface TableRowView : NSTableRowView {
NSBox *_box;
NSTrackingArea *_trackingArea;
}
#property (weak) id owner;
#property (copy) NSDictionary<id, id> *userInfo;
#property (nonatomic) CGFloat indentation;
#property (nonatomic) BOOL indentationMarkerHidden;
#end
#implementation TableRowView
- (void)setFrame:(NSRect)frame
{
[super setFrame:frame];
if (_trackingArea) {
[self removeTrackingArea:_trackingArea];
}
_trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow owner:[self owner] userInfo:[self userInfo]];
[self addTrackingArea:_trackingArea];
}
#end
To use the NSTableRowView subclass, implement the NSOutlineViewDelegate messages like this:
- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
{
TableRowView *view = [[TableRowView alloc] init];
view.owner = self;
view.userInfo = item;
return view;
}
With this in place you're ready to receive mouseEntered: and mouseExited: events. Use NSOutlineView levelForItem: together with indentationPerLevel to calculate the position of the marker NSBox.:
- (void)mouseEntered:(NSEvent *)event
{
id item = [event userData];
CGFloat indentation = [_outlineView levelForItem:item] * [_outlineView indentationPerLevel];
[self setIndentationMarker:indentation hidden:NO item:item];
}
- (void)mouseExited:(NSEvent *)event
{
id item = [event userData];
[self setIndentationMarker:0.0 hidden:YES item:item];
}
Now you get the NSTableRowView subclass by rowViewAtRow:makeIfNecessary: and recursively do the same for all children in your data:
- (void)setIndentationMarker:(CGFloat)indentation hidden:(BOOL)hidden item:(NSDictionary *)item
{
TableRowView *view = [_outlineView rowViewAtRow:[_outlineView rowForItem:item] makeIfNecessary:NO];
view.indentationMarkerHidden = hidden;
view.indentation = indentation;
for (NSMutableDictionary *child in [item objectForKey:#"children"]) {
[self setIndentationMarker:indentation hidden:hidden item:child];
}
}
Now layout the NSBox the NSTableRowView subclass:
#implementation TableRowView
- (instancetype)init
{
self = [super init];
if (self) {
_indentationMarkerHidden = YES;
_box = [[NSBox alloc] init];
_box.boxType = NSBoxCustom;
_box.borderWidth = 0.0;
_box.fillColor = [NSColor tertiaryLabelColor];
_box.hidden = _indentationMarkerHidden;
[self addSubview:_box];
}
return self;
}
- (void)layout
{
[super layout];
NSRect rect = [self bounds];
rect.origin.x = _indentation + 7;
rect.size.width = 10;
_box.frame = rect;
}
- (void)setIndentation:(CGFloat)indentation
{
_indentation = indentation;
[self setNeedsLayout:YES];
}
- (void)setIndentationMarkerHidden:(BOOL)indentationMarkerHidden
{
if (_indentationMarkerHidden != indentationMarkerHidden) {
_indentationMarkerHidden = indentationMarkerHidden;
_box.hidden = indentationMarkerHidden;
}
}
#end
This enough to make a basic version like here:
So I got a Window:
Window.xib
I got a WindowController too:
WindowController.h reads:
#import <Cocoa/Cocoa.h>
#interface MainWindowController : NSWindowController
{
IBOutlet NSView *firstView;
IBOutlet NSView *secondView;
IBOutlet NSView *thirdView;
int currentViewTag;
}
-(IBAction)switchView:(id)sender;
#end
And the WindowController.m reads:
#import "MainWindowController.h"
#interface MainWindowController ()
#end
#implementation MainWindowController
-(id)init
{
self = [super initWithWindowNibName:#"MainWindow"];
if (self){
// Initialization code here
}
return self;
}
//- (void)windowDidLoad {
// [super windowDidLoad];
//
// // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
//}
#pragma mark - Custom view drawing
-(NSRect)newFrameForNewContentView:(NSView *)view
{
NSWindow *window = [self window];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
-(NSView *)viewForTag:(int)tag{
NSView *view = nil;
if (tag == 0) {
view = firstView;
} else if (tag == 1) {
view = secondView;
} else {
view = thirdView;
}
return view;
}
-(BOOL) validateToolbarItem:(NSToolbarItem *)item
{
if ([item tag] == currentViewTag) return NO;
else return YES;
}
-(void)awakeFromNib
{
[[self window] setContentSize:[firstView frame].size];
[[[self window] contentView]addSubview:firstView];
[[[self window] contentView]setWantsLayer:YES];
}
-(IBAction)switchView:(id)sender
{
int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
[[NSAnimationContext currentContext] setDuration:1.0];
[[[[self window]contentView]animator]replaceSubview:previousView with:view];
[[[self window]animator]setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
#end
The problem I have is that when i switch tabs in my app, the custom view (and there are three of different sizes) draw differently each time. Look at the screenshots, all of the numbers should be centre aligned but they are sometimes and others not. Can anyone see what my error is please?
I will also add that all of the actions have been correctly configured + the code works perfectly if the custom view size is the same all the time.
The view that works
The view that almost works
Again pointing out that in my .xib all of the numbers are aligned to 0x and 0y axis.
Appdelegate.h
#import <Cocoa/Cocoa.h>
#class MainWindowController;
#interface AppDelegate : NSObject <NSApplicationDelegate> {
MainWindowController *mainWindowController;
}
#end
Appdelegate.m
#interface AppDelegate ()
#property (nonatomic) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
-(void)awakeFromNib
{
if(!mainWindowController){
mainWindowController = [[MainWindowController alloc]init];
}
[mainWindowController showWindow:nil];
}
#end
In Interface Builder, make sure to disable the "autoresizes subviews" checkbox of the default view of your window
I have created a NSPopUpButton in my app in a custom view which has a black fill. I would now like to make the NSPopUpButton have a white text color but I do not seem to be able to do this. How should I go about doing this?
Thanks
That's easy. You just need to override the -[NSPopUpButtonCell drawTitle:withFrame:inView], and then can change everything about title, like color, font, frame , etc. Here is a example below. Hope that can help you.
Objective-C:
#interface MyPopUpButtonCell : NSPopUpButtonCell
#property (nonatomic, readwrite, copy) NSColor *textColor;
#end
#implementation MyPopUpButtonCell
- (NSRect)drawTitle:(NSAttributedString*)title withFrame:(NSRect)frame inView:(NSView*)controlView {
NSRect newFrame = NSMakeRect(NSMinX(frame), NSMinY(frame)+2, NSWidth(frame), NSHeight(frame));
if (self.textColor) {
NSMutableAttributedString *newTitle = [[NSMutableAttributedString alloc] initWithAttributedString:title];
NSRange range = NSMakeRange(0, newTitle.length);
[newTitle addAttribute:NSForegroundColorAttributeName value:self.textColor range:range];
return [super drawTitle:newTitle withFrame:newFrame inView:controlView];
}else {
return [super drawTitle:title withFrame:newFrame inView:controlView];
}
}
#end
Swift:
class MyPopUpButtonCell: NSPopUpButtonCell {
var textColor: NSColor? = nil
override
func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect {
if self.textColor != nil {
let attrTitle = NSMutableAttributedString.init(attributedString: title)
let range = NSMakeRange(0, attrTitle.length)
attrTitle.addAttributes([NSAttributedString.Key.foregroundColor : self.textColor!], range: range)
return super.drawTitle(attrTitle, withFrame: frame, in: controlView)
}else {
return super.drawTitle(title, withFrame: frame, in: controlView)
}
}
}
Use -[NSButton setAttributedTitle:], bearing in mind that NSPopUpButton is a subclass of NSButton. Make a mutable copy of the attributed title, set its foreground color to white, and then set that as the new attributed title.
NSMutableAttributedString* myTitle = [[button.attributedTitle mutableCopy] autorelease];
NSRange allRange = NSMakeRange( 0, [myTitle length] );
myTitle addAttribute: NSForegroundColorAttributeName
value: [NSColor whiteColor]
range: allRange];
button.attributedTitle = myTitle;
I'm trying to figure out how to custom draw Buttons in Cocoa/OSX. Since my view is custom drawn I will not use IB and want to do it all in code. I created a subclass of NSButtonCell and a subclass of NSButton. In the Subclass of NSButtonCell I override the Method drawBezelWithFrame:inView: and in the initWithFrame Method of my subclassed NSButton I use setCell to set my CustomCell in the Button. However, drawBezelWithFrame gets not called and I don't understand why. Can someone point out what I've done wrong or what I miss here?
Subclass of NSButtonCell:
#import "TWIButtonCell.h"
#implementation TWIButtonCell
-(void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
{
//// General Declarations
[[NSGraphicsContext currentContext] saveGraphicsState];
//// Color Declarations
NSColor* fillColor = [NSColor colorWithCalibratedRed: 0 green: 0.59 blue: 0.886 alpha: 1];
//// Rectangle Drawing
NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRect: NSMakeRect(8.5, 7.5, 85, 25)];
[fillColor setFill];
[rectanglePath fill];
[NSGraphicsContext restoreGraphicsState];
}
#end
Subclass of NSButton:
#import "TWIButton.h"
#import "TWIButtonCell.h"
#implementation TWIButton
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TWIButtonCell *cell = [[TWIButtonCell alloc]init];
[self setCell:cell];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
}
#end
Usage:
- (void)addSendButton:(NSRect)btnSendRectRect
{
TWIButton *sendButton = [[TWIButton alloc] initWithFrame:btnSendRectRect];
[self addSubview:sendButton];
[sendButton setTitle:#"Send"];
[sendButton setTarget:self];
[sendButton setAction:#selector(send:)];
}
Following are the things seems to be missing out from your code.
You are not calling the [super drawRect:dirtyRect]
You are not overriding + (Class)cellClass in the Class(TWIButton) which is derived from NSButton.
Below is the code after changes:
#implementation TWIButton
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
TWIButtonCell *cell = [[TWIButtonCell alloc]init];
[self setCell:cell];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
//Changes Added!!!
[super drawRect:dirtyRect];
}
//Changes Added!!!!
+ (Class)cellClass
{
return [TWIButtonCell class];
}
#end
Now keep the break point at drawBezelWithFrame and check it will get called.
One might forgo the NSButton subclass, since it looks like you are ONLY using it to instantiate the Cell type within the initializer.
Simply
NSButton *button ...
[button setCell: [[TWIButtonCell alloc] init] autorelease]];
btw. you will probably have a leak in the previous examples since you init and then call setCell that likely has its own retain.