Customising an NSTextFieldCell depending on the isEnabled biding of the table column - cocoa

What I basically trying to achieve is to change the formatting of a cell in a table to blank it out if the cell is not enabled.
To define whether a cell in a table column is enabled I've bound using the enabled binding and this is working fine, enabling the cells in some rows of the table and not in others.
I have defined a sub-class of NSTextFieldCell that I am using to change the format of the cell. This also appears to be working fine, but I am having trouble triggering the change depending on whether the cell in the table is enabled. Originally I tried to activate the switch by calling [self isEnabled] on the cell. This worked but the switch never activated, which I assume is because the original binding defining whether a cell is enabled was to the table column rather than to the cell.
Does anyone have a method to easily achieve this. The only method I have noticed would be to sub-class the NSTableColumn and write a custom version of dataCellForRow: but this somehow doesn't seem like the best way to solve this problem.

I use the table view's delegate method to set the text color before displaying a cell. You could set the cell color to the background color to make it invisible when the cell is disabled.
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
NSColor* theColor = [NSColor blackColor];
BOOL enable = YES;
if(![self tableView:aTableView shouldSelectRow:rowIndex])
{
theColor = [aCell backgroundColor];
enable = NO;
}
[aCell setTextColor:theColor];
[aCell setEnabled:enable];
}
I tested this code briefly and it worked. I am not using bindings, but that shouldn't matter for this as long as the table's delegate is set up properly.

Thankyou for your answer Mark. It didn't seem to work for me but it did point me in the right direction. I can't quite do everything I want because filling a cell with a background colour is a little harsh with the square edges but it works fairly robustly so I'll use it for now. The actually code I used is:
- (void)tableView:(NSTableView *)aTableView
willDisplayCell:(id)aCell
forTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
NSColor *cellColor = [NSColor lightGrayColor];
if([aCell isEnabled])
{
cellColor = [NSColor whiteColor];
}
if([aCell isKindOfClass:[NSTextFieldCell class]])
{
[aCell setDrawsBackground:YES];
}
[aCell setBackgroundColor:cellColor];
}

Related

scrollRectToVisible UITextField doesn't scroll with Autolayout

[self.scrollView scrollRectToVisible:textField.bounds animated:YES];
I can't seem to get my UIScrollView to scroll at all so that it doesn't obscure my UITextField. I thought that scrollRectToVisible would be my savior but it looks like a no go. Maybe I'm missing something like translating the coordinates of my textField to my scrollView. Either way check out my sample project.
https://github.com/stevemoser/Programming-iOS-Book-Examples/tree/master/ch20p573scrollViewAutoLayout2
Oh, and this project might be missing the delegate connection but I checked that and it still doesn't scroll.
I've seen other questions similar to this but none that mention Autolayout.
I was having issues with scrollRectToVisible:: as well after converting to Auto Layout. I just changed it to a direct call to setContentOffset:: and it started working again.
I had the same problem, I wanted to scroll an autolayouted UITextEdit into view without making it the first responder.
For me the issue was that the bounds of the UITextField were set later on during the auto layout pass, so if you do it immediately after setting up the layout the bounds are not valid yet.
To workaround I did create a descendant of UITextField, did overwrite setBounds: and added a 0 timer to scroll into view "later on" (You can't scroll in that moment because the auto layout pass of the system might no be finished at that point)
#interface MyTextField: UITextField
{
bool _scrollIntoView;
}
..
#end
#implementation MyTextField
-(void)setBounds:(CGRect)bounds
{
bool empty=CGRectIsEmpty(self.bounds);
bool isFirstResponder=self.isFirstResponder;
[super setBounds:bounds];
if (empty && !isFirstResponder && _scrollIntoView)
[self performSelector:#selector(scrollIntoViewLater) withObject:nil afterDelay:0];
else if (empty && isFirstResponder)
[self performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0];
}
-(void)scrollIntoViewLater
{
CGRect r=[scrollView convertRect:self.bounds fromView:self];
[scrollView scrollRectToVisible:r animated:TRUE];
}
#end
If the field should be additionally editable with the on screen keyboard, simply call becomeFirstResponder later on: it scrolls automagically into view above the keyboard using the private scrollTextFieldToVisible API which in turn calls scrollRectToVisible:animated: of the scrollview.
Your sample link is broken btw...

NSOutlineView source list style, view based, change font

I'm using an NSOutlineView with source list style, and using the view based (rather than cell based) outline view.
I would like to be able to make some rows bold. However, my attempts to change the font (manually in IB, through code in viewForTableColumn:…, or through the Font Bold binding) have so far been ignored.
From this message, it appears that this is because the source list style for NSOutlineView takes over managing the text field's appearance:
I'm guessing that you've hooked up your text field to the textField outlet of the NSTableCellView? If so, I think you might be running into NSTableView's automatic management of appearance for source lists.
Try disconnecting the text field from the textField outlet and see if your custom font sticks.
If I disconnect the textField outlet, the appearance does come under my control, and my emboldening works.
However, now I can't get it to look like the automatic one. By which I mean, when NSOutlineView was managing the text field's appearance, the font was bold and gained a drop shadow when any item was selected, but when I'm managing it manually this is not the case.
Can anyone answer either of these questions:
How can I get the Font Bold binding to work when NSOutlineView is managing the appearance of my text field
If I don't have NSOutlineView manage the appearance of my text field, how can I make it look and behave like it would if I did have it manage it?
I think I found the solution:
NSTableCellView manages the appearance of it's textField outlet by setting the backgroundStyle property on cells of contained controls. Setting this to NSBackgroundStyleDark triggers a special path in NSTextFieldCell which essentially sets an attributedStringValue, changing the text color and adding an shadow via NSShadowAttributeName.
What you could do is two things:
Set the backgroundStyle on your own in a custom row or cell view subclass.
Use a custom NSTextFieldCell in the cell's text field and change the behavior/drawing.
We did the latter since we needed a different look for a themed (differently colored) table view. The most convenient (albeit surely not most efficient) location we found for this was to override - drawInteriorWithFrame:inView: and modify the cell's attributed string before calling super, restoring the original afterwards:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSAttributedString *originalString = self.attributedStringValue;
// Customize string as you like
if (/* whatever */)
[self setAttributedStringValue: /* some string */];
// Regular drawing
[super drawInteriorWithFrame:cellFrame inView:controlView];
// Reset string
if (self.attributedStringValue != originalString)
self.attributedStringValue = originalString;
}
In the hope this may help others in similar situations.
Not sure if I have missed anything in your question but changing the font using the following works for me. ReminderTableCellView is just a subclass of NSTableCellView with an additional dateField added.
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
//LOG(#"viewForTableColumn called");
// For the groups, we just return a regular text view.
if ([_topLevelItems containsObject:item]) {
//LOG(#" top level");
NSTableCellView *result = [outlineView makeViewWithIdentifier:#"HeaderCell" owner:self];
// Uppercase the string value, but don't set anything else. NSOutlineView automatically applies attributes as necessary
NSString *value = [item uppercaseString];
[result.textField setStringValue:value];
//[result.textField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
return result;
} else {
//LOG(#" menu item");
// The cell is setup in IB. The textField and imageView outlets are properly setup.
// Special attributes are automatically applied by NSTableView/NSOutlineView for the source list
ReminderTableCellView *result = [outlineView makeViewWithIdentifier:#"DataCell" owner:self];
if ([item isKindOfClass:[OSTreeNode class]]) {
[result.textField setFont:[NSFont boldSystemFontOfSize:13]];
result.textField.stringValue = [item displayName];
result.dateField.stringValue = [item nextReminderDateAsString];
}
else
result.textField.stringValue = [item description];
if (_loading)
result.textField.textColor = [NSColor grayColor];
else
result.textField.textColor = [NSColor textColor];
NSImage *image = [NSImage imageNamed:#"ReminderMenuIcon.png"];
[image setSize:NSMakeSize(16,16)];
[result.imageView setImage:image];
//[result.imageView setImage:nil];
return result;
}
}
Resulting view is shown below. Note this is is an NSOutlineView with Source Listing option selected but I can't see why this would'nt work for a normal outlineView.

UITableView becomes unresponsive

UITableViewCell becomes unresponsive this was a very different problem with a very different solution.
My tableView which is a subView in a UIViewController initially works fine and I can select individual rows in the table. However, I have created my own popup when a row is selected (the popup is a UIView) that appears towards the bottom of the screen. As this pops-up I also create a another UIView which covers the screen behind the popup and it makes the background go dim. The third thing that happens is that i create a UITapGestureRecogniser to keep track of the user's taps, and if they tap outside the UIView then the two UIViews and the TapGestureRecogniser are removed and call the deselectRowAtIndex... method.
However, it is at this point that I cannot use the tableView, as i want to be able to select a different string within the tableView and the popup to appear again (the popup will eventually contain links that will enable the user to move to different viewControllers).
I have tried to reload the data, remove the tableview and replace it, edit the didSelectRowAtIndex, remove the deselectRowAtIndex method, however nothing I tried seems to work and i can't find anything on stackoverflow as my question seems to be quite specific (although I apologise if there is something out there).
I'll add a few parts of my code in, however, I'm not sure where the problem is and I may not have copied the right part in.
The remove overhead is the selector method from the tapGesture
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(_popOverView == nil)
{
_popOverView = [[UIView alloc]initWithFrame:CGRectMake(20, 200, 280, 150)];
_popOverView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.jpeg"]];
}
if(_mask == nil)
{
_mask = [[UIView alloc] initWithFrame:self.view.frame];
[_mask setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.78]];
}
if (_tapDetector == nil)
{
_tapDetector= [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(removeOverHead:)];
}
[self.view addSubview:_mask];
[self.view addSubview:_popOverView];
[self.view addGestureRecognizer:_tapDetector];
}
-(void) removeOverHead:(UITapGestureRecognizer*) sender
{
CGPoint locationOfTap = [_tapDetector locationInView:self.view];
if (locationOfTap.y < (200 + 150) && locationOfTap.y > 200 && locationOfTap.x > 20 && locationOfTap.x < (20 + 280) ) {
NSLog(#"%f,%f",[_tapDetector locationInView:self.view].x,[_tapDetector locationInView:self.view].y);
}
else
{
[_mask removeFromSuperview];
[_popOverView removeFromSuperview];
[_tapDetector removeTarget:self action:#selector(removeOverHead:)];
/*this idea doesn't work :(
[self.tableView removeFromSuperview];
[self.view addSubview:_tableView];*/
}
}
I really hope the answer is in here and is very simple, and thank you in advance for taking the time to read this.
Solved it! Sorry for wasting your time. It was the wrong remove method for the gestureRecogniser. I replaced
[_tapDetector removeTarget:self action:#selector(removeOverHead:)]
with
[self.view removeGestureRecognizer:_tapDetector]
as the UIGestureRecogniser was lingering and obstructing the tableView!!
If you stick a breakpoint or NSLog() inside the else block of that remove method, do you get inside it?
It sounds like your if statement might be off. You should use CGRectContainsPoint(). However if I understand correctly, you're attempting to dismiss everything when the user taps the dimming background view. You could make this view a button or you could compare the touch's view pointer to the pointer to the background view.

Setting text on multiple UILabels in a NSArray collection

I am trying to set multiple labels with identical text and cannot for the life of me figure out the proper way of doing so.
I am using an ibaction to handle a switch that will either change several labels in a collection to say "yes" or "no" and have been trying both a for loop and makeobjectsperformselector withobject method, but so far no luck.
Any insight would be greatly appreciated.
You can set the tag of UILabel subviews to help you out with this. If it's not already set, go to the storyboard, click on your label, go to the Attributes Inspector and under "View" there's a tag field.
If the labels all have different tags (0,1,2...) , the following loop should do what you need:
for(UIView *subview in [self.view subviews] ) {
if([subview isKindOfClass:[UILabel class]]) {
UILabel *currentLabel = (UILabel *)[self.view viewWithTag:subview.tag];
currentLabel.text = #"yes";
}
}

View-based NSOutlineView header cell font issues

I'm currently trying to use a new view-based NSOutlineView in my Cocoa app. As I'm not using bindings, so I implemented all required delegate and datasource methods in my controller.
In interface builder I've added a NSOutlineView with a highlighting set to SourceList and Content Mode set to View Based. Thus, there were two default table cell views provided (one Header cell with HeaderCell set as identifier and one data cell with DataCell set as identifier)
This is what it looks like in interface builder, header cell views correctly show a grey-blue textField while data cell views have a image view and a textField with correct color and font settings
To provide the views, I use the following code, to return a DataCell-view or a HeaderCell-view and set the textField of the cell accordingly, based on the corresponding identifier set in interface builder.
- (NSView *)outlineView:(NSOutlineView *)outlineView
viewForTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
NSTableCellView *result = nil;
if ([item isKindOfClass:[NSMutableDictionary class]]) {
result = [outlineView makeViewWithIdentifier:#"HeaderCell" owner:self];
id parentObject = [outlineView parentForItem:item] ? [outlineView parentForItem:item] : groupedRoster;
[[result textField] setStringValue:[[parentObject allKeys] objectAtIndex:0]];
} else {
result = [outlineView makeViewWithIdentifier:#"DataCell" owner:self];
[item nickname] ? [[result textField] setStringValue:[item nickname]] : [[result textField] setStringValue:[[item jid] bare]];
}
return result;
}
Running everything it looks like the following.
Could anybody provide me with hints, to why the header cell is neither bold, nor correctly colored, when selected?
You need to implement the -outlineView:isGroupItem: delegate method and return YES for your header rows. That will standardize the font and replace the disclosure triangle on the left with a Show/Hide button on the right. You will still need to manually uppercase your string to get the full effect.
I'm not sure if the group row delegate method above makes the selection style look okay or not. However, you normally don't want the header rows to be selectable at all in source lists, which you by returning NO for header items from the -outlineView:shouldSelectItem: delegate method.
I have created a little sample project which includes a source list and also uses the -outlineView:isGroupItem: method as #boaz-stuller has suggested.
Display a list of items
Edit the items in a master-detail fashion
Remove and add items
Usage of bindings
Check out besi/mac-quickies on github.
Most of the stuff is either done in IB or can be found in the AppDelegate

Resources