I'm adding new cells to a table view using a pull to add function. When you pull down on the table view past a threshold a placeholder cell is added. Then, I call reloadData and becomeFirstResponder on the placeholder cell.
The problem is reloadData doesn't appear to be resetting the tableview frame for iPhone 5 and lower. This causes the placeholder cell to be out of visible range. The table offset should be 0.
Add Placeholder
-(void)todoPlaceHolderAdded {
NSLog(#"todoPlaceHolderAdded");
// create the new item
TodoItem *newItem = [[TodoItem alloc] initWithText:#""];
[_todoList insertObject:newItem atIndex:0];
// refresh the table
[self.tableView reloadData];
// find new cell
ToDoCell* editCell;
for (ToDoCell* cell in _tableView.visibleCells) {
if (cell.todoItem == newItem) {
editCell = cell;
editCell.todoItem = newItem;
_cellInEdit = cell;
editCell.textField.placeholder = #"Enter task";
break;
}
}
// enter edit mode
[editCell.textField becomeFirstResponder];
}
Edit Cell
-(void)cellDidBeginEditing:(ToDoCell *)editingCell {
NSLog(#"cellDidBeginEditing");
_tableView.scrollEnabled = NO;
_tableView.bounces = NO;
_superViewHorizontalScroll.scrollEnabled = NO;
_cellInEdit = editingCell;
//Set offset to current editing cell
_editingOffset = _tableView.contentOffset.y - editingCell.frame.origin.y;
NSLog(#"Table Offset: %f", _tableView.contentOffset.y);
NSLog(#"Frame y: %f", editingCell.frame.origin.y);
NSLog(#"Offset: %f", _editingOffset);
//Slide rows to editing cell
for(ToDoCell* cell in [_tableView visibleCells]) {
//Add flag for cells that edit is in progress
cell.isEditing = YES;
[UIView animateWithDuration:0.3
animations:^{
//Set new frame for cell
cell.frame = CGRectOffset(cell.frame, 0, _editingOffset);
//Reduce alpha for non editing cells
if (cell != _cellInEdit) {
cell.alpha = 0.2;
}
}];
}
}
iPhone 5 and Lower
2015-04-25 23:09:39.093 QuickScrollTest[7996:320879] todoPlaceHolderAdded
2015-04-25 23:09:39.093 QuickScrollTest[7996:320879] cellDidBeginEditing
2015-04-25 23:09:39.093 QuickScrollTest[7996:320879] Table Offset: -136.000000
2015-04-25 23:09:39.093 QuickScrollTest[7996:320879] Frame y: 0.000000
2015-04-25 23:09:39.093 QuickScrollTest[7996:320879] Offset: -136.000000
iPhone 6
2015-04-25 23:11:01.071 QuickScrollTest[8179:323067] todoPlaceHolderAdded
2015-04-25 23:11:01.078 QuickScrollTest[8179:323067] cellDidBeginEditing
2015-04-25 23:11:01.078 QuickScrollTest[8179:323067] Table Offset: 0.000000
2015-04-25 23:11:01.078 QuickScrollTest[8179:323067] Frame y: 0.000000
2015-04-25 23:11:01.078 QuickScrollTest[8179:323067] Offset: 0.000000
Related
I have a UICollectionView which expands on clicking a cell and once the screen fills it becomes scrollable.
Now when I scroll down I need my header view to scroll down with it and for that I've implemented the logic in the layoutAttributesForSupplementaryViewOfKind method in my custom UICollectionViewLayout class.
This works fine but now the issue is that when I the content becomes scrollable and I scroll down few cells and immediately click on a cell to shrink the content back to one screen at that point the header view doesn't gets arranged, i.e it still remains in the last scrolled position.
But there after if I perform any other action like cell tap it gets arranged properly.
I've tried calling setNeedsLayout, setNeedsDisplay and layoutSubviews where I reload my UICollectionView but the header still doesn't updates to its proper position.
Below is the code for my layoutAttributesForSupplementaryViewOfKind method.
Any help is appreciated.
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if (![kind isEqualToString:[myGridHeaderView kind]]) {
return nil;
}
myGridHeaderPosition headerPosition = [[self collectionView] headerPositionAtIndexPath:indexPath];
CGRect cellRect = [[self delegate] getRectForHeaderAtIndex:indexPath headerPosition:headerPosition];
if (CGRectEqualToRect(cellRect, CGRectZero)) {
return nil;
}
myGridHeaderLayoutAttribute* attributes = [myGridHeaderLayoutAttribute layoutAttributesForSupplementaryViewOfKind:kind withIndexPath:indexPath];
CGPoint centerPoint = CGPointMake(CGRectGetMidX(cellRect), CGRectGetMidY(cellRect));
CGSize size = cellRect.size;
UICollectionView * const cv = self.collectionView;
NSInteger zIndex = 1;
CGPoint const contentOffset = cv.contentOffset;
if (contentOffset.x > 0)
{
if (headerPosition != myGridHeaderPositionColumn)
{
centerPoint.x += contentOffset.x;
}
zIndex = 1005;
}
if (contentOffset.y > 0)
{
if (headerPosition != myGridHeaderPositionRow)
{
centerPoint.y += contentOffset.y;
}
zIndex = 1005;
}
if (headerPosition == myGridHeaderPositionCommon) {
zIndex = 1024;
}
attributes.zIndex = zIndex;
attributes.headerPosition = headerPosition;
attributes.center = centerPoint;
attributes.size = size;
attributes.alpha = 1.0;
return attributes;
}
When you scroll up and down , header will be visible and hidden , for use this code.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
isScrollingStart=YES;
NSLog(#"scrollViewDidScroll %f , %f",scrollView.contentOffset.x,scrollView.contentOffset.y);
if (scrollView.contentOffset.y<=124) {
_img_top_header.alpha=scrollView.contentOffset.y/124;
}
else
{
_img_top_header.alpha=1.0;
}
}
must be set image in header.
I have five rows of cells each row containing four cells - 5x4. I am trying to accomplish almost an Apple watch like effect. The row in the center of the screen should have cell sizes of 100x100 and the rest return sizes of 80x80. When scrolled, the row moving away from the center should turn to 80x80 and the row moving into the center should turn 100x100.
I have implemented prepareLayout, layoutAttributesForElementsInRect, and layoutAttributesForItemAtIndexPath
So right now I have a center row of 100x100 cells and the rest 80x80.
To make it dynamic I implement shouldInvalidateLayoutForBoundsChange but nothing happens.
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];
[self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier,
NSDictionary *elementsInfo,
BOOL *stop) {
[elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath,
UICollectionViewLayoutAttributes *attributes,
BOOL *innerStop) {
// NSLog(#"%f, %f", newBounds.origin.x, newBounds.origin.y);
if ((newBounds.origin.y < [[UIScreen mainScreen]bounds].size.height/2-50) && (newBounds.origin.y > [[UIScreen mainScreen]bounds].size.height/2+50)) {
CGRect frame = attributes.frame;
frame.size.width = 100.0f;
frame.size.height = 100.0f;
attributes.frame = frame;
}else{
CGRect frame = attributes.frame;
frame.size.width = 80.0f;
frame.size.height = 80.0f;
attributes.frame = frame;
}
[allAttributes addObject:attributes];
}];
}];
return YES;
}
shouldInvalidateLayoutForBoundsChange: is called pretty frequently (whenever the view is resized, or scrolled...), so you probably don't need to be doing all that work there. If you know your cells will always have a fixed size and position then you can just return NO here.
As long as you have implemented prepareLayout, layoutAttributesForElementsInRect, and layoutAttributesForItemAtIndexPath, then make sure you also implement collectionViewContentSize to return the total size of the content.
I'm trying to create a tableviewCell with 1 label, to expand it when clicking on it and to collopase it again when again clicking on it. Now my animation on how to expand the cell is like this:
CGFloat targetHeightOfCell = [c.textLabel sizeOfMultiLineLabel].height;
_expandedState.height = #(targetHeightOfCell);
CGFloat difference = targetHeightOfCell - DEFAULT_CELL_HEIGHT;
CGFloat targetHeightOfContent = self.tableView.contentSize.height + difference;
[self.tableView beginUpdates];
[self.tableView endUpdates];
[UIView animateWithDuration:0.3f
animations:^{
CGRect frame = self.tableView.frame;
frame.size.height = targetHeightOfContent;
self.tableView.frame = frame;
}
completion:^(BOOL finished) {
}];
and in my heightForRowAtIndexPath I ofcourse return the right height. The cell expands but my calculation of sizeOfMultiLineLabel isn't correct. The text expands but still not all text is visible and so it's still appended by ...
This is my category on UILabel:
- (CGSize)sizeOfMultiLineLabel {
NSAssert(self, #"UILabel was nil");
//Label text
NSString *aLabelTextString = [self text];
//Label font
UIFont *aLabelFont = [self font];
//Width of the Label
CGFloat aLabelSizeWidth = self.frame.size.width;
//Return the calculated size of the Label
CGSize size = [aLabelTextString boundingRectWithSize:CGSizeMake(aLabelSizeWidth, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : aLabelFont
}
context:nil].size;
//Check if the height isn't smaller then the default one
if(size.height < DEFAULT_HEIGHT){
return CGSizeMake(aLabelSizeWidth, DEFAULT_HEIGHT);
} else {
return size;
}
}
What I want is that how long the text is, the label must expand so the user can see it.
I assume that your label has numberOfLines set correctly. If not, you probably want to set it to 0 to ensure that it can have as many lines as needed. You also need to ensure the wrapping mode is correct, e.g. UILineBreakModeWordWrap. Finally, depending on your cell layout there might be margins arount the label - so ensure you account for that when returning the height.
I have a small collectionView with 2 cells. That is centred on the screen vertically. When user selects a cell a new cell appears in the collection view and collection view changes its insets accordingly to middle 3 cells vertically in the screen. However this change is not smooth, it is jumpy. Is there any way to animate collectionView inset change ?
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* array = [super layoutAttributesForElementsInRect:rect];
UICollectionViewLayoutAttributes* att = [array lastObject];
if (att){
CGFloat lastY = att.frame.origin.y + att.frame.size.height;
CGFloat diff = self.collectionView.frame.size.height - lastY;
if (diff > 0){
UIEdgeInsets contentInsets = UIEdgeInsetsMake(diff/2, 0.0, 0.0, 0.0);
self.collectionView.contentInset = contentInsets;
}
self.diff = diff;
}
return array;
}
It did not seem possible to animate the collectionView insets without creating custom UICollectionViewLayout so I changed the logic and implemented changing position of the cell instead of changing the insets of collection view. This gives real smooth animation !
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* array = [super layoutAttributesForElementsInRect:rect];
UICollectionViewLayoutAttributes* att = [array lastObject];
if (att){
CGFloat lastY = att.frame.origin.y + att.frame.size.height;
CGFloat diff = self.collectionView.frame.size.height - lastY;
if (diff > 0){
for (UICollectionViewLayoutAttributes* a in array){
a.frame = CGRectMake(a.frame.origin.x, a.frame.origin.y + diff/2, a.frame.size.width, a.frame.size.height) ;
}
}
}
return array;
}
It appears that ila's answer is no longer accurate as of iOS 12 - the following code shows the inset change animating for me:
if !disableCustomInsets {
UIView.animate(withDuration: 0.2, animations: {
self.disableCustomInsets = true
self.collectionViewLayout.invalidateLayout()
}, completion: nil)
}
I am attempting to create a stack of photos using UICollectionView in which a user can swipe a photo to the side. As a photo is dragged, it gets smaller (to a minimum scale) and the photo behind is enlarged to a maximum scale. This maximum scale is the original size of the foreground photo.
When the pan gesture which drags the foreground photo ends, if it is the minimum scale, it is sent to the back of the stack, otherwise, it is animated back to the centre of the screen and enlarged back to its original size (along with the growing photo behind it).
I hope you're still with me haha.
Now, assuming the photo being dragged is at its minimum size, I remove user interaction of the UICollectionView, animate the photo to the centre of the screen again, and its size is animated to match the rest of the background images. I then send the cell to the back of the UICollectionViews subviews.
Once all of these animations have taken place, I update my data source (an array of images) by adding the photo in front to the back, then deleting that photo at the front.
Then, I update the UICollectionView. This consists of a batch update in which I delete the item at an NSIndexPath of 0 : 0, then, insert an item at NSIndexPath ([photo count] - 1) : 0.
When this batch update finished, I reload my UICollectionView (as only the item at NSIndexPath 0 : 0 has a pan gesture) and re-add user interaction to the UICollectionView.
I hope this makes sense.
Notes
The size of the background photos is a percent of 80%.
The size of the foreground photo is a percent of 100%.
This is relative to the UICollectionView which fits the screen of the iPhone.
My problem
My code seems to work very well. My issue comes from when I take my finger off the screen (the pan gesture ends) and the animation of the photo (centring it to the centre of the screen and sizing it like the rest of the background photos) ends.
The photo dragged off reappears on screen, sizes from the size of the background photos (80%) to the size of the foreground photo (100%) then fades away. Once this happens the photos are reordered as expected.
Does anyone know why this might happen?
Here is my Pan Gesture code:
CGPoint touchTranslation = [gesture translationInView:self.view];
static CGPoint originalCenter;
DIGalleryCollectionViewLayout *galleryCollectionViewLayout = (DIGalleryCollectionViewLayout *)self.galleryCollectionView.collectionViewLayout;
if (gesture.state == UIGestureRecognizerStateBegan) {
//
// Began.
//
originalCenter = gesture.view.center;
} else if (gesture.state == UIGestureRecognizerStateChanged) {
//
// Changed.
//
CGPoint translate = [gesture translationInView:gesture.view.superview];
touchTranslation = CGPointMake(originalCenter.x + translate.x, originalCenter.y + translate.y);
[gesture.view setCenter:touchTranslation];
CGPoint pointsPhotoIsMovedBy = CGPointMake(fabsf(fabsf(originalCenter.x) - fabsf(touchTranslation.x)),
fabsf(fabsf(originalCenter.y) - fabsf(touchTranslation.y)));
// Update cells.
//
CGFloat currentIncrement = MAX(pointsPhotoIsMovedBy.x, pointsPhotoIsMovedBy.y);
for (NSIndexPath *indexPath in self.galleryCollectionView.indexPathsForVisibleItems) {
UICollectionViewCell *cell = [self.galleryCollectionView cellForItemAtIndexPath:indexPath];
if ([indexPath isEqual:[NSIndexPath indexPathForItem:0 inSection:0]]) {
//
// Front.
//
CGFloat frontScalePercent = MIN(GalleryCollectionViewLayoutFrontRatioDefault,
MAX(GalleryViewFrontScaleMinimum, GalleryCollectionViewLayoutFrontRatioDefault - (currentIncrement / 250.0f)));
[cell setTransform:CGAffineTransformMakeScale(frontScalePercent, frontScalePercent)];
} else if ([indexPath isEqual:[NSIndexPath indexPathForItem:1 inSection:0]]) {
//
// Next.
//
CGFloat nextScalePercent = MAX(GalleryCollectionViewLayoutBackRatioDefault,
MIN(GalleryViewNextScaleMaximum, (currentIncrement / 150.0f)));
[cell setTransform:CGAffineTransformMakeScale(nextScalePercent, nextScalePercent)];
} else {
//
// Background.
//
[cell setTransform:CGAffineTransformMakeScale(GalleryCollectionViewLayoutBackRatioDefault,
GalleryCollectionViewLayoutBackRatioDefault)];
}
}
} else if (gesture.state == UIGestureRecognizerStateEnded) {
//
// Ended.
//
if (gesture.view.transform.a == GalleryViewFrontScaleMinimum) {
//
// Next photo.
//
[self.galleryCollectionView setUserInteractionEnabled:NO];
[self.galleryCollectionView sendSubviewToBack:gesture.view];
[UIView animateWithDuration:0.3f
animations:^{
[gesture.view setCenter:self.galleryCollectionView.center];
[gesture.view setTransform:CGAffineTransformMakeScale(GalleryCollectionViewLayoutBackRatioDefault,
GalleryCollectionViewLayoutBackRatioDefault)];
}
completion:^(BOOL finished) {
//
// Data source
//
NSMutableArray *photos = [self.photos mutableCopy];
[photos addObject:[photos objectAtIndex:0]];
[photos removeObjectAtIndex:0];
[self setPhotos:photos];
// Contents
//
[self.galleryCollectionView performBatchUpdates:^{
[self.galleryCollectionView deleteItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:0 inSection:0]]];
[self.galleryCollectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:(self.photos.count - 1) inSection:0]]];
} completion:^(BOOL finished) {
[self.galleryCollectionView reloadData];
[self.galleryCollectionView setUserInteractionEnabled:YES];
}];
}];
} else {
//
// Stay.
//
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionCurveEaseIn
animations:^{
//
// Front cell.
//
[gesture.view setCenter:self.galleryCollectionView.center];
[gesture.view setTransform:CGAffineTransformMakeScale(GalleryCollectionViewLayoutFrontRatioDefault,
GalleryCollectionViewLayoutFrontRatioDefault)];
// Next cell.
//
for (NSIndexPath *indexPath in self.galleryCollectionView.indexPathsForVisibleItems) {
if ([indexPath isEqual:[NSIndexPath indexPathForItem:1 inSection:0]]) {
//
// Next.
//
UICollectionViewCell *cell = [self.galleryCollectionView cellForItemAtIndexPath:indexPath];
[cell setTransform:CGAffineTransformMakeScale(GalleryCollectionViewLayoutBackRatioDefault,
GalleryCollectionViewLayoutBackRatioDefault)];
}
}
}
completion:^(BOOL finished) {
[galleryCollectionViewLayout setFrontRatio:GalleryCollectionViewLayoutFrontRatioDefault];
[galleryCollectionViewLayout setNextRatio:GalleryCollectionViewLayoutBackRatioDefault];
[galleryCollectionViewLayout invalidateLayout];
}];
}
}
And here is my prepareLayout: code for the collection view layout:
NSMutableDictionary *newLayoutInfo = [NSMutableDictionary dictionary];
NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary];
NSIndexPath *indexPath;
for (NSInteger section = 0; section < [self.collectionView numberOfSections]; section++) {
NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
for (NSInteger item = 0; item < itemCount; item++) {
indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
[itemAttributes setSize:[self sizeForPhotoAtIndexPath:indexPath inContainerOfSize:self.collectionView.bounds.size]];
[itemAttributes setCenter:self.collectionView.center];
// Z Index.
//
NSInteger zIndex = itemCount - item;
[itemAttributes setZIndex:zIndex];
// Scale cells based on z position.
//
if (zIndex == itemCount) {
//
// Foreground.
//
[itemAttributes setTransform:CGAffineTransformMakeScale(self.frontRatio, self.frontRatio)];
} else if (zIndex == itemCount - 1) {
//
// Next.
//
[itemAttributes setTransform:CGAffineTransformMakeScale(self.nextRatio, self.nextRatio)];
} else {
//
// Background.
//
[itemAttributes setTransform:CGAffineTransformMakeScale(GalleryCollectionViewLayoutBackRatioDefault,
GalleryCollectionViewLayoutBackRatioDefault)];
}
cellLayoutInfo[indexPath] = itemAttributes;
}
}
newLayoutInfo[GalleryCollectionViewLayoutCellKind] = cellLayoutInfo;
self.layoutInfo = newLayoutInfo;
And as an additional reference, here is my code for sizeForPhotoAtIndexPath: inContainerOfSize:
UIImage *photo = [[(DIGalleryViewController *)self.collectionView.delegate photos] objectAtIndex:indexPath.row];
CGSize size = CGSizeMake(photo.size.width, photo.size.height);
UIGraphicsBeginImageContext(containerSize);
[photo drawInRect:CGRectMake(0.0f, 0.0f, containerSize.width, containerSize.height)];
UIImage *resizedPhoto = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGFloat ratio = photo.size.width / resizedPhoto.size.width;
size = CGSizeMake(resizedPhoto.size.width, photo.size.height / ratio);
return size;