UICollectionViewFlowLayout subclass not working - ios

Here is my UICollectionViewFlowLayout subclass:
#implementation MyCollectionViewFlowLayout
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (void)setup
{
self.itemSize = CGSizeMake(320, 320);
self.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
self.minimumInteritemSpacing = 0;
self.minimumLineSpacing = 0;
self.scrollDirection = UICollectionViewScrollDirectionVertical;
}
- (void)prepareLayout {
[super prepareLayout];
}
- (CGSize)collectionViewContentSize {
return self.itemSize;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* allAttributesInRect = [super layoutAttributesForElementsInRect:rect];
return allAttributesInRect;
}
- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
return attributes;
}
#end
When I do the following, the collection view will not scroll:
MyCollectionViewFlowLayout* flowLayout = [[MyCollectionViewFlowLayout alloc] init];
However if I do this:
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.itemSize = CGSizeMake(320, 320);
flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0);
flowLayout.minimumInteritemSpacing = 0;
flowLayout.minimumLineSpacing = 0;
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
Then the collection view scrolls fine. What am I doing wrong in the UICollectionViewFlowLayout subclass?

The problem is collectionViewContentSize
- (CGSize)collectionViewContentSize {
return self.itemSize;
}
Returning itemSize here means that the entire contentSize for your collection view is only the size of one item. Try removing that code, or changing it to
- (CGSize)collectionViewContentSize {
[super collectionViewContentSize];
}

Related

iOS: TableView inside TableView cell not scrolling

I have a tableview inside a tableview cell. I am unable scroll that table. I have also attached the screenshot of the table Here is my code:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString
*)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.frame = CGRectMake(0, 0, 300, 50);
self.subMenuTableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStylePlain];
self.subMenuTableView.tag = 100;
self.subMenuTableView.delegate = self;
self.subMenuTableView.dataSource = self;
self.subMenuTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.subMenuTableView.bounces = NO;
self.subMenuTableView.scrollEnabled = YES;
self.subMenuTableView.alwaysBounceVertical = NO;
[self addSubview:self.subMenuTableView]; // add it cell
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
//UITableView *subMenuTableView =(UITableView *) [self viewWithTag:100];
self.subMenuTableView.frame = CGRectMake(0.2, 0.3, self.bounds.size.width-5, self.bounds.size.height-5);//set the frames for tableview
}

Adding NSTimer in image slide and page contol to move images automatically

Here is the image slider works fine when dragging manually the image changes with page control indicator change but I want to add the timer to the following code and move the images and indicator automatically. Please help me to apply NS timer in this code and also want to move from the last image to 1st image and so on.
#implementation DashboardViewController {
NSArray * animationArray;
}
#synthesize scroller = scroller;
#synthesize pageControl = pageControl;
-(void) viewDidLoad {
[super viewDidLoad];
scroller.pagingEnabled = YES;
scroller.showsHorizontalScrollIndicator = NO;
scroller.delegate = self;
animationArray = [NSArray arrayWithObjects: [UIImage imageNamed: # "image1.jpg"],
[UIImage imageNamed: # "image2.jpg"],
[UIImage imageNamed: # "image3.jpg"],
[UIImage imageNamed: # "image4.png"],
[UIImage imageNamed: # "image5.jpg"],
nil
];
CGRect scrollFrame = CGRectMake(0, 0, self.view.frame.size.width, self.scroller.frame.size.height);
scroller.frame = scrollFrame;
self.scroller.contentSize = CGSizeMake(self.scroller.frame.size.width * animationArray.count, self.scroller.frame.size.height);
for (int i = 0; i < animationArray.count; i++) {
CGRect frame;
frame.origin.x = self.scroller.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scroller.frame.size;
UIImageView * imgView = [
[UIImageView alloc] initWithFrame: CGRectMake(self.scroller.frame.size.width * i, 0, self.scroller.frame.size.width, self.scroller.frame.size.height)
];
imgView.image = [animationArray objectAtIndex: i];
imgView.frame = frame;
[self.scroller addSubview: imgView];
}
self.pageControl.currentPage = 0;
}
-(void) scrollViewDidScroll: (UIScrollView * ) sender {
if (!pageControlBeingUsed) {
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = self.scroller.frame.size.width;
int page = floor((self.scroller.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
}
}
-(void) scrollViewWillBeginDragging: (UIScrollView * ) scrollView {
pageControlBeingUsed = NO;
}
-(void) scrollViewDidEndDecelerating: (UIScrollView * ) scrollView {
[self setIndiactorForCurrentPage];
}
-(void) setIndiactorForCurrentPage {
uint page = scroller.contentOffset.x / scroller.frame.size.width;
[pageControl setCurrentPage: page];
}
- (IBAction) changePage {
// Update the scroll view to the appropriate page
CGRect frame;
pageControl.currentPage = animationArray.count;
frame.origin.x = self.scroller.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.scroller.frame.size;
[self.scroller scrollRectToVisible: frame animated: YES];
pageControlBeingUsed = YES;
}
- (void) didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
self.scroller = nil;
self.pageControl = nil;
}
Please try this code for NSTimer (i use collectionView for easy use)
var timer : Timer?
var currentFeaturedCouponIndex = 0
//MARK: - NSTimer -
func setupTimer(){
timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(self.scrollFeatured), userInfo: nil, repeats: true);
}
func scrollFeatured(){
if self.arrFeaturedCoupon.count > 0 {
//Becauese we scroll coupon if total is > 1
if self.arrFeaturedCoupon.count > 1 {
if self.currentFeaturedCouponIndex < self.arrFeaturedCoupon.count - 1 {
self.currentFeaturedCouponIndex = self.currentFeaturedCouponIndex + 1
}
else {
self.currentFeaturedCouponIndex = 0
}
self.pageControl.currentPage = currentFeaturedCouponIndex
//When currentFeaturedCouponIndex == 0 we make animated: false
if self.currentFeaturedCouponIndex == 0 {
self.featuredCouponColView.scrollToItem(at: self.featuredCouponCurrentIndexPath, at: UICollectionViewScrollPosition.left, animated: false)
}
else {
self.featuredCouponColView.scrollToItem(at: self.featuredCouponCurrentIndexPath, at: UICollectionViewScrollPosition.left, animated: true)
}
}
}
}
To invalidate timer when and where you want
func invalidateTimer(){
if timer != nil {
self.timer!.invalidate()
self.timer = nil
}
}

how to create property method in swift

I make one UITableView property in my class and make getter method for it like this:
#interface SideViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
#property (nonatomic,strong) UITableView *table;
#end
#implementation SideViewController
#synthesize selectedIndex;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
- (UITableView*)table {
if (!_table) {
CGRect tableFrame = CGRectMake(0,150,230, self.view.frame.size.height-150);
_table = [[UITableView alloc]initWithFrame:tableFrame style:UITableViewStylePlain];
_table.scrollEnabled = NO;
_table.delegate = self;
_table.dataSource = self;
_table.showsVerticalScrollIndicator = YES;
_table.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0);
[_table setBackgroundColor:[UIColor clearColor]];
_table.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return _table;
}
when I call self.table get my table now I want write this code in swift but I'm so confused!!!
please guide me about that.
this is my swift code :
import UIKit
class SideViewController: UIViewController,UITableViewDelegate {
let table:UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.redColor()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You can use lazy in swift:
class SideViewController: UIViewController, UITableViewDelegate {
lazy var table: UITableView = {
let tableFrame = CGRectMake(0, 150, 230, self.view.frame.size.height - 150)
let _table = UITableView(frame: tableFrame, style: UITableViewStyle.Plain)
_table.scrollEnabled = false
_table.delegate = self
_table.dataSource = self
_table.showsVerticalScrollIndicator = true
_table.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0)
_table.backgroundColor = UIColor.clearColor()
_table.separatorStyle = UITableViewCellSeparatorStyle.None
return _table
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.redColor()
}
}

How do you vertically align the UICollectionViewCells in a UICollectionView?

I have cells of varying height on a horizontally scrolling UICollectionView that appears to evenly distribute the cells vertically leaving empty space in between the cells and I would like to top align them and have the variable empty space at the bottom of each column.
I extended my UICollectionViewFlowLayout provided to my UICollectionView. The following overriding worked for me.
#import "TopAlignedCollectionViewFlowLayout.h"
const NSInteger kVerticalSpacing = 10;
#implementation TopAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* currentItemAttributes =
[super layoutAttributesForItemAtIndexPath:indexPath];
UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];
if (indexPath.item == 0) {
CGRect frame = currentItemAttributes.frame;
frame.origin.y = sectionInset.top;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
CGFloat previousFrameRightPoint = previousFrame.origin.y + previousFrame.size.height + kVerticalSpacing;
CGRect currentFrame = currentItemAttributes.frame;
CGRect strecthedCurrentFrame = CGRectMake(currentFrame.origin.x,
0,
currentFrame.size.width,
self.collectionView.frame.size.height
);
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) {
CGRect frame = currentItemAttributes.frame;
frame.origin.y = frame.origin.y = sectionInset.top;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
CGRect frame = currentItemAttributes.frame;
frame.origin.y = previousFrameRightPoint;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
#end
I ported this to Xamarin/MonoTouch for my project and thought it could be useful
public class TopAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout
{
const int VerticalSpacing = 10;
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(RectangleF rect)
{
var attributesToReturn = base.LayoutAttributesForElementsInRect(rect);
foreach (UICollectionViewLayoutAttributes attributes in attributesToReturn)
{
if (attributes.RepresentedElementKind == null)
{
var indexPath = attributes.IndexPath;
attributes.Frame = this.LayoutAttributesForItem(indexPath).Frame;
}
}
return attributesToReturn;
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem(NSIndexPath indexPath)
{
var currentItemAttributes = base.LayoutAttributesForItem(indexPath);
UIEdgeInsets sectionInset = ((UICollectionViewFlowLayout) this.CollectionView.CollectionViewLayout).SectionInset;
if (indexPath.Item == 0)
{
var frame1 = currentItemAttributes.Frame;
frame1.Y = sectionInset.Top;
currentItemAttributes.Frame = frame1;
return currentItemAttributes;
}
var previousIndexPath = NSIndexPath.FromItemSection(indexPath.Item - 1, indexPath.Section);
var previousFrame = this.LayoutAttributesForItem(previousIndexPath).Frame;
var previousFrameRightPoint = previousFrame.Y + previousFrame.Size.Height + VerticalSpacing;
var currentFrame = currentItemAttributes.Frame;
var strecthedCurrentFrame = new RectangleF(currentFrame.X, 0, currentFrame.Size.Width, this.CollectionView.Frame.Size.Height);
if (!previousFrame.IntersectsWith(strecthedCurrentFrame))
{
var frame = currentItemAttributes.Frame;
frame.Y = frame.Y = sectionInset.Top;
currentItemAttributes.Frame = frame;
return currentItemAttributes;
}
RectangleF frame2 = currentItemAttributes.Frame;
frame2.Y = previousFrameRightPoint;
currentItemAttributes.Frame = frame2;
return currentItemAttributes;
}
}
I imagine you are using a UICollectionViewFlowLayout to layout your cells. I don't think you can do this with a flow layout. You probably have to write a custom subclass of UICollectionViewLayout to align the top edges of each cell.
This is not a very pretty solution, but what about forcing all of your cells to be the same size and setting up the constrains in your cell to align the content to the top? This could simulate the look you are going for without having to subclass UICollectionViewLayout.

UICollectionView with a sticky header

I found a blog on how to make sticky headers and it works great. Only thing is I don't think it takes into account the sectionInserts.
This is how its intended to look:
I have my inserts:
collectionViewFlowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16);
With the sticky header, it is moved down by 16 pixles:
I tried tinking with the original code and I think the issue is with the last part:
layoutAttributes.frame = (CGRect){
.origin = CGPointMake(origin.x, origin.y),
.size = layoutAttributes.frame.size
If i change it to origin.y - 16, the header will start in the right location but when pushed up, 16 pixels of the head go off screen:
I'm not sure how to get it to take into account sectionInsects. Can anybody help?
Here is the code in full from the blog:
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
UICollectionView * const cv = self.collectionView;
CGPoint const contentOffset = cv.contentOffset;
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section];
}
}
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[missingSections removeIndex:layoutAttributes.indexPath.section];
}
}
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[answer addObject:layoutAttributes];
}];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
NSInteger section = layoutAttributes.indexPath.section;
NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];
NSIndexPath *firstCellIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
UICollectionViewLayoutAttributes *firstCellAttrs = [self layoutAttributesForItemAtIndexPath:firstCellIndexPath];
UICollectionViewLayoutAttributes *lastCellAttrs = [self layoutAttributesForItemAtIndexPath:lastCellIndexPath];
CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);
CGPoint origin = layoutAttributes.frame.origin;
origin.y = MIN(
MAX(
contentOffset.y,
(CGRectGetMinY(firstCellAttrs.frame) - headerHeight)
),
(CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
}
}
return answer;
}
Simplest solution for iOS 9 + as it doesn't need of writing subclass of UICollectionViewFlowLayout.
In viewDidLoad of viewController with collectionView use following code:
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true
Fix by Todd Laney to handle Horizontal and Vertical scrolling and to take into account the sectionInsets:
https://gist.github.com/evadne/4544569
#implementation StickyHeaderFlowLayout
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (NSUInteger idx=0; idx<[answer count]; idx++) {
UICollectionViewLayoutAttributes *layoutAttributes = answer[idx];
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section]; // remember that we need to layout header for this section
}
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[answer removeObjectAtIndex:idx]; // remove layout of header done by our super, we will do it right later
idx--;
}
}
// layout all headers needed for the rect using self code
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
if (layoutAttributes != nil) {
[answer addObject:layoutAttributes];
}
}];
return answer;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
UICollectionView * const cv = self.collectionView;
CGPoint const contentOffset = cv.contentOffset;
CGPoint nextHeaderOrigin = CGPointMake(INFINITY, INFINITY);
if (indexPath.section+1 < [cv numberOfSections]) {
UICollectionViewLayoutAttributes *nextHeaderAttributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section+1]];
nextHeaderOrigin = nextHeaderAttributes.frame.origin;
}
CGRect frame = attributes.frame;
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
frame.origin.y = MIN(MAX(contentOffset.y, frame.origin.y), nextHeaderOrigin.y - CGRectGetHeight(frame));
}
else { // UICollectionViewScrollDirectionHorizontal
frame.origin.x = MIN(MAX(contentOffset.x, frame.origin.x), nextHeaderOrigin.x - CGRectGetWidth(frame));
}
attributes.zIndex = 1024;
attributes.frame = frame;
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
return attributes;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
#end
This is really a good solution and works perfectly. However, since we have to return YES from shouldINvalidateLayoutForBoundsChange, this basically calls prepareLayout every time the view scrolls. Now, IF your prepareLayout has the responsibility of creating the layout attributes, which is quite common, this will immensely affect the scroll performance.
One solution, which worked for me, is to not create the layout attributes in prepareLayout but instead do it in a separate method which you call explicitly before calling invalidateLayout. UICollectionView calls prepareLayout as and when it feels it needs to know about the layout and hence this solution will take care of those cases also.
You just need to create a new UICollectionViewFlowLayout with this code:
class StickyHeaderLayout: UICollectionViewFlowLayout {
override init() {
super.init()
self.sectionFootersPinToVisibleBounds = true
self.sectionHeadersPinToVisibleBounds = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.sectionFootersPinToVisibleBounds = true
self.sectionHeadersPinToVisibleBounds = true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
for attribute in attributes {
adjustAttributesIfNeeded(attribute)
}
return attributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) else { return nil }
adjustAttributesIfNeeded(attributes)
return attributes
}
func adjustAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
switch attributes.representedElementKind {
case UICollectionElementKindSectionHeader?:
adjustHeaderAttributesIfNeeded(attributes)
case UICollectionElementKindSectionFooter?:
adjustFooterAttributesIfNeeded(attributes)
default:
break
}
}
private func adjustHeaderAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
guard let collectionView = collectionView else { return }
guard attributes.indexPath.section == 0 else { return }
if collectionView.contentOffset.y < 0 {
attributes.frame.origin.y = collectionView.contentOffset.y
}
}
private func adjustFooterAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
guard let collectionView = collectionView else { return }
guard attributes.indexPath.section == collectionView.numberOfSections - 1 else { return }
if collectionView.contentOffset.y + collectionView.bounds.size.height > collectionView.contentSize.height {
attributes.frame.origin.y = collectionView.contentOffset.y + collectionView.bounds.size.height - attributes.frame.size.height
}
}
}
This code works for me
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
UICollectionView * const cv = self.collectionView;
//CLS_LOG(#"Number of sections = %d", [cv numberOfSections]);
CGPoint const contentOffset = cv.contentOffset;
//CLS_LOG(#"Adding missing sections");
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section];
}
}
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[missingSections removeIndex:layoutAttributes.indexPath.section];
}
}
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[answer addObject:layoutAttributes];
}];
NSInteger numberOfSections = [cv numberOfSections];
//CLS_LOG(#"For loop");
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
NSInteger section = layoutAttributes.indexPath.section;
//CLS_LOG(#"Customizing layout attribute for header in section %d with number of items = %d", section, [cv numberOfItemsInSection:section]);
if (section < numberOfSections) {
NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];
NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
BOOL cellsExist;
UICollectionViewLayoutAttributes *firstObjectAttrs;
UICollectionViewLayoutAttributes *lastObjectAttrs;
if (numberOfItemsInSection > 0) { // use cell data if items exist
cellsExist = YES;
firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
} else { // else use the header and footer
cellsExist = NO;
firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
atIndexPath:lastObjectIndexPath];
}
CGFloat topHeaderHeight = (cellsExist) ? CGRectGetHeight(layoutAttributes.frame) : 0;
CGFloat bottomHeaderHeight = CGRectGetHeight(layoutAttributes.frame);
CGRect frameWithEdgeInsets = UIEdgeInsetsInsetRect(layoutAttributes.frame,
cv.contentInset);
CGPoint origin = frameWithEdgeInsets.origin;
origin.y = MIN(
MAX(
contentOffset.y + cv.contentInset.top,
(CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight)
),
(CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight)
);
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
}
}
}
return answer;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
Try this guys...
None of the above worked for me. I was looking for a clean layout that, taking care of my insets, give me a Photo.app style scrolling collection.
I adapted the solution prosed here to take care of edgeInsets settings. For clarity I attach the complete solution here. However, you can get the complete solution from the following gist: #3e1955a4492a897e677f.
#implementation SpringboardLayout
- (id)init
{
if (self = [super init])
{
self.headerReferenceSize = CGSizeMake(0, 50);
self.footerReferenceSize = CGSizeMake(0, 0);
self.sectionInset = UIEdgeInsetsMake(10, 10, 80, 10);
self.scrollDirection = UICollectionViewScrollDirectionVertical;
self.minimumInteritemSpacing = 10;
self.minimumLineSpacing = 10;
if(IS_IPHONE_6 || IS_IPHONE_6PLUS) {
self.itemSize = CGSizeMake(100, 128);
} else {
self.itemSize = CGSizeMake(80, 108);
}
}
return self;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
UICollectionView * const cv = self.collectionView;
CGPoint const contentOffset = cv.contentOffset;
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section];
} else if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[missingSections removeIndex:layoutAttributes.indexPath.section];
}
}
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[answer addObject:layoutAttributes];
}];
for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
NSInteger section = layoutAttributes.indexPath.section;
NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];
NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];
BOOL cellsExist;
UICollectionViewLayoutAttributes *firstObjectAttrs;
UICollectionViewLayoutAttributes *lastObjectAttrs;
if (numberOfItemsInSection > 0) { // use cell data if items exist
cellsExist = YES;
firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
} else { // else use the header and footer
cellsExist = NO;
firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:firstObjectIndexPath];
lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
atIndexPath:lastObjectIndexPath];
}
CGFloat topHeaderHeight = (cellsExist) ? CGRectGetHeight(layoutAttributes.frame) : 0;
CGFloat bottomHeaderHeight = CGRectGetHeight(layoutAttributes.frame);
CGRect frameWithEdgeInsets = UIEdgeInsetsInsetRect(layoutAttributes.frame,
cv.contentInset);
CGPoint origin = frameWithEdgeInsets.origin;
origin.y = MIN(MAX(contentOffset.y + cv.contentInset.top,
(CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight - self.sectionInset.top))
,(CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight + self.sectionInset.bottom));
layoutAttributes.zIndex = 1024;
layoutAttributes.frame = (CGRect){
.origin = origin,
.size = layoutAttributes.frame.size
};
}
}
return answer;
}
#end

Resources