I am trying to implement a table view in which as I scroll the height of the cell at top becomes bigger than rest of the cells.
I basically want the first cell to be of 200f always and rest to be 100f.
I am able to to do it when user is scrolling down but when user is scrolling to the top and is scrolling too fast, the frame is not reset properly.
relavant code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
int row = scrollView.contentOffset.y / kSmallHeight;
_currentCellIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
UITableViewCell *topCell = [self.tableView cellForRowAtIndexPath:_currentCellIndexPath];
NSIndexPath *path = [NSIndexPath indexPathForRow:_currentCellIndexPath.row + 1 inSection:_currentCellIndexPath .section];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:path];
[self.tableView insertSubview:cell aboveSubview:topCell];
[self adjustCellHeightForCell:cell withDefaultOffset:NO];
}
- (void)adjustCellHeightForCell:(UITableViewCell *)cell withDefaultOffset:(BOOL)defailtOffset
{
int row = self.tableView.contentOffset.y / kSmallHeight;
double fakeOriginy = (kBigHeight + kSmallHeight * (cell.tag-1));
double set = row * kSmallHeight;
CGRect frame = cell.frame;
double offset;
if(defailtOffset)
offset = kSmallHeight;
else {
offset = ((self.tableView.contentOffset.y - set ) * ((kBigHeight - kSmallHeight)/ kSmallHeight));
}
frame.origin.y = fakeOriginy - offset;
frame.size.height = kSmallHeight + offset;
cell.frame = frame;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = self.tableView.contentOffset.y / kSmallHeight;
_currentCellIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
if(indexPath.row != 0 && indexPath.row < _currentCellIndexPath.row)
[self adjustCellHeightForCell:cell withDefaultOffset:YES];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
//This is the index of the "page" that we will be landing at
NSUInteger nearestIndex = (NSUInteger) ( (int)targetContentOffset->y % (int)kSmallHeight) >= kSmallHeight/2 ? targetContentOffset->y / kSmallHeight + 1 : targetContentOffset->y / kSmallHeight;
//Just to make sure we don't scroll past your conpo tent
nearestIndex = MAX( MIN( nearestIndex, self.store.coupons.count - 1 ), 0 );
//This is the actual x position in the scroll view
CGFloat yOffset = nearestIndex * kSmallHeight;
//I've found that scroll views will "stick" unless this is done
yOffset = yOffset==0?1:yOffset;
//Tell the scroll view to land on our page
*targetContentOffset = CGPointMake(targetContentOffset->x,yOffset);
}
Try This!
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
{
if(indexpath.row == 0)
{
//call your re-adjust height method here
[your_table setContentOffset:CGPointZero animated:YES];
[self adjustCellHeightForCell:cell withDefaultOffset:NO];
}
}
Iterating through the visible cells and resetting their frames at the end of viewDidScroll worked excluding the cell that has to be transformed i.e. first visible 100.0f cell on the screen.
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) {
NSIndexPath *path = [self.tableView indexPathForCell:cell];
if(path.row > topCellPath.row + 1) {
// reset frames of small cells
}
else if(path.row <= topCellPath.row){
// reset frames of big cells
}
}];
Related
So i have a tableview with N amount of rows. The tableview is designed like a swimming track. Each row has a swimmer image and the swimmer's X location is based upon their position in the race.
The UITableViewCell has been designed with IB and using Autolayout Constraints.
Each cell swimmer gets their position from an index of an array, ie...
// Method - cellForRowAtIndexPath:
cell.swimmerLeading.constant = [_array[indexPath.row] integerValue]
Whenever i scroll, the swimmerLeading.constant gets reset back to its original value of 8;
Any clues as to why?
- (void)viewDidLoad {
...
[self registerNib:[UINib nibWithNibName:#"RacePredictorTrackTableViewCell" bundle:nil] forCellReuseIdentifier:#"TrackCell"];
...
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == TableSectionTrack) {
RacePredictorTrackTableViewCell *cell = (RacePredictorTrackTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"TrackCell" forIndexPath:indexPath];
cell.delegate = self;
cell.indexPath = indexPath;
Runner *runner = _runners[indexPath.row];
cell.isStarred = NO;
cell.cellPositionlabel.text = runner.predictorPlace;
cell.cellPercentageFloat = runner.predictorRanking.floatValue;
return cell;
}
}
cell.cellPercentageFloat is just a setter method that sets the value to two constraints, swimmer and label for swimmer
RacePredictorTrackTableViewCell.m
- (void)setCellPercentageFloat:(CGFloat)cellPercentageFloat {
cellPercentageFloat = cellPercentageFloat / 100;
CGFloat labelWidth = _cellHorseLabel.intrinsicContentSize.width;
CGFloat labelRaceLineBuffer = 8;
_horseImageViewLeading.constant = (self.totalSpace * cellPercentageFloat) + kOriginalHorseImageViewLeading;;
if ((_horseImageViewLeading.constant + _horseImageViewWidth.constant) > labelWidth) {
_horseLabelLeading.constant = (_horseImageViewLeading.constant + _horseImageViewWidth.constant) - labelWidth - labelRaceLineBuffer;
} else {
_horseLabelLeading.constant = _horseImageViewLeading.constant;
}
}
I've got a tableView. When user taps one of records, I check it's property and basing on this I'm filtering results and showing only similar results - so I need to refresh the tableView.
The problem is that user can scroll up/down the tableView. I need to scroll the tableView so the cell is exactly at the same UITableViewScrollPosition as before the refresh.
Obviously, I'm saving last tapped item
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_lastSelectedItem = [self itemForIndexPath:indexPath];
}
Then after reloading the tableView:
NSIndexPath *indexPath = [self indexPathForItem:_lastSelectedItem];
if (_tableView.numberOfSections > indexPath.section && [_tableView numberOfRowsInSection:indexPath.section] > indexPath.row) {
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
_lastSelectedItem = nil;
}
It would be good but... user could not finish at UITableViewScrollPositionMiddle. He could have finish scrolling at UITableViewScrollPositionTop or UITableViewScrollPositionBottom or even somewhere between.
-- edit --
Calculating via offset is also problematic, as the offset is the difference between start of view and top table scroll position.. while I'm not interested in this value :/.
Because my structure was quite complicated I finished to do it this way (eg. expandable sections etc). Please do mind, that I'm saving _lastSelectedItem, which is my object at datasource, as indexPath will change after refresh.
- (void)refresh {
[self saveLastScrollPosition];
[self reloadData]; // inside we reload data + reload tableView
[self scrollToLastScrollPosition];
}
- (NSInteger)heightToMinYOfCellAtIndexPath:(NSIndexPath *)indexPath {
NSInteger sections = indexPath.section;
NSInteger totalRows = 0;
for (NSInteger section = 0; section < indexPath.section; section++) {
totalRows += [self.tableView numberOfRowsInSection:section];
}
totalRows += indexPath.row;
return ((sections + 1) * kHeaderHeight + totalRows * kRowHeight);
}
- (void)saveLastScrollPosition {
if (_lastSelectedItem) { // make sure we have that item selected
NSIndexPath *indexPath = [self itemForItem:_lastSelectedItem];
NSInteger aboveItemHeight = [self heightToMinYOfCellAtIndexPath:indexPath];
CGFloat contentOffsetY = self.tableView.contentOffset.y;
_previousOffset = aboveItemHeight - contentOffsetY;
}
}
- (void)scrollToLastScrollPostion {
if (_lastSelectedItem) { // make sure we have that item selected
NSIndexPath *indexPath = [self itemForItem:_lastSelectedItem];
if (self.tableView.numberOfSections > indexPath.section && [self.tableView numberOfRowsInSection:indexPath.section] > indexPath.row) { // make sure the indexPath still exist after reload
NSInteger aboveItemHeight = [self heightToMinYOfCellAtIndexPath:indexPath]; // height of items above selected index
CGPoint offset = CGPointMake(0.f, aboveItemHeight - _previousOffset);
// in case index should be higher: eg earlier item was at index (4,8) now is at (0,0)
if (offset.y < 0) {
offset.y = 0;
}
[self.tableView setContentOffset:offset animated:NO]; // just jump, user shouldn't see this
_previousOffset = 0; // forget the calculated values
_lastSelectedItem = nil;
}
}
}
I need to create a UICollectionView cells with dynamic heights according to each cell's image view height. I have created a subclass from UICollectionViewFlowLayout to implement the desired behaviour as follows:
#define numColumns 2
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (attributes.representedElementKind == nil) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item < numColumns) {
CGRect frame = currentItemAttributes.frame;
frame.origin.y = 5;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
NSIndexPath* indexPathPrev = [NSIndexPath indexPathForItem:indexPath.item-numColumns inSection:indexPath.section];
CGRect framePrev = [self layoutAttributesForItemAtIndexPath:indexPathPrev].frame;
CGFloat YPointNew = framePrev.origin.y + framePrev.size.height + 10;
CGRect frame = currentItemAttributes.frame;
frame.origin.y = YPointNew;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
And in the collection view controller delegate flow layout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
float imageHeight = [[(Product *)self.products[indexPath.row] image] height];
float imageWidth = [[(Product *)self.products[indexPath.row] image] width];
float ratio = 150.0/imageWidth;
return CGSizeMake(150, ratio*imageHeight);
}
Cell implementation
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ProductCell *cell = (ProductCell *)[self.collectionView dequeueReusableCellWithReuseIdentifier:#"ProductCell" forIndexPath:indexPath];
....
[cell.imageView setImageWithURL:[NSURL URLWithString:[[(Product *)self.products[indexPath.row] image] url]]];
return cell;
}
All the cells showed up as expected, but the content view was too long as shown.
I think I have to implement the method -(CGSize)collectionViewContentSize to get the correct contentSize, but I don't know how to calculate the actual height of the collection view
I was trying to subclass collection view layout , in order to get a constant spacing between vertical cells. i have 2 columns and many rows, with dynamic cells height .
The goal is Pinterest like grid.
So i have subclassed the layout class, and now has a constant space between cells, but there is a serious problem caused by that .
When i scroll down, the left cells are not being loaded in time= there are many "holes" so that there is blank space of 3-4 cells, and than- they suddenly appears at once -lately.
so i have this :
1 2
3 4
6
8
than 5 and 7 appears when i scroll down more . i just can't get rid of this !
EDIT: seems that when all cells are in the same size, this will not happens ,so when i return here a constant height :
//cell size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
CGSize size=CGSizeMake( imageWidth, scale*height+[Globals sharedGlobals].gridViewStripHeight );
return size;
My subclass(which when not using it, also solves the problem )
//the subclass
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* atts in arr)
{
if (nil == atts.representedElementKind)
{
NSIndexPath* ip = atts.indexPath;
atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
}
}
return arr;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* atts =[super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item == 0 || indexPath.item == 1) // degenerate case 1, first item of section
return atts;
NSIndexPath* ipPrev =
[NSIndexPath indexPathForItem:indexPath.item-2 inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat rightPrev = fPrev.origin.y + fPrev.size.height + 50;
if (atts.frame.origin.y <= rightPrev) // degenerate case 2, first item of line
return atts;
CGRect f = atts.frame;
f.origin.y = rightPrev;
atts.frame = f;
return atts;
}
To use it i have :
UICollectionViewFlowLayout *layout=[[TopAlignedCollectionViewFlowLayout alloc] init]; subclass
CGRect size=CGRectMake( ([UIScreen mainScreen].bounds.size.width-collectionWidth)/2,
upperLineMargin, collectionWidth, [UIScreen mainScreen].bounds.size.height-upperLineMargin);
self.GridView=[[UICollectionView alloc] initWithFrame:size collectionViewLayout:layout];
[self.GridView registerClass:[GridCell class] forCellWithReuseIdentifier:#"Cell"];
I have a chat function in the app I am making. Each cell has a UITextView and I need to calculate the **height and width** of each cell. I do that and then cache that data.
Using NSLog I have determined that the data is correctly cached and the height for each row is only being asked for once.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *message = [feedItems objectAtIndex:indexPath.row];
NSString *reuseIdentifier;
if (message.iSentThisMessage) {
reuseIdentifier = #"MeCell";
} else {
reuseIdentifier = #"HostCell";
}
ChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textView.text = message.content;
if (message.frame.size.height == 0 && message.frame.size.width == 0) {
CGRect rect = cell.textView.frame;
rect.origin.x = 5;
rect.origin.y = 3;
CGSize size = [self text:message.content
sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(225.0, CGFLOAT_MAX)];
rect.size.height = size.height
+ cell.textView.textContainerInset.bottom
+ cell.textView.textContainerInset.top;
rect.size.width = [cell.textView sizeThatFits:CGSizeMake(225.0, rect.size.height)].width
+ cell.textView.textContainerInset.left
+ cell.textView.textContainerInset.right;
if (message.iSentThisMessage) {
rect.origin.x = 320 - (rect.origin.y + rect.size.width);
}
cell.textView.frame = rect;
message.frame = rect;
NSLog(#"Not Cached :(");
} else {
cell.textView.frame = message.frame;
NSLog(#"Cached!");
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *message = [feedItems objectAtIndex:indexPath.row];
CGSize size = [self text:message.content
sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:CGSizeMake(225.0, CGFLOAT_MAX)];
if (indexPath.row + 1 == feedItems.count)
size.height += 4;
NSLog(#"Height asked for.");
return size.height + 12;
}
Also, one other possibility may be that I am overriding the following method in my custom uitableviewcell class because otherwise it rearranges the contents of my cell.
- (void) layoutSublayersOfLayer:(CALayer *)layer {
}
I can't seem to figure out how to make it not lag!!
Not clear why you're using a text view, but you should change to a label if you can.
Also, don't cache the height in your table delegate method, that's a nasty side effect. Calculate the height up front, either when the message is loaded / received, or on a background thread for batch load.
The text view / label frame should also be relative to the cell, either by auto resizing / layout. So you should never set it explicitly.
On top of that - profile using instruments. This is the only thing that will really tell you what's wrong.