How do you vertically align the UICollectionViewCells in a UICollectionView? - ios

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.

Related

How to create a Horizontal collection view like AIRBNB (Auto select index path on slide)

Sample video of AIRBNB APP
I implemented logic in func scrollViewDidScroll(_ scrollView: UIScrollView) but it is not working properly.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let principal = scrollView.contentSize.width - scrollView.frame.width
let progress = scrollView.contentOffset.x / principal
var progresss = max(0, min(1, progress))
progressBar.setProgress(Float(progresss), animated: true)
new = Int((CGFloat(arrayTagBasedFilter.count)) * progresss)
new = Int(max(0, min(arrayTagBasedFilter.count-1, new)))
let newIndex = IndexPath(item: new, section: 0)
if let selectedIndexForFilter = selectedIndexForFilter
{
if let cell = collectionViewFilter.cellForItem(at: selectedIndexForFilter) as? BoostFilterCVCell
{
cell.lblTitle.textColor = .black.withAlphaComponent(0.5)
}
}
if let cell = collectionViewFilter.cellForItem(at: newIndex) as? BoostFilterCVCell
{
selectedIndexForFilter = newIndex
cell.lblTitle.textColor = .black
progressView.frame = CGRect(origin: CGPoint(x: cell.frame.origin.x, y: collectionViewFilter.frame.origin.y - 50),
size: CGSize(width: cell.frame.width, height: 1))
}
}
I was able to achieve this using the visibleCells method of UICollectionView. Here is an example using colored cells, where the "highlighted" cell has its alpha transparency reduced to 25%.
This example is in Objective-C, but you should be able to convert it to Swift fairly easily.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
void (^animateCellAlpha)(__kindof UICollectionViewCell *cell, CGFloat alpha) = ^(__kindof UICollectionViewCell *cell, CGFloat alpha) {
[UIView animateWithDuration:0.25 animations:^{
[cell setAlpha:alpha];
}];
};
void (^highlightCell)(__kindof UICollectionViewCell *cell) = ^(__kindof UICollectionViewCell *cell) {
animateCellAlpha(cell, 0.25);
};
void (^unhighlightCell)(__kindof UICollectionViewCell *cell) = ^(__kindof UICollectionViewCell *cell) {
animateCellAlpha(cell, 1.0);
};
// fetch your collection view
UICollectionView * const collectionView = (UICollectionView *)scrollView;
// get the visible cells sorted by X value
NSArray <__kindof UICollectionViewCell *> * const visibleCells = [[collectionView visibleCells] sortedArrayUsingComparator:^NSComparisonResult(__kindof UICollectionViewCell * _Nonnull obj1, __kindof UICollectionViewCell * _Nonnull obj2) {
return [#(CGRectGetMinX([obj1 frame])) compare:#(CGRectGetMinX([obj2 frame]))];
}];
// determine which cell should or should not be highlighted
BOOL __block didHighlightCell = NO;
[visibleCells enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) {
if (didHighlightCell) {
// we do not want to highlight cells if a cell has already been highlighted
unhighlightCell(cell);
return;
}
// only highlight the cell if more than 50% of it is visible
CGRect const frame = [[self view] convertRect:[cell frame] fromView:[cell superview]];
if (CGRectGetMinX(frame) + CGRectGetWidth(frame) > CGRectGetWidth(frame) * 0.50) {
highlightCell(cell);
didHighlightCell = YES;
} else {
unhighlightCell(cell);
}
}];
}

Hiding Tableview section headers on the fly

I am trying to hide and display section headers when there is or isn't data populating the tableview. The code I have now works as intended occasionally, but mostly, one or both of the section headers will remain displayed, (However, if I drag the header off screen when there is no data it will disappear as intended).
This is the code I am using to hide/unhide the sections.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Create custom header titles for desired header spacing
if (section == 0) {
UIView *headerView;
if (self.searchUsers.count || self.searchTracks.count)
{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
}
else{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
}
HeaderTitleLabel *headerTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 10, 150, 20) headerText:#"USERS"];
[headerView addSubview:headerTitle];
return headerView;
}
else if (section == 1){
UIView *headerView;
if (self.searchUsers.count || self.searchTracks.count)
{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
}
else{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
}
HeaderTitleLabel *headerTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 0, 150, 20) headerText:#"SONGS"];
[headerView addSubview:headerTitle];
return headerView;
}
else
return nil;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
//custom header spacing
if (section == 0) {
if (self.searchUsers.count || self.searchTracks.count)
{
return 40;
}
else
return 0;
}
else if (section == 1) {
if (self.searchUsers.count || self.searchTracks.count)
{
return 50;
}
else
return 0;
}
else
return 0;
}
I check if my array has objects, if not then set the frame's height to 0. This doesn't seem to be working though. Any ideas how I should go about doing this? Thank you.
Your model should drive your table view. Don't just return a 'number of rows' value from your model, but also a 'number of sections' value.
Consider an array that contains two arrays as your model.
You are using wrong approach of table view. Can you clarify what do you exactly want to do. I could help you. Rather then UIView use UITableView Cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.searchUsers.count || self.searchTracks.count)
{
if (indexPath.section ==0) {
static NSString *simpleTableIdentifier = #"CategoryCell";
CategoryCell *cell = (CategoryCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CategoryCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.lbl_CatName.text = [[self.CategoryArray objectAtIndex:indexPath.row] objectForKey:kNAME];
return cell;}
else if (indexPath.section ==1){
static NSString *EarnPointIdentifier = #"EarnPointTableCell";
EarnPointTableCell *EarnPointcell = (EarnPointTableCell *)[tableView dequeueReusableCellWithIdentifier:EarnPointIdentifier];
if (EarnPointcell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"EarnPointTableCell" owner:self options:nil];
EarnPointcell = [nib objectAtIndex:0];
}
return EarnPointcell ;
}
else{
//it will have transparent background and every thing will be transparent
static NSString *RewardIdentifier = #"RewardTableCell";
RewardTableCell *RewardCell = (RewardTableCell *)[tableView dequeueReusableCellWithIdentifier:RewardIdentifier];
if (RewardCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"RewardTableCell" owner:self options:nil];
RewardCell = [nib objectAtIndex:0];
}
return RewardCell ;
}
}
}
It seems to be working fine now. What I did was create boolean values to keep track of my data when my data source is nil. Like so;
[SoundCloudAPI getTracksWithSearch:userInput userID:nil withCompletion:^(NSMutableArray *tracks) {
self.searchTracks = tracks;
[self.tableView beginUpdates];
if (tracks.count) {
self.songsHeaderIsHidden = NO;
}
else
self.songsHeaderIsHidden = YES;
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
[[self.tableView headerViewForSection:1] setNeedsLayout];
[[self.tableView headerViewForSection:1] setNeedsDisplay];
[self.tableView endUpdates];
}];
Then set the header accordingly..
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Create custom header titles for desired header spacing
if (section == 0) {
if (self.userHeaderIsHidden) {
return nil;
}
else {
UIView *userHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
HeaderTitleLabel *usersHeaderTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 10, 150, 20) headerText:#"USERS"];
[userHeaderView addSubview:usersHeaderTitle];
return userHeaderView;
}
}
else if (section == 1){
if (self.songsHeaderIsHidden) {
return nil;
}
else {
UIView *songsHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
songsHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
HeaderTitleLabel *songsHeaderTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 0, 150, 20) headerText:#"SONGS"];
[songsHeaderView addSubview:songsHeaderTitle];
return songsHeaderView;
}
}
else
return nil;
}
Personally, I would use UICollectionView and write your own layout. That way you can explicitly layout where the section headers will be at all times. The best approach is to probably subclass UICollectionViewFlowLayout because you get all the superclass properties for free (such as itemSize, headerReferenceSize, etc.)
Here's an implementation I wrote for having sticky headers, like the Instagram app.
Notice that in prepareLayout, I just calculate the normal positions of every element. Then in layoutAttributesForElementInRect I go through and figure out where to place the header. This is where you can go through and see if the header should be shown or not.
import UIKit
class BoardLayout: UICollectionViewFlowLayout {
private var sectionFrames: [CGRect] = []
private var headerFrames: [CGRect] = []
private var footerFrames: [CGRect] = []
private var layoutAttributes: [UICollectionViewLayoutAttributes] = []
private var contentSize: CGSize = CGSizeZero
override func prepareLayout() {
super.prepareLayout()
self.sectionFrames.removeAll(keepCapacity: false)
self.headerFrames.removeAll(keepCapacity: false)
self.footerFrames.removeAll(keepCapacity: false)
self.layoutAttributes.removeAll(keepCapacity: false)
let sections = self.collectionView?.numberOfSections()
var yOffset: CGFloat = 0.0
for var i: Int = 0; i < sections; i++ {
let indexPath = NSIndexPath(forItem: 0, inSection: i)
var itemSize: CGSize = self.itemSize
if let d = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout {
if let collection = self.collectionView {
if let size = d.collectionView?(collection, layout: self, sizeForItemAtIndexPath: NSIndexPath(forItem: 0, inSection: i)) {
itemSize = size
}
}
}
let headerFrame = CGRect(x: 0, y: yOffset, width: self.headerReferenceSize.width, height: self.headerReferenceSize.height)
self.headerFrames.append(headerFrame)
var headerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withIndexPath: indexPath)
headerAttribute.frame = headerFrame
headerAttribute.zIndex = 1000
self.layoutAttributes.append(headerAttribute)
yOffset += self.headerReferenceSize.height - 1 // - 1 so that the bottom border of the header and top border of the cell line up
let sectionFrame = CGRect(x: 0, y: yOffset, width: itemSize.width, height: itemSize.height)
self.sectionFrames.append(sectionFrame)
var sectionAttribute = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
sectionAttribute.frame = sectionFrame
self.layoutAttributes.append(sectionAttribute)
yOffset += itemSize.height
let footerFrame = CGRect(x: 0, y: yOffset, width: self.footerReferenceSize.width, height: self.footerReferenceSize.height)
self.footerFrames.append(footerFrame)
var footerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withIndexPath: indexPath)
footerAttribute.frame = footerFrame
self.layoutAttributes.append(footerAttribute)
yOffset += self.minimumLineSpacing + self.footerReferenceSize.height
}
self.contentSize = CGSize(width: self.collectionView!.width, height: yOffset)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var newAttributes: [UICollectionViewLayoutAttributes] = []
for attributes in self.layoutAttributes {
let frame = attributes.frame
if !CGRectIntersectsRect(frame, CGRect(x: 0, y: rect.origin.y, width: rect.size.width, height: rect.size.height)) {
continue
}
let indexPath = attributes.indexPath
let section = indexPath.section
if let kind = attributes.representedElementKind {
if kind == UICollectionElementKindSectionHeader {
var headerFrame = attributes.frame
let footerFrame = self.footerFrames[section]
if CGRectGetMinY(headerFrame) <= self.collectionView!.contentOffset.y {
headerFrame.origin.y = self.collectionView!.contentOffset.y
}
if CGRectGetMinY(headerFrame) >= CGRectGetMinY(footerFrame) {
headerFrame.origin.y = footerFrame.origin.y
}
attributes.frame = headerFrame
self.headerFrames[section] = headerFrame
} else if kind == UICollectionElementKindSectionFooter {
attributes.frame = self.footerFrames[section]
}
} else {
attributes.frame = self.sectionFrames[section]
}
newAttributes.append(attributes)
}
return newAttributes
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let frame = self.sectionFrames[indexPath.item]
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
attributes.frame = frame
return attributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let attributes = super.layoutAttributesForSupplementaryViewOfKind(elementKind, atIndexPath: indexPath)
if elementKind == UICollectionElementKindSectionHeader {
attributes.frame = self.headerFrames[indexPath.section]
} else if elementKind == UICollectionElementKindSectionFooter {
attributes.frame = self.footerFrames[indexPath.section]
}
return attributes
}
override func collectionViewContentSize() -> CGSize {
return self.contentSize
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
}
I'd recommend using the UICollectionViewDelegateFlowLayout methods so you can dynamically determine the reference size of the header.

Set height of delete button that appears on swipe in UITableViewCell

I have UITableViewCell as shown in figure below.
The cell occupy the height occupied by delete. The cell height is set so as to keep spacing between two cell.
Now, when i swipe and delete button appears (red in color), it occupies cell height as given in picture above. I simply want to set its height to height of white part only or say the height of gray button. Can anyone help me on how to set the height of delete button that appears after swipe in UITableViewCell?
The best way to solve this was overriding
-(void)layoutSubviews in YourCustomCell:UITableViewCell
then
if ([NSStringFromClass([subview class])isEqualToString:#"UITableViewCellDeleteConfirmationControl"]){
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect buttonFrame = deleteButtonView.frame;
buttonFrame.origin.x = Xvalue;
buttonFrame.origin.y = Yvalue;
buttonFrame.size.width = Width;
buttonFrame.size.height = Height;
deleteButtonView.frame = buttonFrame;
}
Use this code in your custom Cell class
-(void) layoutSubviews
{
NSMutableArray *subviews = [self.subviews mutableCopy];
UIView *subV = subviews[0];
if ([NSStringFromClass([subV class])isEqualToString:#"UITableViewCellDeleteConfirmationView"]){
[subviews removeObjectAtIndex:0];
CGRect f = subV.frame;
f.size.height = 106; // Here you set height of Delete button
subV.frame = f;
}
}
Swift 5, works for iOS12, iOS13 and iOS14
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
// for iOS13, iOS14
if let swipeContainerView = tableView.subviews.first(where: { String(describing: type(of: $0)) == "_UITableViewCellSwipeContainerView" }) {
if let swipeActionPullView = swipeContainerView.subviews.first, String(describing: type(of: swipeActionPullView)) == "UISwipeActionPullView" {
swipeActionPullView.frame.size.height -= 10
}
}
// for iOS12
tableView.subviews.forEach { subview in
if String(describing: type(of: subview)) == "UISwipeActionPullView" {
subview.frame.size.height -= 10
}
}
}
Add this method to your customCell.m file.
-(void) layoutSubviews
{
NSMutableArray *subviews = [self.subviews mutableCopy];
UIView *subview = subviews[0];
if ([NSStringFromClass([subview class])isEqualToString:#"UITableViewCellDeleteConfirmationView"]){
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect buttonFrame = deleteButtonView.frame;
buttonFrame.origin.x = deleteButtonView.frame.origin.x;
buttonFrame.origin.y = deleteButtonView.frame.origin.y;
buttonFrame.size.width = deleteButtonView.frame.size.width;
buttonFrame.size.height = 46;
deleteButtonView.frame = buttonFrame;
subview.frame=CGRectMake(subview.frame.origin.x, subview.frame.origin.y, subview.frame.size.width, 46);
deleteButtonView.clipsToBounds=YES;
subview.clipsToBounds=YES;
}
}
For IOS 13 , the Position has been yet again change , not inside table view it is once again in _UITableViewCellSwipeContainerView . Thus you should iterate through that as well.Take a look below
([NSStringFromClass([subview class])
isEqualToString:#"_UITableViewCellSwipeContainerView"]){
for (UIView *deleteButtonSubview in subview.subviews){
if ([NSStringFromClass([deleteButtonSubview class])
isEqualToString:#"UISwipeActionPullView"]) {
if ([NSStringFromClass([deleteButtonSubview.subviews[0] class]) isEqualToString:#"UISwipeActionStandardButton"]) {
//do what you want
}
}
}
}
Swift 5 - iOS 14
Change the way you are handling cell height to add spacing using the following:
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 10
super.frame = frame
}
}
Write below code in your custom cell hope it will work for you-
- (void)willTransitionToState:(UITableViewCellStateMask)state
{
[super willTransitionToState:state];
if(state == UITableViewCellStateShowingDeleteConfirmationMask)
{
[self performSelector:#selector(resetDeleteButtonSize) withObject:nil afterDelay:0];
}
}
- (void)resetDeleteButtonSize
{
NSMutableArray *subviews = [self.subviews mutableCopy];
while (subviews.count > 0)
{
UIView *subV = subviews[0];
[subviews removeObjectAtIndex:0];
if ([NSStringFromClass([subV class])isEqualToString:#"UITableViewCellDeleteConfirmationButton"])
{
CGRect f = subV.frame;
f.size.height = 74;
subV.frame = f;
break;
}
else
{
[subviews addObjectsFromArray:subV.subviews];
}
}
}
To see how it's work in IOS 11 please copy this Swift 4 code snippet in your UITableViewController:
override func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
self.tableView.subviews.forEach { subview in
print("YourTableViewController: \(String(describing: type(of: subview)))")
if (String(describing: type(of: subview)) == "UISwipeActionPullView") {
if (String(describing: type(of: subview.subviews[0])) == "UISwipeActionStandardButton") {
var deleteBtnFrame = subview.subviews[0].frame
deleteBtnFrame.origin.y = 12
deleteBtnFrame.size.height = 155
// Subview in this case is the whole edit View
subview.frame.origin.y = subview.frame.origin.y + 12
subview.frame.size.height = 155
subview.subviews[0].frame = deleteBtnFrame
subview.backgroundColor = UIColor.yellow
}
}
}
}
This code working for IOS 11 and higher
SWIFT
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
if String(describing: type(of: subview.self)) == "UITableViewCellDeleteConfirmationView" {
let deleteButton = subview
let deleteButtonFrame = deleteButton.frame
let newFrame = CGRect(x: deleteButtonFrame.minX,
y: deleteButtonFrame.minY,
width: deleteButtonFrame.width,
height: yourHeight)
deleteButton.frame = newFrame
}
}
}

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

Detect if UITableView section header is floating

is there a way to detect if a header of a section in an UITableView is currently floating? I want to scroll the table view to the header position only if it is floating.
Thanks in advance!
The header will be floating if the first cell of the section is no longer visible. So:
NSIndexPath *topCellPath = [[self.tableView indexPathsForVisibleRows] objectAtIndex:0];
if (topCellPath.row != 0)
{
// Header must be floating!
}
You could achieve a similar effect by scrolling to the index path with scrollToRowAtIndexPath:atScrollPosition:animated: and a scroll position of UITableViewScrollPositionNone - this would not scroll if the first cell in the section was already on the screen.
This works.
#implementation UITableView (rrn_extensions)
-(BOOL)rrn_isFloatingSectionHeaderView:(UITableViewHeaderFooterView *)view {
NSNumber *section = [self rrn_sectionForHeaderFooterView:view];
return [self rrn_isFloatingHeaderInSection:section.integerValue];
}
-(BOOL)rrn_isFloatingHeaderInSection:(NSInteger)section {
CGRect frame = [self rectForHeaderInSection:section];
CGFloat y = self.contentInset.top + self.contentOffset.y;
return y > frame.origin.y;
}
-(NSNumber *)rrn_sectionForHeaderFooterView:(UITableViewHeaderFooterView *)view {
for (NSInteger i = 0; i < [self numberOfSections]; i++) {
CGPoint a = [self convertPoint:CGPointZero fromView:[self headerViewForSection:i]];
CGPoint b = [self convertPoint:CGPointZero fromView:view];
if (a.y == b.y) {
return #(i);
}
}
return nil;
}
#end
Adding a swift port of #robdashnash's answer
extension UITableView
{
func isFloatingSectionHeader( view:UITableViewHeaderFooterView )->Bool
{
if let section = section( for:view )
{
return isFloatingHeaderInSection( section:section )
}
return false
}
func isFloatingHeaderInSection( section:Int )->Bool
{
let frame = rectForHeader( inSection:section )
let y = contentInset.top + contentOffset.y
return y > frame.origin.y
}
func section( for view:UITableViewHeaderFooterView )->Int?
{
for i in stride( from:0, to:numberOfSections, by:1 )
{
let a = convert( CGPoint.zero, from:headerView( forSection:i ) )
let b = convert( CGPoint.zero, from:view )
if a.y == b.y
{
return i
}
}
return nil
}
}
iOS 11.0+ solution for UITableViews as well as UICollectionViews
In both cases, use the scrollViewDidScroll method. You can use the currentHeader property to store current header displayed at the top. HEIGHT_OF_HEADER_VIEW is a value you should specify yourself.
For UITableView
private var currentHeader: UITableViewHeaderFooterView? {
willSet {
// Handle `old` header
} didSet {
// Hanlde `new` header
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let point = CGPoint(x: 0, y: HEIGHT_OF_HEADER_VIEW + tableView.contentOffset.y + tableView.adjustedContentInset.top)
guard let path = tableView.indexPathForRow(at: point) else {
return
}
let section = path.section
let rect = tableView.rectForHeader(inSection: section)
let converted = tableView.convert(rect, to: tableView.superview)
let safeAreaInset = tableView.safeAreaInsets.top
// Adding 1 offset because of large titles that sometimes cause slighly wrong converted values
guard converted.origin.y <= safeAreaInset,
let header = tableView.headerView(forSection: section) else {
return
}
currentHeader = header
}
For UICollectionView
private var currentHeader: UICollectionReusableView? {
willSet {
// Handle `old` header
} didSet {
// Handle `new` header
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let x = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset.left ?? 0
let point = CGPoint(x: x, y: HEIGHT_OF_HEADER_VIEW + collectionView.contentOffset.y + collectionView.adjustedContentInset.top)
guard let path = collectionView.indexPathForItem(at: point),
let rect = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: path.section))?.frame else {
return
}
let converted = collectionView.convert(rect, to: collectionView.superview)
let safeAreaInset = collectionView.safeAreaInsets.top
guard converted.origin.y <= safeAreaInset,
let header = collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: path.section)) else {
return
}
currentHeader = header
}
You can first store the original rect of section header view.
headerView.originalPoint = [tableView rectForHeaderInSection:section].origin;
And send scrollViewDidScroll:scrollView message to header.
In your header view
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
BOOL isFloating = self.frame.origin.y > self.originalPoint.y;
if (isFloating) {
//DO what you want
}
}
The code works for me.
- (BOOL)isSection0HeaderSticky {
CGRect originalFrame = [self.listTableView rectForHeaderInSection:0];
UIView *section0 = [self.listTableView headerViewForSection:0];
if (originalFrame.origin.y < section0.frame.origin.y) {
return YES;
}
return NO;
}

Resources