I want to mimic the features of gallery app provided in Sony Xperia in iPhone app. I gallary app, images are shown in grid grouped by date and the first photo of the section is twice in size than the others. On pinching out / in, all photos get zoom out / in.
I used PSTCollectionView as suggested by Lithu T.V and have created a custom layout. In that layout I have overridden - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect. Bellow is the code for same.
// called continuously as the rect changes
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attribs = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *arrmFrames=nil;
for (int i=0;i<attribs.count;i++) {
UICollectionViewLayoutAttributes *attributesInitial=[attribs objectAtIndex:0];
UICollectionViewLayoutAttributes *attributes=[attribs objectAtIndex:i];
//Take initial frame from first cell
if(i==0)
fFirstCellsY = attributes.frame.origin.y;
//while Y is constant, save the adjusted frames for next cells
else if(attributes.frame.origin.y<fFirstCellsY+attributesInitial.frame.size.height)
{
if(arrmFrames==nil)
arrmFrames=[[NSMutableArray alloc]init];
attributes.frame=CGRectMake(attributes.frame.origin.x, attributesInitial.frame.origin.y, attributes.frame.size.width, attributes.frame.size.height);
[arrmFrames addObject:NSStringFromCGRect(CGRectMake(attributes.frame.origin.x, attributes.frame.origin.y+attributes.frame.size.height+10, attributes.frame.size.width, attributes.frame.size.height))];
}
//Adjust the frame of other cells
else
{
CGRect frame = attributes.frame;
attributes.frame=CGRectFromString((NSString*)[arrmFrames objectAtIndex:0]);
[arrmFrames removeObjectAtIndex:0];
[arrmFrames addObject:NSStringFromCGRect(frame)];
}
}
}
return attribs;
}
This works and the layout is looking like I wanted.
By this method more cells become visible than those by using default layout and my layout looks OK. but when I scroll down then some visible cells are getting reloaded. I guess the reason is the delegate method - (PSUICollectionViewCell *)collectionView:(PSUICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath is getting called before - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect which is there in my custom layout. and so the cells which were not visible in default layout but visible in custom layout are reloaded late.
How can I overcome this?
Use UICollectionView
If app is supposed to support in less than 5.0 use PSTCollectionView
A nice startup
Related
I am utilizing a UICollectionView for my universal app. Inside of each collection view cell I have a custom view, which ultimately hold's an image. Each cell is representing a playing card, so when tapped it flip's from front to back.The issue that I am having is when I scale up the size of that subview. If I have it smaller within the cell then everything run's fine. There are no slow down's of any kind. If I then scale up that subview to be larger then there are serious slowdown's. Not only while I scroll through the collection view up to down, but also when I tap the individual cell's. The lag happen's when a row of cell's comes from off screen. There is also lag when I tap a 'card' and it flip's from front to back.
This problem appear's to only exist on the iPad version of the app. When I run it on my iPhone there are no problem's at all in regards to lag.
Is there any reason as to why this would be happening?
Edit to add code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Card" forIndexPath:indexPath];
Card *card = [self.game getCardFromPlayAtIndex:indexPath.item];
[self updateCell:cell usingCard:card withAnimation:NO];
return cell;
}
Abstract method that is used above:
- (void)updateCell:(UICollectionViewCell *)cell usingCard:(Card *)card withAnimation:(BOOL)isAnimated
{
// Using introspection to make sure that our cell is a PlayingCardCollectionViewCell
if ([cell isKindOfClass:[PlayingCardCollectionViewCell class]]) {
// If it is we an take that cell and cast it to a PCCVC and then pull out it's
// outlet for the PlayingCardView
PlayingCardView *playingCardView = ((PlayingCardCollectionViewCell *)cell).playingCardView;
if ([card isKindOfClass:[PlayingCard class]]) {
PlayingCard *playingCard = (PlayingCard *)card;
playingCardView.rank = playingCard.rank;
playingCardView.suit = playingCard.suit;
playingCardView.faceUp = playingCard.isFaceUp;
playingCardView.alpha = playingCard.isUnplayable ? 0.3 : 1.0;
}
}
}
I'd like to customize the animation styles when a UICollectionViewCell is inserted and/or deleted.
The reason why I need this is that by default I see that inserting a cell has a smooth fade in animation, however deleting a cell has a combination of move-to-the-left + fade out animation. I would be very happy with this if not for one problem.
After I delete a cell, it is still reused when I add new ones, and when it's reused it's added not with the default fade in effect, but instead it's a combination of move-to-the-left + fade in.
I'm not sure why I'm getting this inconsistency in animations. If this is a known bug/problem/stupidity(on my side :)) please let me know how to fix it.
Otherwise, let me know how to set custom animations when the cell is deleted (or point me towards a tutorial).
Thanks
UPDATE
Fixed the weird animation behavior by subclassing UICollectionViewFlowLayout and adding this line of code
- (UICollectionViewLayoutAttributes *) initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
return nil;
}
That's it! :)
If you use your own subclass of UICollectionViewLayout, you can implement the methods:
initialLayoutAttributesForAppearingItemAtIndexPath: for insertions
finalLayoutAttributesForDisappearingItemAtIndexPath: for deletions
According to the documentation, the attributes you return are used as starting points for the animation, and the end point are the normal attributes returned by your layout (or the opposite for deletion). Layout attributes include position, alpha, transform...
Of course, it is more work to write your own layout class than to use the Apple provided flow layout.
Edit: To answer your question in the comments, here is a super basic implementation of a layout for rows of items which are all the same size.
A cell has a frame and, by default, an alpha of 1.0 (as defined by layoutAttributesForItemAtIndexPath:). When it is deleted, its properties will be animated from its current state before the deletion to the properties set by finalLayoutAttributesForDisappearingItemAtIndexPath:, which correspond to the same frame and an alpha of 0.0. So it won't move but it will fade out. However, the cells to the right are going to be moved to the left (because their indexPath has changed, and thus their frame as set by layoutAttributesForItemAtIndexPath:).
- (CGSize)collectionViewContentSize
{
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:0];
return CGSizeMake(numberOfItems * ITEM_WIDTH, ITEM_HEIGHT);
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger index = [indexPath indexAtPosition:0];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(index * ITEM_WIDTH, 0, ITEM_WIDTH, ITEM_HEIGHT);
return attributes;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributes = [NSMutableArray new];
NSUInteger firstIndex = floorf(CGRectGetMinX(rect) / ITEM_WIDTH);
NSUInteger lastIndex = ceilf(CGRectGetMaxX(rect) / ITEM_WIDTH);
for (NSUInteger index = firstIndex; index <= lastIndex; index++) {
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndexes:(NSUInteger [2]){ 0, index } length:2];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
attributes.alpha = 0.0;
return attributes;
}
Download the circle Layout. It is a sample custom layout that using
initialLayoutAttributesForAppearingItemAtIndexPath:
finalLayoutAttributesForDisappearingItemAtIndexPath:
That will be a good working material for you.
The same behavior of UICollectionView as described here has been led to this question. Even though I decided to post my own one, because I did further investigations, I didn't want to post in a comment or in edit of the question mentioned above.
What happens?:
When large cells being displayed in a UICollectionView with a UICollectionViewFlowLayout, after scrolling the collection view to a certain offset, the cells will disappear.
When scrolling further until another cell comes into visible area, the vanished/hidden cell becomes visible again.
I tested with a vertical scrolling collection view and full-width-cells, but I'm rather sure, that it would also happen with similar setups for horizontal scrolling.
What are large cells?:
The described behavior happens with cells higher than twice the display height (960.f + 1.f on 3,5 inch displays, 1136.f + 1.f on 4 inch).
What exactly happens?:
When the scrolling offset of the collection view exceeds cell.frame.origin.y + displayHeightOfHardware the cells hidden property is set to YES and -collectionView:didEndDisplayingCell:forItemAtIndexPath: gets called (e.g. the first cell changes to hidden when scrollingOffset.y reaches 481.f on 3,5-inch-iPhone).
As described above, when scrolling until next cell comes into view, the hidden cell gets displayed again (i.e. hidden property changes to NO) and furthermore, when scrolling far enough the cell will never vanish again, when it shouldn't, no matter where you scroll to.
This changes when working with cells larger than triple-display-height (1441.f/1705.f). Those show the same behavior, but it stays the same, no matter how far they're being scrolled up and down.
What else?:
The situation can not be fixed by overriding -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds to return YES.
The cells cannot being forced to display with setting the hidden property to NO programmatically after they were hidden (in didEndDisplayingCell for example)
So, whats the question?:
I'm pretty sure, that this is a bug in UICollectionView/Controller/Cell/Layout and I'll submit a TSI at Apple. But for the meantime: Has anyone any ideas for a quick hack solution?
i have a VERY dirty and internal solution for this problem:
#interface UICollectionView ()
- (CGRect)_visibleBounds;
#end
#interface MyCollectionView : UICollectionView
#end
#implementation MyCollectionView
- (CGRect)_visibleBounds {
CGRect rect = [super _visibleBounds];
rect.size.height = [self heightOfLargestVisibleCell];
return rect;
}
- (float)heightOfLargestVisibleCell {
// do your calculations for current max cellHeight and return it
return 1234;
}
#end
I have a workaround that seems to be working for me and should not run amok of Apple's rules for iOS applications.
The key is the observation that the large cells bounds are the issue. I've worked around that by ensuring that one edge of the cell is within the viewable area of the scrollable content region. You'll obviously need to subclass the UICollectionViewFlowLayout class or UICollectionViewLayout depending on your needs and make use of the contentOffset value to track where you are in the UIScrollView.
I also had to ensure:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
returns YES or face a runtime exception indicating the layout was invalid. I keep the edge of the larger cell bound to the left edge in my case. This way you can avoid the erroneous bounds intersection detection for these larger cells.
This does create more work depending on how you would like the contents of the cell to be rendered as the width/height of the cell is being updated as you scroll. In my case, the subviews within the cell are relatively simple and do not require a lot of fiddling with.
As requested here is an example of my layoutAttributesInRect
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray* attributes = [NSMutableArray array];
NSArray *vertical = myVerticalCellsStore.cells;
NSInteger startRow = floor(rect.origin.y * (vertical.count)/ (vertical.count * verticalViewHeight + verticalViewSpacing * 2));
startRow = (startRow < 0) ? 0 : startRow;
for (NSInteger i = startRow; i < vertical.count && (rect.origin.y + rect.size.height >= i * verticalViewHeight); i++) {
NSArray *horizontals = myHorizontalStore.horizontalCells;
UICollectionViewLayoutAttributes *verticalAttr = [self layoutAttributesForSupplementaryViewOfKind:#"vertical" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
if (CGRectIntersectsRect(verticalAttr.frame, rect)) {
[attributes addObject:verticalAttr];
}
BOOL foundAnElement = NO;
for (NSInteger j = 0 ; j < horizontals.count; j++) {
MYViewLayoutAttributes *attr = (MyViewLayoutAttributes *)[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
if (CGRectIntersectsRect(rect, attr.frame)) {
[attributes addObject: attr];
foundAnElement = YES;
}
else if (foundAnElement) {
break;
}
}
}
return attributes;
}
This is my sanitized code. Basically I calculate about were the first cell should be based on the cell height. In my case that is fixed, so the calculation is pretty easy. But my horizontal elements have various widths. So the inner loop is really about figuring out the right number of horizontal cells to include in the attributes array. There I'm using the CGRectIntersectsRect to determine if the cell intersects. Then the loop keeps going until the intersection fails. And if at least one horizontal cell has been found the loop will break. Hope that helps.
My solution is basically the same as Jonathan's but in a category, so you don't have to use your own subclass.
#implementation UICollectionView (MTDFixDisappearingCellBug)
+ (void)load {
NSError *error = nil;
NSString *visibleBoundsSelector = [NSString stringWithFormat:#"%#isib%#unds", #"_v",#"leBo"];
if (![[self class] swizzleMethod:NSSelectorFromString(visibleBoundsSelector) withMethod:#selector(mtd_visibleBounds) error:&error]) {
FKLogErrorVariables(error);
}
}
- (CGRect)mtd_visibleBounds {
CGRect bounds = [self mtd_visibleBounds]; // swizzled, no infinite loop
MTDDiscussCollectionViewLayout *layout = [MTDDiscussCollectionViewLayout castedObjectOrNil:self.collectionViewLayout];
// Don`t ask me why, but there's a visual glitch when the collection view is scrolled to the top and the max height is too big,
// this fixes it
if (bounds.origin.y <= 0.f) {
return bounds;
}
bounds.size.height = MAX(bounds.size.height, layout.maxColumnHeight);
return bounds;
}
#end
I found that this issue only occurred when using a subclassed UICollectionViewLayoutAttributes and when that attribute class did not have a correct isEqual: method.
So for example:
#implementation COGridCollectionViewLayoutAttributes
- (id)copyWithZone:(NSZone *)zone
{
COGridCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
attributes.isInEditMode = _isInEditMode;
return attributes;
}
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![[other class] isEqual:[self class]]) {
return NO;
}
if ([((COGridCollectionViewLayoutAttributes *) other) isInEditMode] != [self isInEditMode]) {
return NO;
}
return [super isEqual:other];
}
#end
Worked but originally I had:
return YES;
This is on iOS 7.
I have a bare-bones sample project here:
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
In this project, a UITableView has a custom UITableViewCell. In each cell are 3 UIViews containing a label.
The goal is to expand the cell when tapped, then collapse it when tapped again. The cell itself must change it’s height to expose the subviews. Inserting or removing rows is unacceptable.
The demo project works almost as expected. In fact, in iOS 4.3 it works perfect. Under iOS 5, however, when the rows collapse, the previous cells magically disappear.
To re-create the problem, run the project in the simulator or device with iOS 5 and tap the first cell to expand it. Then tap the cell again to collapse it. Finally, tap the cell directly underneath it. The previous one disappears.
Continuing the tapping for each cell in the section will cause all the cells to disappear, to where the entire section is missing.
I’ve also tried using reloadData instead of the current setup, but that ruins the animations and feels a bit like a hack anyway. reloadRowsAtIndexPaths should work, but the question is why doesn’t it?
See images of what's happening below:
Table appears:
Cell expands:
Cell collapses:
Cell disappears (when tapping the cell underneath):
Keep repeating until the entire section disappears:
EDIT:
Overriding the alpha is a hack, but works. Here is another 'hack' that fixes it as well but WHY does it fix it?
JVViewController.m line 125:
if( previousIndexPath_ != nil )
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if( expanded )
{
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
else if( currentCellSameAsPreviousCell )
{
[previousCell setExpanded:YES];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
//[indicesToReload addObject:[previousIndexPath_ copy]];
}
EDIT 2:
Made a few minor changes to demo project, worth checking out and reviewing JVViewController didSelectRowAtIndexPath method.
Your problem is in setExpanded: in JVCell.m, you are directly editing the frame of the target cell in that method.
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
CGFloat newHeight = heightCollapsed_;
if( expanded_ ) newHeight = heightExpanded_;
CGRect frame = self.frame;
frame.size.height = newHeight;
self.frame = frame;
}
Update it to:
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
}
Then remove the call to -reloadRowsAtIndexPaths:withRowAnimation: at line 163 of JVViewController.m and it will animate as expected.
-reloadRowsAtIndexPaths:withRowAnimation: expects different cells to be returned for the provided indexPaths. Since you are only adjusting sizes -beginUpdates & -endUpdates is sufficient to layout the table view cells again.
May be I am missing a point, but why dont you just use:
UITableViewRowAnimationNone
I mean instead of :
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
use
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];
To animate the height changes of a tableView just call.
[tableView beginUpdates];
[tableView endUpdates];
Don't call reloadRowsAtIndexPaths:
See Can you animate a height change on a UITableViewCell when selected?
The cell that is fading out is the previous cell that is not changing size. As the documentation of reloadRowsAtIndexPaths:withRowAnimation: states:
The table animates that new cell in as it animates the old row out.
What happens is the opacity is set to 1 then immediately set to 0 and so it fades out.
If both the previous and the new cell change size then it works as intended. This is because the begin/end Updates notice the height changes and create new animations on those cells overriding the reloadRowsAtIndexPaths:withRowAnimation: ones.
Your problem is due to abusing reloadRowsAtIndexPaths:withRowAnimation: to resize the cells when it's intended for loading new cells.
But you don't need reloadRowsAtIndexPaths:withRowAnimation: at all. Just change the expanded state of the cells and do the begin/end updates. That will handle all the animation for you.
As a side note I found the blue selection a little annoying, in JVCell set the selectedBackgroundView to the same image as the backgroundView (or create a new image that has the correct look of a selected cell).
EDIT:
Move the statement adding previousIndexPath_ to indicesToReload to the if statement (at line 132) so that it is only added if the previous cell was expanded and needs to resize.
if( expanded ) {
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
This removes the case where the previous collapsed cell would disappear.
Another option would be to set previousIndexPath_ to nil when the current cell is collapsed and only set it when a cell expands.
This still feels like a hack. Doing both the reloadRows and the begin/end Updates causes the tableView to reload everything twice but both seem to be needed to animate correctly. I suppose if the table is not too large this won't be a performance problem.
Short, pragmatic answer: Changing UITableViewRowAnimationAutomatic to UITableViewRowAnimationTop solves the issue. No more disappearing rows! (tested on iOS 5.1)
Another short, pragmatic answer, since UITableViewRowAnimationTop is said to cause its own issues: Create a new cell view instead of modifying the existing one's frame. In a real app the data displayed in the cell view is supposed to be in the Model part of the app anyway, so if properly designed it shouldn't be a problem to create another cell view which displays the same data only in a different manner (frame in our case).
Some more thoughts regarding animating the reload of the same cell:
UITableViewRowAnimationAutomatic seems to resolve to UITableViewRowAnimationFade in some cases, which is when you see the cells fading away and disappearing. The new cell is supposed to fade in while the old one fades out. But here the old cell and the new one are one and the same - So, could this even work? In the Core Animation level, Is it possible to fade out a view AND fade it in at the same time? Sounds dubious. So the result is that you just see the fade out. This could be considered an Apple bug, since an expected behavior could be that if the same view has changed, the alpha property wouldn't be animated (since it can't animate both to 0 and to 1 at the same time), but instead just the frame, color etc. would be animated.
Note the problem is just in the animation's display - if you scroll away and back, everything will appear correctly.
In iOS 4.3 the Automatic mode might have been resolved to something other than Fade which is why things work there (as you write they do) - I didn't dig into that.
I don't know why iOS chooses the Fade mode when it does. But one of the cases it does is when your code asks reloads a previously tapped cell, which is collapsed, and is different than the current tapped cell. Note the previously tapped cell is always reloaded, this line in your code is always called:
[indicesToReload addObject:[previousIndexPath_ copy]];
This explains the magic disappearing cells scenario you have described.
By the way, the beginUpdates/endUpdates seem like a hack to me. This pair of calls is just supposed to contain animations, and there aren't any animations you are adding in addition to the rows you already asked to reload. All it did in this case is magically cause the Automatic mode to not choose Fade in some cases - But this just obscured the problem.
A final note: I played around with the Top mode and found it can also cause problems. For example plugging the following code makes cells disappear funkily:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
Not sure if there is a real issue here (similar to the one with fading a view in and out at the same time), or maybe an Apple bug.
I just downloaded your project & found this section of code in didSelectRowAtIndexPath delegate where reloadRowsAtIndexPaths is used.
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];
instead of the above why don't you try this?
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
The reason i am suggesting this is that I believe reloadRowsAtIndexPaths:... only works when wrapped inbetween calls to:
- (void)beginUpdates;
- (void)endUpdates;
Outside of that, behavior is undefined and as you've discovered, fairly unreliable. Quoting relevant part of "Table View Programming Guide for iPhone OS":
To animate a batch insertion and deletion of rows and sections, call the insertion and deletion methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion and deletion methods within this block, row and section indexes may be invalid. beginUpdates...endUpdates blocks are not nestable.
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
The reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods, which were introduced in iPhone OS 3.0, are related to the methods discussed above. They allow you to request the table view to reload the data for specific sections and rows instead of loading the entire visible table view by calling reloadData.
There could be other valid reason but let me mull on this a bit, since i have your code too I could muck around with it. Hopefully we should figure it out...
When you use this method, you have to be sure that you are on the main thread.
Refreshing a UITableViewCell as follow should do the trick :
- (void) refreshTableViewCell:(NSNumber *)row
{
if (![[NSThread currentThread] isMainThread])
{
[self performSelector:_cmd onThread:[NSThread mainThread] withObject:row waitUntilDone:NO];
return;
}
/*Refresh your cell here
...
*/
}
I think you should not retain the previous indexPath when the cell is not expanded,
try by modifying you did select method like the below, its working fine..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL currentCellSameAsPreviousCell = NO;
NSMutableArray *indicesToReload = [NSMutableArray array];
if(previousIndexPath_ != nil)
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if(expanded)
{
[previousCell setExpanded:NO];
}
else if (currentCellSameAsPreviousCell)
{
[previousCell setExpanded:YES];
}
[indicesToReload addObject:[previousIndexPath_ copy]];
if (expanded)
previousIndexPath_ = nil;
else
previousIndexPath_ = [indexPath copy];
}
if(currentCellSameAsPreviousCell == NO)
{
JVCell *currentCell = (JVCell*)[self cellForIndexPath:indexPath];
BOOL expanded = [currentCell expanded];
if(expanded)
{
[currentCell setExpanded:NO];
previousIndexPath_ = nil;
}
else
{
[currentCell setExpanded:YES];
previousIndexPath_ = [indexPath copy];
}
// moving this line to inside the if statement blocks above instead of outside the loop works, but why?
[indicesToReload addObject:[indexPath copy]];
}
// commenting out this line makes the animations work, but the table view background is visible between the cells
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
// using reloadData completely ruins the animations
[tableView beginUpdates];
[tableView endUpdates];
}
This problem is caused by returning cached cells in cellForRowAtIndexPath.
The reloadRowsAtIndexPaths is expecting to get fresh new cells from cellForRowAtIndexPath.
If you do that you will be ok ... no workarounds required.
From Apple doc:
"Reloading a row causes the table view to ask its data source for a new cell for that row."
I had a similar issue where I wanted to expand a cell when a switch is activated to display and extra label and button in the cell that is normally hidden when the cell is at its default height (44). I tried various versions of reloadRowsAtPath to no avail. Finally I decided to keep it simpler by adding a condition at heightForRowAtIndexPath like so:
override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if ( indexPath.row == 2){
resetIndexPath.append(indexPath)
if resetPassword.on {
// whatever height you want to set the row to
return 125
}
}
return 44
}
And in whatever code you want to trigger the expanding of the cell just insert tableview.reloadData(). In my case it was when a switch was turned on to indicate the desire to reset a password.
#IBAction func resetPasswordSwitch(sender: AnyObject) {
tableView.reloadData()
}
There is no lag with this approach, no visible way to see that the table reloaded and the expansion of the sell is done gradually like you'd expect. Hope this helps someone.
#Javy, i noticed some weird behavior while testing your app.
While running on iPhone 5.0 simulator the previousIndexpth_ variable is of
class NSArray (it looks like.) here is the debugger output
(lldb) po previousIndexPath_
(NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
<JVSectionData: 0x6a61230>,
<JVSectionData: 0x6a64920>,
<JVSectionData: 0x6a66260>
)
(lldb) po [previousIndexPath_ class]
(id) $7 = 0x0145cb64 __NSArrayI
Whereas in iPhone 4.3 simulator it is of type NSIndexPath.
lldb) po [previousIndexPath_ class]
(id) $5 = 0x009605c8 NSIndexPath
(lldb) po previousIndexPath_
(NSIndexPath *) $6 = 0x04b5a570 <NSIndexPath 0x4b5a570> 2 indexes [0, 0]
Are you aware of this issue? Not sure whether this will help but thought of letting you know.
Try this hope it will help u Cell Expansion
I want to make horizontal paging in my app.
I have big text, which placed in UITextView, but I want to make horizontal paging, like iBooks or bookmate.
Do you have any solution or idea?
Have a look at the new (iOS 5) class UIPageViewController. For the iBooks page curl effect, try using
[controller initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
You can then set the view controllers using setViewControllers:direction:animated:completion:. For more reference for this class, visit the UIPageViewController Class Reference.
Use this handy class called PagedView which manages a paging scroll view as well as view loading/unloading and reuse for you.
You have to implement two delegate methods to get it working:
- (NSUInteger)numberOfPagesInPagedView:(PagedView *)view;
- (UIView *)pagedView:(PagedView *)view viewForPageAtIndex:(NSUInteger)page;
They will look familiar if you've ever used UITableView. In numberOfPagesInPagedView: you just have to return the number of pages you want to display.
- (NSUInteger)numberOfPagesInPagedView:(PagedView *)view
{
// return the number of pages in the paging view
return 10;
}
In pagedView:viewForPageAtIndex: you have to return a view for a specific page index. You can reuse the views by sending the dequeueReusableViewWithIdentifier: message to the paged view.
- (UIView *)pagedView:(PagedView *)pagedView viewForPageAtIndex:(NSUInteger)page
{
static NSString *reuseIdentifier = #"PageIdentifier";
UIView *view = [pagedView dequeueReusableViewWithIdentifier:reuseIdentifier];
if(view == nil) {
view = [[[MyPageView alloc] initWithFrame:view.bounds] autorelease];
}
// add contents specific to this page index to the view
return view;
}
In order to get view reuse working, your UIView subclass (MyPageView) should conform to the ReusableObject protocol and implement reuseIdentifier (in this case you would return #"PageIdentifier").
I have used this class in a number of projects and it works pretty well.
UIScrollView with pageEnabled turned on?
You can use a UIWebView (subclass of UIScrollView) for your horizontal paging needs.
For it to work, you'd need to split the NSString that you use to store your text, depending on the size (and height) of your web view.
Store the split NSString into an NSArray, and then depending on user swipe, load up the correct 'page' (array index) and display with animation.
Hope that helps.