How to enable reordering a collection view in tvOS? - uicollectionview

In iOS 9 all it takes to enable reordering a collection view is to implement moveItenAtIndexPath... like this...
override func collectionView(collectionView: UICollectionView,
moveItemAtIndexPath sourceIndexPath: NSIndexPath,
toIndexPath destinationIndexPath: NSIndexPath) {
print("sourceIndexPath= \(sourceIndexPath)")
print("destinationIndexPath= \(destinationIndexPath)")
}
But it doesn't seem to be enough in tvOS. What else needs to be implemented to enable reordering?
Here's the code from the view controller...
import UIKit
class ReorderableCollectionViewController: UICollectionViewController {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numbers.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CellReuseIdentifier", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.backgroundColor = UIColor.yellowColor()
let number = numbers[indexPath.item]
cell.label.text = "\(number)"
return cell
}
override func collectionView(collectionView: UICollectionView,
moveItemAtIndexPath sourceIndexPath: NSIndexPath,
toIndexPath destinationIndexPath: NSIndexPath) {
print("sourceIndexPath= \(sourceIndexPath)")
print("destinationIndexPath= \(destinationIndexPath)")
}
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
print("didUpdateFocusInContext")
coordinator.addCoordinatedAnimations({
if let previousItem = context.previouslyFocusedView as? CustomCollectionViewCell {
previousItem.backgroundColor = UIColor.yellowColor()
}
if let nextItem = context.nextFocusedView as? CustomCollectionViewCell {
nextItem.backgroundColor = UIColor.redColor()
}
}, completion: nil)
}
}

I was unable to use built in methods to enable reordering of collectionview/tableview cells. However, there's always an option for a custom solution. In my current project I have concept of focused cell and highlighted cell. Also I have a separate button to trigger moving of highlighted cell. Also I wanted to have a behavior similar to home screen where long tap triggers moving too. Here are most of the code snippets required to achieve the result:
The class inherits from UIView and has UICollectionView connected to playlistCollection property.
Core idea: The view has special flag for reordering state. When this flag is triggered, all focus changes are blocked. If focus was about to move to another cell - swap currently focused cell with the one that user tried to focus. Otherwise don't do anything (so that focus never leaves collection view unless it's in normal state).
Other option: Instead of swapping cells around, you can also swap their data in the model and reload the cells. Advantages of this is that you can move selected cell through the list much faster (at the speed of scrolling through the list), but reloading 2 cells causes a slight lag, hence I decided to use my current solution. If you want to try this solution out the code triggering the cell change has to be moved from shouldUpdateFocusInContext to didUpdateFocusInContext and only focus changes moving outside of collectionview must be forbidden (rather than all focus changes).
Hope this helps.
_pressureGestureRecognizer overrides default behavior for all the remote buttons that are accessible to the app (menu, play/pause, tap), therefore it's only enabled when view is reordering state.
#implementation PlaylistView {
int _highlightedCell;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
UITapGestureRecognizer *_pressGestureRecognizer;
BOOL _isBeingReordered;
NSMutableArray *_data;
}
#synthesize isBeingReordered = _isBeingReordered;
- (void)setup {
_isBeingReordered = NO;
_data = [[NSMutableArray alloc] initWithCapacity:20];
for (int i = 0; i < 20; i++)
{
[_data addObject:[NSString stringWithFormat:#"Temp %i", i]];
}
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressed:)];
[self.playlistCollection addGestureRecognizer:_longPressGestureRecognizer];
_pressGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(pressed:)];
_pressGestureRecognizer.allowedPressTypes = #[#(UIPressTypeMenu), #(UIPressTypeSelect), #(UIPressTypePlayPause)];
[self.playlistCollection addGestureRecognizer:_pressGestureRecognizer];
_pressGestureRecognizer.enabled = NO;
}
- (void)pressed:(UITapGestureRecognizer *)gesture {
if (!_isBeingReordered)
return;
_isBeingReordered = NO;
_pressGestureRecognizer.enabled = NO;
}
- (void)longPressed:(UILongPressGestureRecognizer *)gesture {
if (_isBeingReordered)
return;
_isBeingReordered = YES;
_pressGestureRecognizer.enabled = YES;
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
[super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
if ([context.nextFocusedView isKindOfClass:[PlaylistCell class]]) {
if (_isBeingReordered)
{
NSLog(#"This should never happen.");
}
else
{
int nextIdx = [self.playlistCollection indexPathForCell:context.nextFocusedView].row;
if (nextIdx != _highlightedCell) {
PlaylistCell *prevCell = [self.playlistCollection cellForItemAtIndexPath:[NSIndexPath indexPathForItem:_highlightedCell inSection:0]];
if ([prevCell highlighted])
prevCell.highlighted = NO;
}
_highlightedCell = nextIdx;
}
}
}
- (void)moveCellFromRow:(int)artwork offset:(int)offset {
if (artwork + offset >= 0 && artwork + offset <= [_data count] - 1)
{
[self.playlistCollection performBatchUpdates:^{
[self.playlistCollection moveItemAtIndexPath:[NSIndexPath indexPathForItem:artwork inSection:0] toIndexPath:[NSIndexPath indexPathForItem:artwork + offset inSection:0]];
[self.playlistCollection moveItemAtIndexPath:[NSIndexPath indexPathForItem:artwork + offset inSection:0] toIndexPath:[NSIndexPath indexPathForItem:artwork inSection:0]];
_highlightedCell += offset;
//if there are certain elements in the cells that are position dependant, this is the right time to change them
//because these cells are not reloaded by default (for example you have idx displayed in your cell... the cells will swap but idxs won't by default)
} completion:^(BOOL finished) {
NSString *temp = _data[artwork + offset];
_data[artwork + offset] = _data[artwork];
_data[artwork] = temp;
}];
}
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldUpdateFocusInContext:(UICollectionViewFocusUpdateContext *)context {
if (_isBeingReordered)
{
//code only supports vertical reording.
if (context.focusHeading == UIFocusHeadingDown)
{
[self moveCellFromRow:_highlightedCell offset:1];
}
else if (context.focusHeading == UIFocusHeadingUp)
{
[self moveCellFromRow:_highlightedCell offset:-1];
}
return NO;
}
return YES;
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
//i have a custom code to handle highlights
return NO;
}
#end

Related

How to detect the swiping of the last or first cell in UICollectionView

I have a collection view with one row and I want to hide it if the user is trying to swipe right on the first cell or trying to swipe left on the last cell.
Just adding a left\right swipe gesture won't do the trick.
I have mannaged to do it with swipe up gesture by adding it to the first and last cell (in cellForItemAtIndexPath method).
Any Ideas?
For get Scroll direction
#property (nonatomic) CGFloat lastContentOffset;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (self.lastContentOffset > scrollView.contentOffset.x)
{
NSLog(#"Scrolling left");
}
else if (self.lastContentOffset < scrollView.contentOffset.x)
{
NSLog(#"Scrolling right");
}
self.lastContentOffset = scrollView.contentOffset.x;
}
Ok, I have managed to get to the solution with the combination of #Tejas answer and comments:
var lastContentOffset = CGFloat()
var scrollDir = UISwipeGestureRecognizerDirection.Left
func scrollViewDidScroll(scrollView: UIScrollView)
{
if (self.lastContentOffset > scrollView.contentOffset.x)
{
self.scrollDir = UISwipeGestureRecognizerDirection.Left
}
else if (self.lastContentOffset < scrollView.contentOffset.x)
{
self.scrollDir = UISwipeGestureRecognizerDirection.Right
}
self.lastContentOffset = scrollView.contentOffset.x;
}
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool)
{
if let indexPath = self.upperCollectionView?.indexPathsForVisibleItems()[0]
{
if indexPath.item == 0 && self.scrollDir == UISwipeGestureRecognizerDirection.Left
{
//hide the collection view
}
}
}

Using UICollectionView in a universal app

I need to create a universal app with a listing. And on clicking a cell it shows the detail view of the cell. I have created the listing for a iPad by using a UICollectionView inside a UIViewController. But when I try the same in iPhone it doesn't show properly. Its kind of a zoomed version of iPad cell.
For iPad I need the cell to be like this
http://i.stack.imgur.com/8mJnI.png
And for iPhone I need the cell to be like this
http://i.stack.imgur.com/moUxm.png
What is the best way to do this?
Any help will be appreciated
I would suggest create to abstract subclass of UICollectionViewCell with properties like image, title, desc and two subclasses of the abstract class, for example iPadCell and iPhoneCell.
In the storyboard add two prototype cells and change it class and identifier to iPhoneCell and iPadCell.
Layout the cell as you need and in collectionView:cellForItemAtIndexPath: dequeue right cell, for appropriate device:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
YourAbstractClass *cell = nil;
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) {
// Make sure it match storyboard identifier for iPad cell
cell = [cv dequeueReusableCellWithReuseIdentifier:#"iPadCellIdentifier" forIndexPath:indexPath];
}
else { //iPhone device
cell = [cv dequeueReusableCellWithReuseIdentifier:#"iPhoneCellIdentifier" forIndexPath:indexPath];
}
cell.imageView.image = ...;
cell.title = ...;
cell.description = ...;
return cell;
}
In very similar way you can set up cell size, if it's different for iPhone/ipad:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) {
return CGSizeMake(400.0f, 500.0f);
}
return CGSizeMake(200.0f, 300.0f)
}
since the content of you cell is same i.e.,
ImageView
UILabel for Title
UILabel for Description
Instead of making a abstract class and then subclassing them, What you could also do is, you just make one custom class of UICollectionViewCell, for example name it as GenericCollectionViewCell, Now this class should have two nib files, and make sure you assign different reuse Identifier to both of them and different file name ofcourse, e.g.
iPhoneCell
iPadCell
So, now basically you have GenericCollectionViewCell.h with IBOutlets to two different nibs.
You will have to create a ENUM to in the GenericeCollectionViewCell e.g.
typedef NS_ENUM(NSUInteger, CellType) {
CELLIPHONE = 0,
CELLIPAD = 1
};
you will use this ENUM to initialise, What type of nib to use for this cell e.g.
+ (UINib*)cellNibForCellType:(CellType)cellType{
switch (cellType) {
case CELLIPHONE:
cellNib = [UINib nibWithNibName:#"IPhoneCollectionViewCell"
bundle:nil];
break;
case CELLIPAD:
cellNib = [UINib nibWithNibName:#"IPadCollectionViewCell"
bundle:nil];
break;
}
Once you have done that, you need to register this cell in viewDidLoad of your ViewController,
- (void)viewDidLoad
{
[super viewDidLoad];
[_collectionView registerNib:[GenericCollectionViewCell cellNibForCellType:CELLIPHONE] forCellWithReuseIdentifier:#"iPhoneCell"];
[_collectionView registerNib:[GenericCollectionViewCell cellNibForCellType:CELLIPAD] forCellWithReuseIdentifier:#"iPadCell"];
}
And then you could proceed with what, #Greg has explained above,
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
GenericCollectionViewCell *cell = nil;
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) {
// Make sure it match storyboard identifier for iPad cell
cell = [cv dequeueReusableCellWithReuseIdentifier:#"iPadCell" forIndexPath:indexPath];
}
else { //iPhone device
cell = [cv dequeueReusableCellWithReuseIdentifier:#"iPhoneCell" forIndexPath:indexPath];
}
cell.imageView.image = ...;
cell.title = ...;
cell.description = ...;
return cell;
}
Basically this is what I have come up with, if the content of the cells are same, what's the point of making super class, I mean I don't know I might be wrong.
In your viewDidLoad():
let layout = collectionViewLayout as! UICollectionViewFlowLayout
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Pad)
{
layout.minimumLineSpacing = 30
layout.minimumInteritemSpacing = 10
layout.itemSize = CGSize(width: 200, height: 200);
layout.scrollDirection = .Vertical
layout.sectionInset = UIEdgeInsets(top: 50, left: 20, bottom: 20, right: 20)
}else if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.Phone){
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 2
layout.itemSize = CGSize(width: 150, height: 150)
layout.scrollDirection = .Vertical
layout.sectionInset = UIEdgeInsets(top: 50, left: 20, bottom: 20, right: 20)
}

Dynamically size uitableViewCell according to UILabel (With paragraph spacing)

I have a UITableView which is populated by text and images from a JSON file. The TableView Cell is currently sizing correctly for "posts" that do not contain many line breaks in the text however I cannot get it to calculate the correct height for "posts" with 4 or 5 line breaks.
Code for getting height:
-(float)height :(NSMutableAttributedString*)string
{
NSString *stringToSize = [NSString stringWithFormat:#"%#", string];
CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:contraint lineBreakMode:NSLineBreakByWordWrapping];
return size.height;
}
How do I calculate the correct size while allowing for line breaks and white space?
EDIT
The Rest of the method,
Inside of TableView CellForRow:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *row = [NSString stringWithFormat:#"%i", indexPath.row];
float postTextHeight = [self height:postText];
NSString *height = [NSString stringWithFormat:#"%f", heightOfPostText + 70];
[_cellSizes setObject:height forKey:row];
}
And the height of Table Cell:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *imageHeightString = [NSString stringWithFormat:#"%#", [_cellSizes objectForKey:indexPath.row]];
float heightOfCell = [imageHeightString floatValue];
if (heightOfCell == 0) {
return 217;
};
return heightOfCell + 5;
}
better u need to calculate the height first, don't include the height calculation part in method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Better to calculate it in method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
since u are getting the data from json it is easy for u to calculate
in the "heightForRowAtIndexPath" method.
follwing code will give the example to calculate height of text change it ur requirement.
hopee this helps u :)
// i am using an array
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIFont *labelFont = [UIFont fontWithName:#"Noteworthy-Bold" size:20];
NSDictionary *arialdict = [NSDictionary dictionaryWithObject:labelFont forKey:NSFontAttributeName];
NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:#"this is just the sample example of how to calculate the dynamic height for tableview cell which is of around 7 to 8 lines. you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
array = [NSMutableArray arrayWithObjects:message, nil];
NSMutableAttributedString *message_1 = [[NSMutableAttributedString alloc] initWithString:#"you will need to set the height of this string first, not seems to be calculated in cellForRowAtIndexPath method." attributes:arialdict];
[array addObject:message_1];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *Cell = [self.aTableView dequeueReusableCellWithIdentifier:#"cell"];
if(Cell == nil)
{
Cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
//dont include the height calculation part hear, becz heights are already set for all the cell
[Cell.textLabel sizeToFit];
Cell.textLabel.attributedText = [array objectAtIndex:indexPath.row]; // dont calculate height hear it will be called after "heightForRowAtIndexPath" method
Cell.textLabel.numberOfLines = 8;
return Cell;
}
// put ur height calculation method i took some hardcoded values change it :)
-(float)height :(NSMutableAttributedString*)string
{
/*
NSString *stringToSize = [NSString stringWithFormat:#"%#", string];
// CGSize constraint = CGSizeMake(LABEL_WIDTH - (LABEL_MARGIN *2), 2000.f);
CGSize maxSize = CGSizeMake(280, MAXFLOAT);//set max height //set the constant width, hear MAXFLOAT gives the maximum height
CGSize size = [stringToSize sizeWithFont:[UIFont systemFontOfSize:20.0f] constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
return size.height; //finally u get the correct height
*/
//commenting the above code because "sizeWithFont: constrainedToSize:maxSize: lineBreakMode: " has been deprecated to avoid above code use below
NSAttributedString *attributedText = string;
CGRect rect = [attributedText boundingRectWithSize:(CGSize){225, MAXFLOAT}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];//you need to specify the some width, height will be calculated
CGSize requiredSize = rect.size;
return requiredSize.height; //finally u return your height
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//whatever the height u need to calculate calculate hear only
CGFloat heightOfcell = [self height:[array objectAtIndex:indexPath.row]];
NSLog(#"%f",heightOfcell);
return heightOfcell;
}
Hope this helps u :)
For SWIFT version
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate
{
var messageArray:[String] = [] //array to holde the response form son for example
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
messageArray = ["One of the most interesting features of Newsstand is that once an asset downloading has started it will continue even if the application is suspended (that is: not running but still in memory) or it is terminated. Of course during while your app is suspended it will not receive any status update but it will be woken up in the background",
"In case that app has been terminated while downloading was in progress, the situation is different. Infact in the event of a finished downloading the app can not be simply woken up and the connection delegate finish download method called, as when an app is terminated its App delegate object doesn’t exist anymore. In such case the system will relaunch the app in the background.",
" If defined, this key will contain the array of all asset identifiers that caused the launch. From my tests it doesn’t seem this check is really required if you reconnect the pending downloading as explained in the next paragraph.",
]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return messageArray.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell;
if(cell == nil)
{
cell = UITableViewCell(style:UITableViewCellStyle.Default, reuseIdentifier: "CELL")
cell?.selectionStyle = UITableViewCellSelectionStyle.None
}
cell?.textLabel.font = UIFont.systemFontOfSize(15.0)
cell?.textLabel.sizeToFit()
cell?.textLabel.text = messageArray[indexPath.row]
cell?.textLabel.numberOfLines = 0
return cell!;
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
var height:CGFloat = self.calculateHeightForString(messageArray[indexPath.row])
return height + 70.0
}
func calculateHeightForString(inString:String) -> CGFloat
{
var messageString = inString
var attributes = [UIFont(): UIFont.systemFontOfSize(15.0)]
var attrString:NSAttributedString? = NSAttributedString(string: messageString, attributes: attributes)
var rect:CGRect = attrString!.boundingRectWithSize(CGSizeMake(300.0,CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil )
var requredSize:CGRect = rect
return requredSize.height //to include button's in your tableview
}
}
#Shan had a good answer but it didn't entirely worked for me.
This is the code I used for calculating the cell height
-(float)height :(NSMutableAttributedString*)string
{
CGRect rect = [string boundingRectWithSize:(CGSize){table.frame.size.width - 110, MAXFLOAT} options:NSStringDrawingUsesLineFragmentOrigin context:nil];
return rect.size.height;
}
I do the -110 because that will give equal space at sides top and bottom.
Hope this helps.
Implement this table view delegate method:
-tableView:heightForRowAtIndexPath:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
You'll call your method for determining the height and return that value with some extra padding if you wanted.
The approach I recommend is to set the text of an actual label and get the required height by calling sizeToFit. For this to work, you've got to set the label's numberOfLines property to 0 and set the desired maximum width.
When using this technique with table views, you can use the prototype cell method discussed here to calculate height using an actual cell.

Custom NSTableCellView labels not changing text color when selected

I have a custom NSTableCellView with 3 textfields, 1 that came along and 2 others that i created myself. Here's the problem:
The textfields' text color stays the same even when i click on the row. I've tried to implement a code i found out by googling but it doesn't work. My Custom NSTableCellView code is:
- (void)drawRect:(NSRect)dirtyRect{
NSColor *color = [NSColor colorWithCalibratedRed:(26/255.0) green:(26/255.0) blue:(26/255.0) alpha:1.0];
[self.textField setTextColor:color];
color = [NSColor colorWithCalibratedRed:(102/255.0) green:(102/255.0) blue:(102/255.0) alpha:1.0];
[_lbl1 setTextColor:color];
[_lbl2 setTextColor:color];
}
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
NSColor *color = (backgroundStyle == NSBackgroundStyleDark) ? [NSColor windowBackgroundColor] : [NSColor controlShadowColor];
self.textField.textColor = color;
self.lbl1.textColor = color;
self.lbl2.textColor = color;
[super setBackgroundStyle:backgroundStyle];
}
What can i do to make the labels' text color white when the user clicks on them?
Actually, overriding setBackgroundStyle on NSTableViewCell has worked perfectly for me, at least on OS X 10.8. It is updated on selection events and on window activation/deactivation.
Here's my custom cell impl — as trivial as it can get:
#implementation RuntimeInstanceCellView
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
[super setBackgroundStyle:backgroundStyle];
self.detailTextField.textColor = (backgroundStyle == NSBackgroundStyleLight ? [NSColor darkGrayColor] : [NSColor colorWithCalibratedWhite:0.85 alpha:1.0]);
// self.detailTextField.textColor = (backgroundStyle == NSBackgroundStyleLight ? [NSColor blackColor] : [NSColor whiteColor]);
}
#end
Expanding on the accepted answer, in Swift 2.0 the process is slightly different. Override the backgroundStyle property of your NSTableCellView subclass to add a didSet property observer:
class CustomTableCellView: NSTableCellView {
#IBOutlet weak var detailTextField: NSTextField!
override var backgroundStyle: NSBackgroundStyle {
didSet {
if self.backgroundStyle == .Light {
self.detailTextField.textColor = NSColor.controlTextColor()
} else if self.backgroundStyle == .Dark {
self.detailTextField.textColor = NSColor.alternateSelectedControlTextColor()
}
}
}
}
And for Swift 3 & 4 (isn’t this fun?):
override var backgroundStyle: NSView.BackgroundStyle {
didSet {
if self.backgroundStyle == .light {
self.detailTextField.textColor = NSColor.controlTextColor
} else if self.backgroundStyle == .dark {
self.detailTextField.textColor = NSColor.alternateSelectedControlTextColor
}
}
}
In your tableViewSelectionDidChange get the cell using
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath]; //replace UITableViewCell with your customCell class name if it other
//now as u got the instance of your cell u can modify the labels in it, like
cell.lable1.textColor = [UIColor whiteColor];
This will work for you.
You may get problem when you select other cell again after this, at that time previous cell may have still white colored labels. If this causes problems to you just have a NSIndexPath instance in your header class which represents previous selected indexPath, using this you can set back to default colors after selecting a new cell.

NSOutlineView Changing group row color

In my NSOutlineview i am using custom cell which is subclassed from NSTextFieldCell,
I need to draw different color for group row and for normal row, when its selected,
To do so, i have done following ,
-(id)_highlightColorForCell:(NSCell *)cell
{
return [NSColor colorWithCalibratedWhite:0.5f alpha:0.7f];
}
Yep i know its private API, but i couldn't found any other way,
this is working very well for Normal Row, but no effect on Group Row, Is there any way to change the group color,
Kind Regards
Rohan
You can actually do this without relying on private API's, at least if your willing to require Mac OS X 10.4 or better.
Put the following in your cell subclass:
- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
// Returning nil circumvents the standard row highlighting.
return nil;
}
And then subclass the NSOutlineView and re-implement the method, - (void)highlightSelectionInClipRect:(NSRect)clipRect;
Here's an example that draws one color for non-group rows and another for group rows
- (void)highlightSelectionInClipRect:(NSRect)clipRect
{
NSIndexSet *selectedRowIndexes = [self selectedRowIndexes];
NSRange visibleRows = [self rowsInRect:clipRect];
NSUInteger selectedRow = [selectedRowIndexes firstIndex];
while (selectedRow != NSNotFound)
{
if (selectedRow == -1 || !NSLocationInRange(selectedRow, visibleRows))
{
selectedRow = [selectedRowIndexes indexGreaterThanIndex:selectedRow];
continue;
}
// determine if this is a group row or not
id delegate = [self delegate];
BOOL isGroupRow = NO;
if ([delegate respondsToSelector:#selector(outlineView:isGroupItem:)])
{
id item = [self itemAtRow:selectedRow];
isGroupRow = [delegate outlineView:self isGroupItem:item];
}
if (isGroupRow)
{
[[NSColor alternateSelectedControlColor] set];
} else {
[[NSColor secondarySelectedControlColor] set];
}
NSRectFill([self rectOfRow:selectedRow]);
selectedRow = [selectedRowIndexes indexGreaterThanIndex:selectedRow];
}
}