UITableViewCell setSelected:animated: not working - objective-c

This is driving me crazy. I've looked here on S.O. for what I thought was a simple answer but couldn't find one.
In my custom UITableViewCell:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
// Configure the view for the selected state
if (selected) {
[self.languageLevelNameLabel setTextColor:[UIColor blackColor]];
}
else {
[self.languageLevelNameLabel setTextColor:[UIColor colorMessageCountZero]];
}
[self setNeedsDisplay];
}
In tableView:cellForRowAtIndexPath: of the controller:
if ([level integerValue] == indexPath.row) {
[cell setSelected:YES];
}
I've inserted break points and selected == YES is getting passed for the correct cell, and the if statement is being executed when it should be, but the text never gets set to blackColor.

For the cell to appear selected, you have to call -setSelected:animated: from within -tableView:willDisplayCell:forRowAtIndexPath: like so:
Objective C:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (/* should be selected */) {
[cell setSelected:YES animated:NO];
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; // required, as noted below
}
}
Swift 3:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if (/* should be selected */) {
cell.setSelected(true, animated: false)
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) // required, as noted below
}
}
Calling -setSelected from anywhere else has no effect.
Why?
After initializing or dequeueing a cell, but before displaying it, the table view calls a private method called -_configureCellForDisplay:forIndexPath: which, among other things, sets the cell's selected property to NO. The delegate's willDisplayCell:forRowAtIndexPath: gets called after this, letting you set anything needed for display.

If you want to set your cell as selected use the method selectRowAtIndexPath:animated:scrollPosition: like this in your cellForRowAtIndexPath method of your table view
Objective-C
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
swift 4
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)

Try
NSIndexPath* selectedCellIndexPath= [NSIndexPath indexPathForRow:number_row inSection:0];
[self tableView:tableViewList didSelectRowAtIndexPath:selectedCellIndexPath];
[tableViewList selectRowAtIndexPath:selectedCellIndexPath animated:YES scrollPosition:UITableViewScrollPositionTop];

There is no need to call cell's setSelected method.Apple hope that we set cell's selected status through two functions ,one is didSelectRowAtIndexPath ,the other is didDeselectRowAtIndexPath,they always occurr in pairs.To solve this problem,we just call two functions:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self tableView:self.leftTableView didSelectRowAtIndexPath:indexPath];
[self.leftTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

Related

Table view cell background goes white when deleting a cell - iOS

I have an iOS app with a UITableView, I have noticed that the cell background colour flashes white when the user selects the Delete button.
In the editActionsForRowAtIndexPath method, I have created two cell buttons: Edit and Delete. The first button's style is set to UITableViewRowActionStyleNormal. however the second button's style is set to UITableViewRowActionStyleDestructive - I have noticed that this issue only occurs when then style is set to destructive. Does anyone know why this is happening?
Here is the method I am using to set the cell action buttons:
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create the table view cell edit buttons.
UITableViewRowAction *editButton = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:#"Edit" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
// Edit the selected action.
[self editAction:indexPath];
}];
editButton.backgroundColor = [UIColor blueColor];
UITableViewRowAction *deleteButton = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:#"Delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
// Delete the selected action.
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}];
return #[deleteButton, editButton];
}
The colour of the cell is normal when the user is scrolling, tapping it or when he/she selects the Edit button, but when they select the Delete button, the cell turns white as the deletion animation is occurring.
How can I fix this?
It turns out the issue I am experiencing, is due to an iOS bug. I found a solution here: https://stackoverflow.com/a/46649768/1598906
[[UITableViewCell appearance] setBackgroundColor:[UIColor clearColor]];
The above code is set in the App Delegate, it set the background color to clear, thus removing the white background view.
Before calling the deleteRowsAtIndexPaths method, you need to remove the object from the data source;
Replace this:
UITableViewRowAction *deleteButton = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:#"Delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
// Delete the selected action.
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}];
with something like this:
UITableViewRowAction *deleteButton = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:#"Delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
// Delete the selected action.
[self deleteObjectAtIndexPath:indexPath];
}];
and the method to delete:
- (void)deleteObjectAtIndexPath:(NSIndexPath *)indexPath {
// remove object from data source. I assume that you have an array dataSource, or change it according with your data source
[self.dataSource removeObjectAtIndex:(indexPath.row)];
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

Collection View didSelectItemAtIndexPath segue

I'm struggling for a long time of this problem.
The reason was I had Collection delegate & datasource under the "CollectionView" not "CollectionView Controller" because I embedded one collectionView in the one of TableView Cell
but when I want to set up the
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)
{
}
This is my code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * Cell;
if (indexPath.section==0)
{
switch (indexPath.row)
{
case 0:
Cell = #"MoviesIntro";
break;
case 1:
Cell = #"影片介紹";
break;
case 2:
Cell = #"Moviesrelative";
default:
break;
}
}
 
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:Cell];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Cell];
}
switch (indexPath.row)
{
case 0:
{
}
break;
case 1:
{
}
break;
case 2:
{
Tony=(CollectionView*)[cell viewWithTag:741];
Tony.pageImages =imagearray;
[Tony reloadData];
}
break;
default:
break;
}
return cell;
}
Here is CollectionView.m Code
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.pic_url=[comment[indexPath.row] objectForKey:#"pic_url"];
self.name_zh=[comment[indexPath.row] objectForKey:#"name_zh"];
self.name_en=[comment[indexPath.row]objectForKey:#"name_en"];
self.intro=[comment[indexPath.row] objectForKey:#"short_Intro"];
self._id=[comment[indexPath.row] objectForKey:#"_id"];
self.trailer=[comment[indexPath.row] objectForKey:#"trailer"];
self.movieTime=[comment[indexPath.row] objectForKey:#"movieTime"];
self.picH_url=[comment[indexPath.row] objectForKey:#"picH_url"];
self.category=[comment[indexPath.row] objectForKey:#"category"];
self.director=[comment[indexPath.row] objectForKey:#"director"];
self.actor=[comment[indexPath.row] objectForKey:#"actor"];
self.language=[comment[indexPath.row] objectForKey:#"language"];
self.MyList=false;
[UIView animateWithDuration:0.05 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
} completion:^(BOOL finished) {
}];
}
Tony is Collection view.h/m files. pageImages is NSMutableArray on the Tony.h files.
to go to other view controller, property not found on object so I don't even know what can i do is anyone have good idea??
You can set add a delegate variable of your UITableViewCell and a delegate method like:
- (void) pushToViewController;
Then in YourTableViewCell.m file collectionView delegate methods add:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *){
[delegate pushToViewController];
}
And "tableView Controller"should be the delegate of your tableViewCell, in the controller of UITableView :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
YourTableViewCell *cell = ...
cell.delegate = self;
}
- (void) pushToViewController {
[self.navigationController pushViewController:someViewController animated:YES];
}
William Hu's suggestion will work.
In general you should not let your UI items implement their own delegates and data sources. It works but breaks the MVC pattern. Instead of that I'd use some custom UICollectionView to hold some reference to the row of the table to which it belongs. You could even 'abuse' the tag property for that (which I personally don't like but is is quite a common pattern) without the need to subclass UICollectionView.
Your UITableViewController can implement the datasource and delegate for the collection views too. On each call of any delegate method a reference to the very collectionView is handed into the method so that you can get the row of the table from there (which you have to set in the table view's cellForRowAtIndexPath of course) and go from there.
Therefore I'd rather suggest re-factoring your code and stick to the MVC pattern.

UITextField subview of UITableViewCell, get indexPath of cell

I have added a UITextField as a subview of a UITableViewCell. Then I have added a target and selector so that I can know when UIControlEventEditingChanged. This works great, but I would like you know the indexPath of the cell that the UITextField is in, as it could be added to any number of cells.
Is this possible? Basically I want to find the parent view which is a UITableViewCell.
Call [UIView superview] on the field to get the cell that it's in, then call [UITableView indexPathForCell:] on the cell to get the index path.
UPDATE: on iOS 7 you need to call superview on that view too (extra layer of views); here's a category on UITableView that should work independent of iOS version:
#interface UITableView (MyCoolExtension)
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view;
#end
#implementation UITableView (MyCoolExtension)
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view {
while (view != nil) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return [self indexPathForCell:(UITableViewCell *)view];
} else {
view = [view superview];
}
}
return nil;
}
#end
Instead of recursively trying to find the superview, there's a simpler solution:
Swift
func indexPathForCellContainingView(view: UIView, inTableView tableView:UITableView) -> NSIndexPath? {
let viewCenterRelativeToTableview = tableView.convertPoint(CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)), fromView:view)
return tableView.indexPathForRowAtPoint(viewCenterRelativeToTableview)
}
Objective-C
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view inTableView:(UITableView *)tableView {
CGPoint viewCenterRelativeToTableview = [tableView convertPoint:CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)) fromView:view];
NSIndexPath *cellIndexPath = [tableView indexPathForRowAtPoint:viewCenterRelativeToTableview];
return cellIndexPath
}
I made this into a Gist for the Swift hearted
Try this solution. It worked for me
#pragma mark - Get textfield indexpath
- (NSIndexPath *)TextFieldIndexpath:(UITextField *)textField
{
CGPoint origin = textField.frame.origin;
CGPoint point = [textField.superview convertPoint:origin toView:self.TblView];
NSIndexPath * indexPath = [self.TblView indexPathForRowAtPoint:point];
NSLog(#"Indexpath = %#", indexPath);
return indexPath;
}
I would like to share #Ertebolle code in swift -
extension UITableView
{
func indexPathForCellContainingView(view1:UIView?)->NSIndexPath?
{
var view = view1;
while view != nil {
if (view?.isKindOfClass(UITableViewCell) == true)
{
return self.indexPathForCell(view as! UITableViewCell)!
}
else
{
view = view?.superview;
}
}
return nil
}
}
Swift 4/5
func indexPathForCellContainingView(view: UIView,
inTableView tableView:UITableView) -> IndexPath? {
let viewCenterRelativeToTableview =
tableView.convert(CGPoint(x: view.bounds.midX, y: view.bounds.midY), to: view)
return tableView.indexPathForRow(at: viewCenterRelativeToTableview)
}

UITableViewCell swiped but delete button doesn't appear

This is odd. I'm right swiping a UITableViewCell in the iPad simulator. Even though the event below fires and the swipedCell is not nil, the Delete button doesn't appear. Actually, it appears-but only sometimes. I never get a bad access or a sigbart.
Here's the code:
- (void)handleSwipeFrom:(UISwipeGestureRecognizer *)recognizer
{
if (userListSwipeRightRecognizer.state == UIGestureRecognizerStateEnded) {
CGPoint swipeLocation = [userListSwipeRightRecognizer locationInView:self.outletView];
NSIndexPath *swipedIndexPath = [self.outletView indexPathForRowAtPoint:swipeLocation];
UITableViewCell* swipedCell = [self.outletView cellForRowAtIndexPath:swipedIndexPath];
[swipedCell setEditing:YES];
}
}
Is this just a simulator issue or am I doing something wrong?
If you simply want to enable swipe-to-delete on your table, there is a much easier way to do it. Implement tableView:commitEditingStyle:forRowAtIndexPath: in your data source and the table view will automatically show the delete button when a cell is swiped.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
If you defined your UITableView in your header, then try:
swipedCell = [self.outletView cellForRowAtIndexPath:swipedIndexPath];
If you use a custom cell and override setEditing, you must call the super method or your delete controls will not be drawn.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
}

Is it possible to refresh a single UITableViewCell in a UITableView?

I have a custom UITableView using UITableViewCells.
Each UITableViewCell has 2 buttons. Clicking these buttons will change an image in a UIImageView within the cell.
Is it possible to refresh each cell separately to display the new image?
Any help is appreciated.
Once you have the indexPath of your cell, you can do something like:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPathOfYourCell, nil] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
In Xcode 4.6 and higher:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
You can set whatever your like as animation effect, of course.
I tried just calling -[UITableView cellForRowAtIndexPath:], but that didn't work. But, the following works for me for example. I alloc and release the NSArray for tight memory management.
- (void)reloadRow0Section0 {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[indexPaths release];
}
Swift:
func updateCell(path:Int){
let indexPath = NSIndexPath(forRow: path, inSection: 1)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) //try other animations
tableView.endUpdates()
}
reloadRowsAtIndexPaths: is fine, but still will force UITableViewDelegate methods to fire.
The simplest approach I can imagine is:
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self configureCell:cell forIndexPath:indexPath];
It's important to invoke your configureCell: implementation on main thread, as it wont work on non-UI thread (the same story with reloadData/reloadRowsAtIndexPaths:). Sometimes it might be helpful to add:
dispatch_async(dispatch_get_main_queue(), ^
{
[self configureCell:cell forIndexPath:indexPath];
});
It's also worth to avoid work that would be done outside of the currently visible view:
BOOL cellIsVisible = [[self.tableView indexPathsForVisibleRows] indexOfObject:indexPath] != NSNotFound;
if (cellIsVisible)
{
....
}
If you are using custom TableViewCells, the generic
[self.tableView reloadData];
does not effectively answer this question unless you leave the current view and come back. Neither does the first answer.
To successfully reload your first table view cell without switching views, use the following code:
//For iOS 5 and later
- (void)reloadTopCell {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
Insert the following refresh method which calls to the above method so you can custom reload only the top cell (or the entire table view if you wish):
- (void)refresh:(UIRefreshControl *)refreshControl {
//call to the method which will perform the function
[self reloadTopCell];
//finish refreshing
[refreshControl endRefreshing];
}
Now that you have that sorted, inside of your viewDidLoad add the following:
//refresh table view
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
You now have a custom refresh table feature that will reload the top cell. To reload the entire table, add the
[self.tableView reloadData]; to your new refresh method.
If you wish to reload the data every time you switch views, implement the method:
//ensure that it reloads the table view data when switching to this view
- (void) viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
}
Swift 3 :
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
Here is a UITableView extension with Swift 5:
import UIKit
extension UITableView
{
func updateRow(row: Int, section: Int = 0)
{
let indexPath = IndexPath(row: row, section: section)
self.beginUpdates()
self.reloadRows(at: [indexPath], with: .automatic)
self.endUpdates()
}
}
Call with
self.tableView.updateRow(row: 1)
Just to update these answers slightly with the new literal syntax in iOS 6--you can use Paths = #[indexPath] for a single object, or Paths = #[indexPath1, indexPath2,...] for multiple objects.
Personally, I've found the literal syntax for arrays and dictionaries to be immensely useful and big time savers. It's just easier to read, for one thing. And it removes the need for a nil at the end of any multi-object list, which has always been a personal bugaboo. We all have our windmills to tilt with, yes? ;-)
Just thought I'd throw this into the mix. Hope it helps.
I need the upgrade cell but I want close the keyboard.
If I use
let indexPath = NSIndexPath(forRow: path, inSection: 1)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) //try other animations
tableView.endUpdates()
the keyboard disappear