I have a UITableView that displays contacts from AddressBook. I have the multi-selection editing style enabled. When I present the tableView, it works as intended. The user taps a row and the red checkmark to the left side of the cell fills in. Great.
I save these selections in an array property on the presenting view controller. When I show my table again, I would like for these cells to remain selected (i.e., not blue highlighted, just with the red check mark still filled in). In cellForRowAtIndexPath: I get the object for the cell at that index path, test whether the presenting view controller's array contains that object, and if so [cell setSelected:YES];
It seems to me that this should result in the same selected appearance as when the user touches a cell (just the red check mark). Instead, the red check does fill in but also the cell highlights blue momentarily and the text turns white permanently, creating the impression that the contact's name has disappeared. I'm happy to post code if it's helpful but this seems like maybe just a quirk of UITableView that someone might be familiar with?
EDIT: This image shows the kind of checkmarks that I have (and want). I can't piece together though why when I call [cell setSelected:YES]; it behaves differently than it does in response to a touch. What you're looking at here is after I call [cell setSelected:YES];. And it looks almost how I want but the names have disappeeared. And I don't want the cell to highlight when the list appears.
If I understand the visual effect you're looking for, you should be getting it by using cell.accessoryType = UITableViewCellAccessoryCheckmark; rather than [cell setSelected:YES];.
This is resolved. In think I was making problems for myself by trying to setSelected in cellForRowAtIndexPath. Maybe the table view's reusing of the cells was making it act crazy?
The solution for getting the cells to select correctly between presentations of the multi-selection style table was to store the selected indexes at viewWillDisappear. I gave the presenting view controller a property NSArray *selectedIndexes, and in viewWillDisappear called
[self.presentingViewController setSelectedIndexes:[myTableView indexPathsForSelectedRows]];
Then in viewWillAppear, I put that array into an ivar, iterated through it and called [myTableView selectRowForIndexPath:[selectedIndexes objectAtIndex:i] animated:YES];
That did the trick. Thanks to user523234 for the comment. Helped get me thinking on the right track.
Related
I've seen people doing this differently, I would prefer that when you select an item in UITableView (say in screen A), the item is highlighted(selected), and another screen B is pushed to the navigation stack, then when you go back from screen B to screen A, the previously selected item will be unselected with animation, so what you do is to put:
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
inside:
- (void)viewWillAppear:(BOOL)animated;
and this is what Apple's sample code does. But I've seen a lot of people deselecting the row just after it is selected, inside:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
I know it may be just a personal preference, but I wonder is there any official clarification on this? Deselecting in viewWillAppear: lets users know his/her previously selected item, which is a bonus, but for some custom cell, the selected background/view may not be just solid color, and they may have a UIView added to the cell to represent the selected state, which makes the app inconsistent if some cells are deselected with animation and some are straight away after selected without animation.
Does anyone have any idea?
It's something that's up to you, if you are customising your applications UI extensively then you might not want to use this highlight at all. Maybe you have a complex cell and instead of using the cells selection trigger to drill down into a detail view or other, maybe you'll have a UIButton inside you cell.
It seems to me that UIKit wants to highlight the cell and retain it's highlight until you pop the detail view controller, at this point, presumably in viewWillAppear:, the currently selected cell of the UITableView is deselected, this allows you to have a very subtle and brief indication of which cell was selected to access where you've just come from.
Doesn't sound too interesting said like that however imagine if you exit the app and return some hours later, it's nice to have this small and as I said very subtle animation.
In the applications I've done in the past I've tended to not use this so much, or select but deselect immediately, so the user doesn't see a deselection animation upon returning or going "back" to the list. But that said all my cells in the applications I'm referring to have been very heavily customised.
I have an NSOutlineView with a custom datasource (I don't know if this is relevant).
I have only one column (again, I don't know if this is relevant) and I want to perform a specific action upon cell selection, so I thought I should override outlineViewSelectionDidChange. This what I did:
-(void)outlineViewSelectionDidChange:(NSNotification *)notification
{
NSLog(#"selection changed");
}
But this is not working. I have been playing around in IB with the Outline View, the Table Column and the Text Field Cell properties but so far I had no luck. I don't know if I changed any property that caused this situation or if this is something specific to my specific implementation.
So, anyone's got any clue on what I may be missing?
EDIT: Just in case I'm mis-interpreting the selection concept within an OutlineView, I expected the cells to be selected if I just click on the text outside the area of the expand arrow.
Well after a long struggle, as always, just after I posted my question, I found the answer. The problem is I am using the NSOutlineView in an NSPanel and somehow the NSPanel is not allowing the cells to be selected. If I just move the NSOutlineView to an NSWindow it works just as intended.
I have embedded UIButtons in my TableViewCells. In order to track which cell the button belongs to, I would like to add an NSIndexPath property to UIButton. I do not want to subclass a UIButton. Is there a way I can do this with categories?
EDIT: I believe the idea of setting tags will not work if I have multiple sections in the table view. Another approach of accessing the button's superview's superview to determine the cell seems more like a hack. Looking for a cleaner way of doing this.
There are several approaches. If you just really need a single number rather than a full index path, you can use setTag:. It's inflexible, but it's readily available.
The next best solution is associative references. These can hang data onto any object, with proper memory management. I usually create a category that adds a property implemented using objc_setAssociatedObject and objc_getAssociatedObject.
You can find out which cell a clicked button was in without having to associate tags or index paths with the buttons, which can get messy when re-using cells. Tags do work with sectioned tables but you have to mess about with numbers, e.g. 10001 is section 1, row 1, so you've got to convert at one end and convert back at the other, too fragile for my liking.
A nicer method is to use UITableView's indexPathForRowAtPoint: method. When your button's action is activated (assuming the target is in your table view controller:
CGPoint point = [sender convertPoint:sender.center toView:self.tableView];
NSIndexPath *path = [self.tableView indexPathForRowAtPoint:point];
sender here is cast to UIButton.
In order to track which cell the button belongs to
You already know which cell the button belongs to: it's the cell that the button is a subview of.
Start with a reference to the button. (If you are the button, this is self. If you're the button's target for its action, then when the button is tapped and emits an action message, it passes itself along as parameter to the action message, usually called sender.)
Look up the view hierarchy (superview) to find the UIViewTableCell containing the button.
UIView* v = theButton;
while (![v isKindOfClass: [UITableViewCell class]]) v = v.superview;
Now you can do whatever you like with that info. For example, you seem to want an NSIndexPath; then call -[UITableView indexPathForCell:].
I'm experiencing this strange situation. I have a UITableView where, when the user selects a cell, a long (network) process begins. So, I performed this in a background thread and I placed (in the didSelectRowAtIndexPath) a UIActivityIndicatorView as the accessory view. This is what I wrote:
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView* activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
cell.accessoryView = activityView;
[activityView startAnimating];
[activityView release];
everything seems to work correctly, except that, if during a loading process (when the indicator is animated), I switch the view using a UITabBar, when I go back to the UITableView, the UIActivityIndicatorView that should still be there has disappeared. Any idea of what I did wrong? Thanks!
I was looking back at this issue, and... after reading the question now, I'm laughing :-)
Answer to my own question is: nothing is wrong with the code, it works very well... but making the UIActivityIndicatorView white... makes it difficult to see it on white background :-D :-D The posted code is correct.
When you go back to the table view after going to another tab, cellForRowAtIndexPath: messages are sent to the table view controller to display the cells of the table but the activity indicator view was set as the accessory view of the cell in the didSelectRowAtIndexPath: method. So, essentially, you changed the cell (i.e., displayed the activity indicator view) when the cell was selected but when you left the view and came back the cellForRowAtIndexPath: method was used to re-display the cell (and, hence, no activity indicator view).
You'll have to keep track of what cells currently have activity indicator views and make sure you set the accessory view of those cells with a UIActivityIndicatorView in the cellForRowAtIndexPath: method. Obviously, if the activity associated with a cell has completed then don't display the activity indicator view for that cell so you'll have to keep track of whether the activities have completed yet or not. There are many ways you can do this so you'll have to decide what works best for your situation.
It's not true that cellForRowAtIndexPath: messages are sent (at least necessarily) when returning to the table view from another tab view so I've "deleted" my answer above.
I am wanting to change the text background color on a tableview's cell when it is hovered upon, similar to how AddressBook "highlights" the label of a contact's element when you mouseover the label names. However I cannot figure out how to accomplish...
detecting a mouseover on a particular NSCell and...
After detecting the cell his hovered upon, highlighting the text in that cell (not highlighting the entire row as if the user selected that row)
As NSCell is not a subclass of NSView this seems to be a very difficult task.
Any example of this or explanation on how this might be done would be greatly appreciated.
Thanks!
I actually got it working using another method. I got it from the example posted here... http://www.cocoadev.com/index.pl?NSTableViewRollover
https://web.archive.org/web/20111013060111/http://cocoadev.com/index.pl?NSTableViewRollover
Instead of using NSCell's tracking mechanism, I am tracking mouseEntered/mouseExited and mouseMoved within my subclassed NSTableView.
When the tableview awakeFromNib method is called, I create a trackingRect from the visible portion of the tableview
I have a BOOL ivar that is set to YES when the mouse is within the tracking area(mouseEntered) and NO when it is not (mouseExited)
Within the mouseMoved method, I determine the current row the mouse cursor is on and set it to an NSInteger ivar and then call the tableview's setNeedsDisplayInRect: passing the rect of the row that the mouse is on.
I also override resetCursorRects to remove the old tracking rect and add a new one...this method is called when the tableview is scrolled upon so that it's tracking the latest visible rect.
Finally in my tableview's delegate, I determine the selected row (by retrieving the row index from the NSInteger ivar of the table view and change the cell's text color (or anything you want) if the currently drawn cell matches the row the mouse cursor is on. All this is done in the delegate method: tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
I hope this helps others, as this was a bit tricky. It is also probably important to make sure that tableview is the firstResponder when the view loads, just makes things a bit more streamlined and cleaner.
Btw, is there a way to make a specific control in a view always be the firstResponder with nothing else possible as being the firstResponder? Even a method such as the iPhones... viewWillAppear method will help as I could set the first responder each time the view is visible...but i'm not aware of such a method on the Mac.
Overall, it's not a simple task as you noticed.
To track the mouse in an NSCell, subclass NSCell and override
-[NSCell startTrackingAt:inView:]
and
-[NSCell stopTracking:at:inView:mouseIsUp:]
Once you've detected the mouse is tracking inside a cell, you can find out which cell you are in the table with [tableView rowAtPoint:point] and [tableView columnAtPoint:point], and then find your frame with [tableView frameOfCellAtColumn:column row:row]
Then, you can change the way your cell is drawn by changing some property of the cell or changing the way it's drawn directly by overriding drawInteriorWithFrame:inView:.
Here's documentation on subclassing NSCell:
http://developer.apple.com/mac/library/documentation/cocoa/conceptual/ControlCell/Tasks/SubclassingNSCell.html
I achieved something similar by making use of addGlobalMonitorForEventsMatchingMask: handler: of NSEvent within my NSTableView subclass for the NSMouseMovedMask. using this along with columnAtPoint and rowAtPoint of NSTableView I was able to figure out if the position of the mouse was within a given cell.
Using this information I was able to bring up a PopOver when the mouse was over a particular cell.