iOS - Detect UITableViewCell being moved out of the visible view? - ios

As soon as the cell is no longer visible on the screen I need to be notified.
UITableView already has a delegate method called tableView:didEndDisplayingCell:forRowAtIndexPath: but this delegate method never gets called. And yes I do have the delegate for my UITableView set.
Any other ways to detect a cell being removed? I need to be able to save the content (inputs) of this cell before it's being reused by another item.
EDIT:
According to the documentation tableView:didEndDisplayingCell:forRowAtIndexPath: is iOS 6 and higher API. Is there a way to achieve this on iOS 5?

On versions of iOS older than 6.0, the table view doesn't send the tableView:didEndDisplayingCell:forRowAtIndexPath: message.
If you are using a subclass of UITableViewCell, you can get the same effect on older versions of iOS by overriding didMoveToWindow:
- (void)didMoveToWindow {
if (self.window == nil) {
// I have been removed from the table view.
}
}
You may need to give your cell a (weak or unsafe_unretained) reference back to your table view delegate so you can send the delegate a message.
However, you can't rely only on didMoveToWindow for all versions of iOS. Before iOS 6, a table view always removed a table view cell as a subview before reusing it, so the cell would always receive didMoveToWindow before being reused. However, starting in iOS 6, a table view can reuse a cell without removing it as a subview. The table view will simply change the cell's frame to move it to its new location. This means that starting in iOS 6, a cell does not always receive didMoveToWindow before being reused.
So you should implement both didMoveToWindow in your cell subclass, and tableView:didEndDisplayingCell:forRowAtIndexPath: in your delegate, and make sure it works if both are called, or if just one is called.

I ended up using the combination of below to make sure that the logic applies to both iOS 5.0 and 6.0
CELL LOGIC
#protocol MyCellDelegate
- (void)myCellDidEndDisplaying:(MyCell *)cell;
#end
#implementation MyCell
// Does not work on iOS 6.0
- (void)removeFromSuperview
{
[super removeFromSuperview];
[self.delegate myCellDidEndDisplaying:(MyCell *)self];
}
#end
VIEWCONTROLLER LOGIC
#implementation MyViewcontroller
- (void)myCellDidEndDisplaying:(MyCell *)cell
{
IndexPath *indexPath = [self.tableView indexPatForCell:cell];
// do stuff
}
// Does not work on iOS below 6.0
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
}
#end

tableView:didEndDisplayingCell:forRowAtIndexPath:
is only available in iOS6 and later.
One (albeit slow) way to accomplish what you are after would be to use the scrollView delegate methods to monitor when the tableview scrolls. From there, call:
NSArray *visiblePaths = [tableView indexPathsForVisibleRows];
and check for any changes to the array of visible paths.

Related

How can I allocate and initialize a custom UITableViewCell NOT in cellForRowAtIndexPath?

I won't go into the WHY on this one, I'll just explain what I need.
I have a property in my implementatioon file like so...
#property (nonatomic, strong) MyCustomCell *customCell;
I need to initialize this on viewDidLoad. I return it as a row in cellForRowAtIndexPath always for the same indexPath, by returning "self.customCell".
However it doesn't work, I don't appear to be allocating and initializing the custom cell correctly in viewDidLoad. How can I do this? If you know the answer, save yourself time and don't read any further.
Ok, I'll put the why here in case people are curious.
I had a table view which was 2 sections. The first section would always show 1 row (Call it A). The second section always shows 3 rows (Call them X, Y, Z).
If you tap on row A (which is S0R0[Section 0 Row]), a new cell would appear in S0R1. A cell with a UIPickerView. The user could pick data from the UIPickerView and this would update the chosen value in cell A.
Cell X, Y & Z all worked the same way. Each could be tapped, and they would open up another cell underneath with their own respective UIPickerView.
The way I WAS doing this, was all these cell's were created in the Storyboard in the TableView, and then dragged out of the View. IBOutlets were created for all. Then in cellForRAIP, I would just returned self.myCustomCellA, or self.myCustomCellY.
Now, this worked great, but I need something more custom than this. I need to use a custom cell, and not just a UITableViewCell. So instead of using IBOutlets for all the cells (8 cells, 4 (A,X,Y,Z) and their 4 hidden/toggle-able UIPickerView Cell's), I created #properties for them in my implementation file. But it's not working like it did before with the standard UITableViewCell.
I'm not sure I'm initializing them correctly? How can I properly initialize them in/off viewDidLoad so I can use them?
.m
#interface MyViewController ()
#property (strong, nonatomic) MyCustomCell *myCustomCellA;
...
viewDidLoad
...
self.myCustomCellA = [[MyCustomCell alloc] init];
...
cellForRowAtIndexPath
...
return self.myCustomCellA;
...
If only I understood your question correctly, you have 3 options:
I would try really hard to implement table view data source with regular dynamic cells lifecycle in code and not statically – this approach usually pays off when you inevitably want to modify your business logic.
If you are certain static table view is enough, you can mix this method with overriding data source / delegate methods in your subclass of table view controller to add minor customisation (e.g. hiding certain cell when needed)
Alternatively, you can create cells using designated initialiser initWithStyle:reuseIdentifier: to instantiate them outside of table view life cycle and implement completely custom logic. There is nothing particular that you should do in viewDidLoad, that you wouldn't do elsewhere.
If you have a particular problem with your code, please post a snippet so community can help you
I suggest you to declare all your cells in storyboard (with date picker at right position) as static table and then override tableView:heightForRowAtIndexPath:
Define BOOL for determine picker visibility and its position in table
#define DATE_PICKER_INDEXPATH [NSIndexPath indexPathForRow:1 inSection:0]
#interface YourViewController ()
#property (assign, nonatomic) BOOL isPickerVisible;
#end
Then setup initial value
- (void)viewDidLoad {
[super viewDidLoad];
self.isPickerVisible = YES;
}
Override tableView delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:DATE_PICKER_INDEXPATH] && !self.isPickerVisible) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
And finally create method for toggling picker
- (void)togglePicker:(id)sender {
self.isPickerVisible = !self.isPickerVisible;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
which you can call in tableView:didSelectRowAtIndexPath:
According to your problem, you can create pairs (NSDictionary) of index path and bool if its visible and show/hide them according to that.
Here's what I was looking for:
MyCustomCell *cell = (MyCustomCell *)[[[UINib nibWithNibName:#"MyNibName" bundle:nil] instantiateWithOwner:self options:nil] firstObject];

How to receive cell highlight actions in UICollectionView subclass?

I would like to subclass a UICollectionView (not a UICollectionViewController), and I would like to know how I can set it up so that when the user highlights (or selects) a cell, the collection view can be notified, so I can perform a little animation on the cell. You may ask why I can't do that in a view controller. I chose to subclass UICollectionView so that it could be reusable. I am relatively new to iOS programming, and I would welcome any suggestions or ideas.
You can use a block ^{}
Create a class with a .xib file. The .xib file will be used for each cell.
In your .xib file, add a clear UIButton, so that is on top of all your subviews. So the user can click on it.
In your .h file add
#property (copy, nonatomic) void (^actionBlock)(void);
In your .m file add and link it to your UIButton in the .xib file
- (IBAction)showAnimation:(id)sender
{
if (self.actionBlock) {
self.actionBlock();
}
}
Now in UICollectionViewController, cellForItemAtIndexPath, call the block
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCellClass *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:#"cell"
forIndexPath:indexPath];
cell.actionBlock = ^{
//Here you have access to indexPath.section and indexPath.row
NSLog(#"Going to animate the cell %# x %#", indexPath.section, indexPath.row);
//do any other code for this specific cell
};
return cell;
}
Using action blocks, is like opening a portal into each cell, happy coding
UICollectionView is the view and UICollectionViewController is the viewController.
The UICollectionView you are subclassing is use for updating user interface with viewController's core logic to be triggered while highlighting (or selecting) the cell.
So you should set up the selected logic in your viewController.
If you understood Delegate Pattern, the normal way to update your UICollectionView's cell while selected is using a delegate. When something triggered, call viewcontroller to do that for you.
Check out these links about
Cocoa MVC Design Pattern and Designing Your Data Source and Delegate for CollectionView.

Only animate cell from willDisplayCell when user scroll, not after reloadData

Ok so I have a custom animation being implemented inside willDisplayCell method. It is working fine when I scroll the view up and down. When I tap on one of the row, it will be pushed to another view controller to show more details and let user update the data.
The issue is when the user get back to the tableview. I called the [tableView reloadData] method inside the viewWillAppear to make sure updated data is shown. This will trigger the animation transition that I set up earlier.
My question is: Is there a way to only perform the animation when user scroll up/down the tableview, not when the reloadData is called?
If there's a way to mix between the willDisplayCell with scrollViewDidScroll or something along that line, it would be awesome.
Thanks!
The easiest solution would be to add a state flag that would tell the willDisplayCell whether it should actually animate.
Add a property to your UITableViewDelegate:
#property (nonatomic) BOOL shouldPreventDisplayCellAnimation;
Set the property before and after calling reloadData:
- (void)viewWillAppear:(BOOL)animated
{
…
self.shouldPreventDisplayCellAnimation = YES;
[self.tableView reloadData];
self.shouldPreventDisplayCellAnimation = NO:
}
Modify willDisplayCell to animate on condition
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.shouldPreventDisplayCellAnimation) {
//animate
}
}

Calling UICollectionView#reloadData in UICollectionViewDelegate#collectionView:didSelectItemAtIndexPath: hides all cells

I have a UIViewController which has the following implementation for the didSelectItemAtIndexPath
#interface
id section1Item
NSMutableArray *section2Items
NSMutableArray *section3Items
#end
#implementation
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
;
} else if (indexPath.section == 1) {
self.section1Item = [self.section2Items objectAtIndex:indexPath.row];
} else { // indexPath.section == 2
id newSection2Item = [self.section3Items objectAtIndex:indexPath.row];
[self.section2Items addObject:newSection2Item];
[self.section3Items removeObject:newSection2Item];
}
[collectionView reloadData];
}
#end
The idea behind the code is that my collectionView has a static number of sections, and taping on an item in section 3 moves the item to section 2, and tapping on an item in section 2 makes it an item in section 1.
However once I make the changes to my dataStructure (section1Item, section2Items and section3Items), and call reloadData, all my UICollectionView cells disappear. A few symptoms of the issue
After the reloadData call, non of my dataSource methods get recalled. I tried putting a breakpoint in my implementation of numberOfSectionsInCollectionView and collectionView:numberOfItemsInSection but they don't get hit.
I tried debugging using RevealApp, and I found out that after reloadData call, all my UICollectionViewCell's have their hidden property set to "YES", even though I don't have any code in my code base calling .hidden = YES;
I also tried overriding UICollectionViewCell#setHidden to detect what (if any) part of the UIKit framework calls it, and again there was no breakpoint triggers.
Tools details: I'm working with XCode5-DP6 on iOS7 simulator.
UPDATE: My UICollectionView shows all the cells correctly on first render.
Ok peeps, so I was able to figure out the issue. The delegate (self) was a subclass of UIViewController. In the init, I was assigning self.view = viewFromStoryBoard where viewFromStoryBoard was passed in by the caller and which was setup in a storyboard.
Since I was not really using any of the facilities offered by subclass UIViewController, I decided to switch to subclassing NSObject and manually retaining the pointer to the UICollectionView.
This fixed my problem. However I'm not a 100% on the exact nature of the issue. I'm guessing somehow overriding a UIViewController's view isn't all that it seems.
There are lots of bugs with iOS 7 and UICollectionView... In my case reloadData doesn't work correctly, it works with delay.

Objective-C: How to declare/define method used on tableView

My initial problem of multiple line selection in a UITableView has been answered in this question. But the answer left me at a point where I can't go on on my own, as I am very new to Objective C and iOS development.
Following daxnitros answer, I want to implement the code he/she suggested:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView indexPathIsSelected:indexPath]) {
[tableView removeIndexPathFromSelection:indexPath];
} else {
[tableView addIndexPathToSelection:indexPath];
}
// Update the cell's appearance somewhere here
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
I still need the methods and I thought I can do it for indexPathIsSelected (for example) like this:
#interface MyTableViewController ()
- (BOOL)indexPathIsSelected:(NSIndexPath *)indexPath;
#end
#implementation MyTableViewController
// ...
- (BOOL)indexPathIsSelected:(NSIndexPath *)indexPath
{
BOOL bIsSelected = NO;
// ...
return bIsSelected;
}
#end
But that doesn't work. The error message is: No visible #interface for 'UITableView' declares the selector 'indexPathIsSelected:' Note: The same happens, if I declare the method in the .h file's interface instead.
Now, what baffles me, is this: [tableView indexPathIsSelected:indexPath] is somehow called on the tableView object and I don't even know why. Is that something I have to take into account in my method declaration/definition? I feel really stupid right now, that I can't even write a method by seeing its invocation.
How do I define and declare the method indexPathIsSelected correctly, so I can use it properly?
In your didSelectRowAtIndexPath, the variable tableView is a UITableView.
Your implementation for indexPathIsSelected is in class MyTableViewController, which is probably a UITableViewController.
UITableViewController and UITableView are different classes.
So you can't find the method indexPathIsSelected on UITableView because it's not implemented there, it's implemented on MyTableViewController which is a different class.
SO... I'm going to take an educated guess and assume that didSelectRowAtIndexPath is part of class MyTableViewController. If this is the case, then
[self indexPathIsSelected:indexPath]
may be the answer (i.e. call indexPathIsSelected in self rather than the table view).
The error message you're seeing is the key to the problem. The method indexPathIsSelected is implemented in your custom class MyTableViewController. However, the UITableView you have is apparently still a basic UITableView. At the very least you'll need to go into the storyboard and set the custom class of the table view controller object to MyTableViewController.
To do this, open the storyboard (or nib) and select the table view controller. Then in the identity inspector (on the right hand side, typically), under custom class, select MyTableViewController from the drop down.

Resources