I added a GIF to describe a problem. I have a UICollectionView with a lot of cells and each cell has a CALayer inside. I have a pinch gesture in my UICollectionView. When it zooming in it looks like each cell zooming apart. See spaces between cells on gif. Is it possible to zoom in each cells together? Thanks in advance
Code:
Cell subclass
#property (nonatomic, strong) CALayer *circularLayer;
- (void)layoutSubviews
{
[self updateRoundedCorners];
}
- (void)updateRoundedCorners
{
CGRect bounds = self.bounds;
self.circularLayer.bounds = bounds;
self.circularLayer.position = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
}
#pragma mark - Property Set
- (void)setCellObject:(VenueLayoutCellObject *)cellObject
{
self->_cellObject = cellObject;
self.objectBackgroundColor = cellObject.objectColor;
self.type = cellObject.type;
}
Controller
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer *)gesture
{
static CGFloat scaleStart;
if (gesture.state == UIGestureRecognizerStateBegan) {
scaleStart = self.venueLayoutZoom;
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
self.venueLayoutZoom = scaleStart * gesture.scale;
[self.activeCollectionView.collectionViewLayout invalidateLayout];
}
}
Updated:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
VenueLayoutCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kVenueLayoutCellReuseIdentifier forIndexPath:indexPath];
self.activeCollectionViewCellsDictionary[indexPath] = cell;
if (self.activeCollectionViewObjects.count > indexPath.section) {
NSArray *rows = self.activeCollectionViewObjects[indexPath.section];
if (rows.count > indexPath.row) {
if ([rows[indexPath.row] isKindOfClass:[VenueLayoutCellObject class]]) {
VenueLayoutCellObject *object = rows[indexPath.row];
cell.cellObject = object;
}
}
}
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
CGFloat widthAndHeight = [self widthAndHeightForActiveCollectionViewItem];
return CGSizeMake(widthAndHeight *self.venueLayoutZoom, widthAndHeight * self.venueLayoutZoom);
}
- (CGFloat)widthAndHeightForActiveCollectionViewItem
{
NSArray *array = self.activeCollectionViewObjects;
__block NSInteger maxCount = 0;
[array enumerateObjectsUsingBlock:^(NSArray *subArray, NSUInteger idx, BOOL * _Nonnull stop) {
if (subArray.count > maxCount) {
maxCount = subArray.count;
}
}];
CGFloat widthAndHeight = CGRectGetWidth(self.activeCollectionView.bounds) / maxCount;
return widthAndHeight;
}
The changes you are making to the layer are implicitly animated, meaning that they are performed with an animation, even though you haven't specifically told them to.
Since you're responding to user gestures, you don't want the changes to be animated as this is making them lag behind the gesture.
You can turn off implicit animations during layoutSubviews like this:
- (void)layoutSubviews
{
[super layoutSubviews];
[CATransaction begin];
[CATransaction setDisableActions: YES];
[self updateRoundedCorners];
[CATransaction commit];
}
Cell subclass
#property (nonatomic, strong) CALayer *circularLayer;
- (void)layoutSubviews
{
[super layoutSubviews];
[self updateRoundedCorners];
}
The only problem I see with this code importantly is missing [super layoutSubviews];
If it doesn't work do provide more code.
Related
I have a menuView in a list view controller. The menuView added on the UITableViewCell when a more button in the cell being taped.
I achieved the effect with singleton.
#implementation ProductsOperationMenu
static ProductsOperationMenu *_instance;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] initWithFrame:CGRectZero];
});
return _instance;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
ZBMyProductsCell.m
#implementation ZBMyProductsCell
- (void)awakeFromNib
{
[super awakeFromNib];
_operationMenu = [[ProductsOperationMenu alloc] initWithFrame: CGRectZero];
}
- (IBAction)operationButtonClick:(UIButton *)sender {
if ([self.contentView.subviews containsObject:_operationMenu]) {
_operationMenu.hidden = ![_operationMenu isHidden];
} else{
[self.contentView addSubview:_operationMenu];
_operationMenu.hidden = NO;
}
[_operationMenu mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(205);
make.height.mas_equalTo(60);
make.bottom.mas_equalTo(self.operationButton).offset(0);
make.right.mas_equalTo(self.operationButton.mas_left).offset(-10);
}];
}
Without Singleton, it became this:
So the question come.
I want to put the menuView on the controller's view, because it is unique or hidden, which used to belong to the cell.
How to convert layout of the more button selected to the controller's view?
How to use the methods to calculate?
- convertPoint:toView:
- convertPoint:fromView:
......
I did it in a simple way. Here is the code:
- (void)clickOperationButtonOfProductsCell:(ZBMyProductsCell *)myProductsCell{
NSUInteger * operationIndex = [self.myProductsTableView.visibleCells indexOfObject:myProductsCell];
CGFloat originY = operationIndex.row * 110 + 50 + 40;
CGRect originFrame = CGRectMake(KScreenWidth - 55, originY, 0, 60);
CGRect finalFrame = CGRectMake(KScreenWidth - 260, originY, 205, 60);
self.operationMenuView.frame = originFrame;
[UIView animateWithDuration: 0.5 delay: 0 options: UIViewAnimationOptionCurveEaseIn animations:^{
self.operationMenuView.frame = finalFrame;
} completion:^(BOOL finished) { }];
}
How to achieve it more adaptively?
Maybe you can try it like this:
// here is your cell where the more button belongs to
#interface ZBMyProductsCell: UITableViewCell
#property (nonatomic, copy) void(^moreAction)(ZBMyProductsCell *cell);
#end
#implementation ZBMyProductsCell
- (void)_moreButtonClicked:(UIButton *)sender {
!self.moreAction ?: self.moreAction(self);
}
#end
// here is your view controller where your table view belongs to
// this is a `UITableViewDataSource` delegate method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZBMyProductsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ZBMyProductsCell" forIndexPath:indexPath];
// Configure the cell...
cell.moreAction = ^(ZBMyProductsCell *cell) {
CGRect rect = [tableView rectForRowAtIndexPath:indexPath];
// write your own code to show/hide the menu
};
return cell;
}
Create a variable in each cell model called cellIndexpath and in cellForRow init it
cell.cellIndexpath = indexpath
have a look of UIPopoverPresnetationController and see if it can do the job. It should be available on iPhone. Putting menu view on the controller’viewwill cause issue when you scroll the table view.
use
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setTargetRect:self.detaiLabel.frame inView:self];
I have a UICollectionView with a lot of cells inside (about 5k+). I want to make pinch to zoom in/out. I've tried to invalidateLayout on every pinch. It's really slow. SO I want to use CGAffineTransformMakeScale but I don't know how to scroll after this.
My code is:
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer *)gesture {
static CGFloat scaleStart;
if (gesture.state == UIGestureRecognizerStateBegan) {
scaleStart = self.venueLayoutZoom;
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
CGAffineTransform transform = CGAffineTransformMakeScale(self.venueLayoutZoom, self.venueLayoutZoom);
self.activeCollectionNode.view.transform = transform;
self.activeCollectionNode.view.contentSize = CGSizeMake(318 * self.venueLayoutZoom, 500 * self.venueLayoutZoom);
}
}
But when is zoomed in I can't scroll left and right. Help me out.
use
#interface ViewController () <UICollectionViewDataSource,
UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
#property (nonatomic,assign) CGFloat scale;
#property (nonatomic,weak) IBOutlet UICollectionView *collectionView;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.scale = 1.0;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cell"];
UIPinchGestureRecognizer *gesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(didReceivePinchGesture:)];
[self.collectionView addGestureRecognizer:gesture];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:
(NSIndexPath *)indexPath
{
return CGSizeMake(50*self.scale, 50*self.scale);
}
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer*)gesture
{
static CGFloat scaleStart;
if (gesture.state == UIGestureRecognizerStateBegan)
{
scaleStart = self.scale;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
self.scale = scaleStart * gesture.scale;
[self.collectionView.collectionViewLayout invalidateLayout];
}
}
I have a simple collection view with 5 elements.
The default order of the cells looks like this:
I wanna know if it's possible to change the padding\order of the cells to get result like this:
Option 1: (Clean option)
You should have 2 sections.
Section 1 with 3 cells
Section 2 with 2 cells
You can then adjust the inset with collectionView(_:layout:insetForSectionAt:) for the section you want to adjust. (in this case, section 2)
If you do not implement this method, the flow layout uses the value in
its sectionInset property to set the margins instead. Your
implementation of this method can return a fixed set of margin sizes
or return different margin sizes for each section.
Section insets are
margins applied only to the items in the section. They represent the
distance between the header view and the first line of items and
between the last line of items and the footer view. They also indicate
they spacing on either side of a single line of items. They do not
affect the size of the headers or footers themselves.
Option 2: (Spaghetti option, but good to know)
Create a custom subclass for section 2 items, where you can customize the inset of the actual content for the UICollectionViewCell contentView subviews.
Then in section 2, return your customized cell.
To do this, you may need to write a custom layout. Check out the Collection View Programming Guide for more information.
The default (flow) layout will always lay the cells out in this pattern:
You can achieve this type of format in UICollectionView by changing the number of sections from 1 to 2.
And then you can define your custom UICollectionViewFlowLayout accordingly for different sections.
SOLUTION: I've created a subclass of UICollectionView named "CenteringCollectionView" and with a few calculations of the sections I made it!
The .h file:
#import <UIKit/UIKit.h>
#class CenteringCollectionView;
#protocol CenteringCollectionViewDelegate <NSObject>
-(void)collectionView:(CenteringCollectionView *)collectionView didDequeueReusableCell:(UICollectionViewCell *)cell indexPath:(NSIndexPath *)indexPath;
#end
#interface CenteringCollectionView : UICollectionView
#property (nonatomic, strong) NSMutableArray *dataSourceArr;
#property (nonatomic, weak) id<CenteringCollectionViewDelegate> delegateCenteringCollection;
#end
The .m file:
#import "CenteringCollectionView.h"
#interface CenteringCollectionView () <UICollectionViewDelegate, UICollectionViewDataSource>
#property (nonatomic, assign) IBInspectable long elementsInRow;
#property (nonatomic, assign) long elementsInRowInitialValue;
#property (nonatomic) IBInspectable NSString *cellIdentifier;
#property (nonatomic) IBInspectable CGFloat cellRelativeSize; // 0..1
#property (nonatomic, assign) long numOfSections;
#property (nonatomic, assign) IBInspectable BOOL autoResize; // *** If we want auto resize - we need to set the height constraint of the collection view in size of 1 line only even if we have more than 1 line (section).
#property (nonatomic, assign)IBInspectable CGFloat heightMiddleSpacing;
#property (nonatomic, assign) long cellSize;
//#property (nonatomic, assign) CGFloat verticalTopInset;
#property (nonatomic, assign) CGFloat initialHeightConstraint;
#property (nonatomic, weak) NSLayoutConstraint *selfHeightConstraint;
#property (nonatomic, assign) CGFloat cellSpacing;
#property (nonatomic, assign) BOOL shouldReloadUIElements;
// UI IBInspectable
#property (nonatomic, weak) IBInspectable UIColor *runtimeColor;
#end
static long const maxElementsInRowDefault = 3;
#implementation CenteringCollectionView
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
self.elementsInRow = maxElementsInRowDefault; // will get the default value if not stored value in storyboard
self.elementsInRowInitialValue = self.elementsInRow;
self.cellRelativeSize = 0.5;
self.initialHeightConstraint = -1;
}
return self;
}
-(void)setDataSourceCount:(long)dataSourceCount
{
if (dataSourceCount == _dataSourceCount)
{
return;
}
_dataSourceCount = dataSourceCount;
self.shouldReloadUIElements = YES;
self.elementsInRow = MIN(self.elementsInRowInitialValue, self.dataSourceCount);
self.numOfSections = ceil((CGFloat)self.dataSourceCount / (CGFloat)self.elementsInRow);
CGFloat selfHeight = [self handleAutoResizeAndReturnTheNewHeightIfNeeded];
CGFloat selfWidth = CGRectGetWidth(self.frame);
CGFloat cellWidth = (selfWidth / self.elementsInRow) * self.cellRelativeSize;
CGFloat cellHeight = (selfHeight / self.numOfSections) * self.cellRelativeSize;
self.cellSize = MIN(cellWidth, cellHeight);
dispatch_async(dispatch_get_main_queue(), ^{
[self setCollectionView];
[self reloadData];
});
}
-(void)awakeFromNib
{
[super awakeFromNib];
self.elementsInRowInitialValue = self.elementsInRow;
[self handleUIelementsIBInspectable];
}
-(void)handleUIelementsIBInspectable
{
if (self.runtimeColor)
{
[self setBackgroundColor:self.runtimeColor];
}
}
-(CGFloat)handleAutoResizeAndReturnTheNewHeightIfNeeded
{
if (self.autoResize)
{
for (NSLayoutConstraint *constraint in [self constraints])
{
if (constraint.firstAttribute == NSLayoutAttributeHeight)
{
if (self.initialHeightConstraint == -1) // not set yet
{
self.initialHeightConstraint = constraint.constant;
}
if (!self.selfHeightConstraint)
{
self.selfHeightConstraint = constraint;
}
CGFloat newHeight = self.initialHeightConstraint * self.numOfSections;
constraint.constant = newHeight;
if (self.bounds.size.height != newHeight)
{
CGRect frame = self.bounds;
frame.size.height = newHeight;
[self setBounds:frame];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.superview layoutIfNeeded];
[self layoutIfNeeded];
});
return newHeight;
}
}
}
return CGRectGetHeight(self.frame);
}
-(long)numOfSpacesInRow
{
return self.elementsInRow + 1;
}
-(long)numOfSpacesBetweenLines
{
return self.numOfSections + 1;
}
-(void)setCellRelativeSize:(CGFloat)cellRelativeSize
{
_cellRelativeSize = MAX(0, MIN(cellRelativeSize, 1));
}
-(void)setCollectionView
{
[self reloadData];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
CGFloat horizontalCellSpacing = ((CGRectGetWidth(self.frame) - (self.cellSize * self.elementsInRow)) / self.numOfSpacesInRow);
CGFloat verticalCellSpacing = (CGRectGetHeight(self.frame) - (self.numOfSections * self.cellSize)) / self.numOfSpacesBetweenLines;
self.cellSpacing = MAX(MIN(horizontalCellSpacing, verticalCellSpacing), 0);
[layout setMinimumInteritemSpacing:self.cellSpacing];
[layout setMinimumLineSpacing:self.cellSpacing];
[layout setScrollDirection:UICollectionViewScrollDirectionVertical];
[self setCollectionViewLayout:layout];
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.scrollEnabled = NO;
if (!self.delegate)
{
self.delegate = self;
self.dataSource = self;
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(self.cellSize, self.cellSize);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
BOOL isLastSection = (section == self.numOfSections - 1);
if (isLastSection == NO)
{
return self.elementsInRow;
}
else
{
long numOfLeftItemsInLastRow = self.dataSourceCount % self.elementsInRow;
if (numOfLeftItemsInLastRow == 0)
{
return self.elementsInRow;
}
else
{
return numOfLeftItemsInLastRow;
}
}
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath];
if ([self.delegateCenteringCollection respondsToSelector:#selector(collectionView:didDequeueReusableCell:indexPath:)])
{
[self.delegateCenteringCollection collectionView:self didDequeueReusableCell:cell indexPath:[self indexPathWithoutSectionsFrom:indexPath]];
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegateCenteringCollection respondsToSelector:#selector(collectionView:didSelectItemAtIndexPath:cell:)])
{
UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
[self.delegateCenteringCollection collectionView:self didSelectItemAtIndexPath:[self indexPathWithoutSectionsFrom:indexPath] cell:selectedCell];
}
}
-(NSIndexPath *)indexPathWithoutSectionsFrom:(NSIndexPath *)indexPath
{
long sectionNum = indexPath.section;
long rowNum = sectionNum * self.elementsInRow + indexPath.row;
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:rowNum inSection:0];
return newIndexPath;
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return self.numOfSections;
}
-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.shouldReloadUIElements == NO)
{
return;
}
if (self.autoResize && self.selfHeightConstraint)
{
BOOL isTheFirstCellInTheLastSection = (indexPath.section == self.numOfSections - 1) && indexPath.row == 0;
if (isTheFirstCellInTheLastSection)
{
CGFloat newHeight = CGRectGetMaxY(cell.frame) + self.cellSpacing;
self.selfHeightConstraint.constant = newHeight;
if (self.bounds.size.height != newHeight)
{
CGRect frame = self.bounds;
frame.size.height = newHeight;
[self setBounds:frame];
}
[self.superview layoutIfNeeded];
[self layoutIfNeeded];
}
}
});
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
if (self.shouldReloadUIElements == NO)
{
return collectionView.contentInset;
}
NSInteger cellsCount = [collectionView numberOfItemsInSection:section];
CGFloat horizontalInset = (collectionView.bounds.size.width - (cellsCount * self.cellSize) - ((cellsCount - 1) * self.cellSpacing)) * 0.5;
horizontalInset = MAX(horizontalInset, 0.0);
BOOL isLastSection = (section == self.numOfSections - 1);
CGFloat verticalTopInset = self.cellSpacing;
CGFloat verticalBottomInset = verticalTopInset;
if (section == 0 && isLastSection == NO)
{
if (self.heightMiddleSpacing)
{
verticalBottomInset += self.heightMiddleSpacing;
}
verticalBottomInset /= 2;
}
if (section > 0)
{
if (self.heightMiddleSpacing)
{
verticalTopInset += self.heightMiddleSpacing;
}
verticalTopInset /= 2;
if (isLastSection == NO)
{
if (self.heightMiddleSpacing)
{
verticalBottomInset += self.heightMiddleSpacing;
}
verticalBottomInset /= 2;
}
}
return UIEdgeInsetsMake(verticalTopInset, horizontalInset, verticalBottomInset, horizontalInset);
}
#end
And to make it work, all we need to do in the parent view is:
self.collectionView.delegateCenteringCollection = self;
self.collectionView.dataSourceCount = 5; // Or whatever number we want!
In the storyboard: we need to create collectionView of this class and set the "elements in row" value, also set the "Cell Identifier" and the "Cell relative size" between 0 to 1 (the "Cell relative size" value: will calculate the cell size & paddings according to the collectionView width & height).
And at last - set "autoResize" to "true" if you want that the collection view will resize its own height constraint(if exist) automatically according to the number of rows. If we set "autoResize" to true, the height constraint that we set to the collectionView will determine the height of a single row. If our collectionView should grow for example to 3 rows, it will multiple our collectionview height constraint by 3.
And it works like a charm!
I have a UICollectionView that when a cell is pressed, there will be a new set of data and will animate the flow layout.
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
for(int i=0;i<200;i++)
{
[tempArray addObject:[NSNumber numberWithInt:i]];
}
items = [NSArray arrayWithArray:tempArray];
CollapseLayout *currentLayout = (CollapseLayout *)_collectionView.collectionViewLayout;
currentLayout.collapse = YES;
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.collectionView.numberOfSections)]];
} completion:nil];
}
It is using a Custom UICollectionViewFlowLayout that performs an animation when reloading the data.
My CustomFlowLayout Class is here
#import "CollapseLayout.h"
#implementation CollapseLayout
- (void)prepareLayout {
self.itemSize = CGSizeMake(200, 100);
self.minimumInteritemSpacing = 30;
self.scrollDirection = UICollectionViewScrollDirectionVertical;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* allAttributesInRect = [super layoutAttributesForElementsInRect:rect];
if(_collapse)
{
for(UICollectionViewLayoutAttributes *attribute in allAttributesInRect)
{
attribute.frame = CGRectMake(attribute.frame.origin.x - self.collectionView.frame.size.width, attribute.frame.origin.y, attribute.frame.size.width, attribute.frame.size.height);
}
}
return allAttributesInRect;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* allAttributesInRect = [super layoutAttributesForItemAtIndexPath:indexPath];
if(_collapse)
{
allAttributesInRect.frame = CGRectMake(allAttributesInRect.frame.origin.x - self.collectionView.frame.size.width, allAttributesInRect.frame.origin.y, allAttributesInRect.frame.size.width, allAttributesInRect.frame.size.height);
}
return allAttributesInRect;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath*)itemIndexPath
{
UICollectionViewLayoutAttributes* layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:itemIndexPath];
return layoutAttributes;
}
- (void)invalidateLayout
{
}
The BOOL
_collapse
is set is called in my collection view to trigger the animation. I am hoping to have the old cells, move from left to right, outside the view, and the new cells animate in from the right side of the screen. Right now, the cells are just moving off screen.
My issue is that the code in layoutAttributesForItemAtIndexPath and layoutAttributesForElementsInRect are not animating correctly. Would adding the animation code in these methods be the best place to perform the animations, or im totally off?
Thanks
You shouldn't do anything to the result of
[super layoutAttributesForItemAtIndexPath:indexPath];
nor
[super layoutAttributesForElementsInRect:rect];
If you want your cells to animate from where they are to off the screen, then you need to modify only their "final" position by overriding finalAttributesForDisappearingElementAtIndexPath:, but not their "regular position".
If you want them to come back from the side of the screen then you need to modify their position through initialLayoutAttributesForAppearingItemAtIndexPath:
I have a UIScrollView within a UITableViewCell for being able to scroll through images within a cell. However, as a cell is reused the scroll position/content is reloaded and therefore the cell doesn't remember what scroll position (page) it was on when it comes into view again.
What's the best way to have a scroll view within a UITableView cell and have it maintain position as it comes back into view. The AirBnB app (https://itunes.apple.com/us/app/airbnb/id401626263?mt=8) seems to have accomplished this for example.
You need to keep track of your scroll views' content offset in a property. In the example below, I do this with a mutable dictionary. In cellForRowAtIndexPath:, I give the scroll view a tag and set the controller as the delegate. In the scroll view delegate method, scrollViewDidEndDecelerating:, the scroll view's content offset is set as the object for the key corresponding to the scroll view's tag. In cellForRowAtIndexPath:, I check for whether the indexPath.row (converted to an NSNumber) is one of the keys of the dictionary, and if so, restore the correct offset for that scroll view. The reason I add 1 to the tags is because the table view has its own scroll view which has a tag of 0, so I don't want to use 0 as a tag for one of the cell's scroll views.
So in cellForRowAtIndexPath, you need something like this:
cell.scrollView.tag = indexPath.row + 1;
cell.scrollView.delegate = self;
if ([self.paths.allKeys containsObject:#(indexPath.row + 1)]) {
cell.scrollView.contentOffset = CGPointMake([self.paths[#(indexPath.row + 1)] floatValue],0);
}else{
cell.scrollView.contentOffset = CGPointZero;
}
return cell;
And in the delegate method:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.tag != 0)
[self.paths setObject:#(scrollView.contentOffset.x) forKey:#(scrollView.tag)];
}
paths is a property (NSMutableDictionary) that I create in viewDidLoad.
Use my tableView customs cells
homeScrollCell.h
#import <UIKit/UIKit.h>
#import "MyManager.h"
#interface homeScrollCell : UITableViewCell<UIScrollViewDelegate>
{
MyManager *manager;
UIScrollView *__scrollView;
}
-(void)setPage:(int)page;
#property int currentPage;
#end
homeScrollCell.m
#import "homeScrollCell.h"
#implementation homeScrollCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
manager=[MyManager sharedManager];
__scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width,self.bounds.size.height)];
[__scrollView setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
NSInteger viewcount= 3;
for(int i = 0; i< viewcount; i++)
{
CGFloat x = i * self.bounds.size.width;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x, 0,self.bounds.size.width,self.bounds.size.height)];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
UILabel *label=[[UILabel alloc] initWithFrame:CGRectMake(50, 20, 200, 50)];
[label setBackgroundColor:[UIColor redColor]];
label.text=[NSString stringWithFormat:#"Hi, I am label %i",i];
[view addSubview:label];
view.backgroundColor = [UIColor greenColor];
[__scrollView addSubview:view];
}
[__scrollView setBackgroundColor:[UIColor redColor]];
__scrollView.contentSize = CGSizeMake(self.bounds.size.width *viewcount, 100);
__scrollView.pagingEnabled = YES;
__scrollView.bounces = NO;
__scrollView.delegate=self;
[self addSubview:__scrollView];
// Initialization code
}
return self;
}
-(void)setPage:(int)page
{
CGFloat pageWidth = __scrollView.frame.size.width;
float offset_X=pageWidth*page;
[__scrollView setContentOffset:CGPointMake(offset_X, __scrollView.contentOffset.y)];
_currentPage=page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
static NSInteger previousPage = 0;
CGFloat pageWidth = scrollView.frame.size.width;
float fractionalPage = scrollView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
if (previousPage != page) {
_currentPage=page;
[manager setpage:_currentPage ForKey:[NSString stringWithFormat:#"%i",self.tag]];
previousPage = page;
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
And use a Singleton file for keeping record or pages inside cell.
MyManager.h
#import <Foundation/Foundation.h>
#interface MyManager : NSObject
+ (id)sharedManager;
-(void)setpage:(int)page ForKey:(NSString*)key;
-(int)getpageForKey:(NSString*)key;
#end
MyManager.m
#import "MyManager.h"
static NSMutableDictionary *dictionary;
#implementation MyManager
#pragma mark Singleton Methods
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
-(void)setpage:(int)page ForKey:(NSString*)key
{
[dictionary setValue:[NSString stringWithFormat:#"%i",page] forKey:key];
}
-(int)getpageForKey:(NSString*)key
{
return [[dictionary valueForKey:key] intValue];
}
- (id)init {
if (self = [super init]) {
dictionary=[[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
#end
And use this custom cell in cellForRow as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Home Scroll Cell";
homeScrollCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell = [[homeScrollCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.tag=indexPath.row;
[cell setPage:[manager getpageForKey:[NSString stringWithFormat:#"%i",indexPath.row]]];
return cell;
}
Set your page inside CustomCell and scroll and after re scrolling get back to previous position will show you page set by you before scrolling.
It will persist your page moved as it is.
Download And Run
rdelmar's answer is missing something. scrollViewDidEndDecelerating will only be called if a users scroll triggers the deceleration behaviour.
A drag only scroll won't call this method so you will get inconsistent behaviour for the user who won't distinguish between the two types of scroll. You need to catch it with scrollViewDidEndDragging:willDecelerate
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
self.tableCellContentOffsets[#(scrollView.tag)] = #(scrollView.contentOffset.x);
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.tableCellContentOffsets[#(scrollView.tag)] = #(scrollView.contentOffset.x);
}