I would like to fire an event when the subview of a UITableviewCell reaches a certain point on the screen, say for example when its origin.y reaches 44 points. It would also be nice to know if it was being scrolled up or down when it reached that point. I was playing with KVO on the frame of the subview but this seems fixed to the cell so no changes with that. Is this task possible?
Vertical position of UITableViewCell is defined by its frame property, which represents position and size of that cell within its superview, UITableView. Typically, the frame property of the cell is changing only once for every time that UITableView requests a cell from its delegate for specific index path. That's it, UITableView gets a cell, places it in itself and that cell just lays there unchanged until rectangle stored in bounds property of UITableView ceases to include rectangle stored in the frame property of that cell. In that case UITableView marks that cell as hidden and places it into the pool of reusable cells.
Since the process of scrolling in essence is not a repositioning of subviews – it is merely a curious illusion of shifting a bounds viewport of UITableView – constant observing of UITableViewCell's properties are pointless.
Moreover, the frame property of subview of UITableViewCell also represents a position and size of that subview within its container, UITableViewCell. It is also will not change on scroll.
You need to observe changes in UITableView bounds property, which is also represented by contentOffset by the way. UITableView happens to be a subclass of UIScrollView, so you can use its delegate methods, such as -scrollViewDidScroll:, like in this simple example:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UITableView *tableView = (UITableView *)scrollView;
// current position
CGFloat currentY = tableView.bounds.origin.y;
// current inset
CGFloat currentInset = tableView.contentInset.top;
// trigger line position
CGFloat triggerY = currentInset + currentY + kYourTriggerPosition;
// nice visual mark
UIView *line = [tableView viewWithTag:88];
if (!line) {
line = [[UIView alloc] initWithFrame:CGRectZero];
line.tag = 88;
line.backgroundColor = [UIColor redColor];
[tableView addSubview:line];
}
line.frame = CGRectMake(0, triggerY, tableView.bounds.size.width, 1);
// determine scroll direction
BOOL scrollingUp = currentY > self.previousY;
// all visible cells
NSArray *visibleCells = tableView.visibleCells;
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) {
// subview
UIView *subview = [cell viewWithTag:kYourSubviewTag];
// subview frame rect in UITableView bounds
CGRect subviewRect = [subview convertRect:subview.frame toView:tableView];
// trigger line within subview?
BOOL triggered = (CGRectGetMinY(subviewRect) <= triggerY) && (CGRectGetMaxY(subviewRect) >= triggerY);
if (triggered) {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
NSLog(#"moving %#, triggered for cell at [%2d:%2d]", #[#"down", #"up"][scrollingUp], indexPath.section, indexPath.row);
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
// save current position for future use
self.previousY = currentY;
}
Reach that subview of UITableViewCell with cellForRowAtIndexPath or tableView.visibleCells, then call convertRectToView: on that subview.
convertRectToView: allows you to do translations on different coordinate systems. For example, you can detect where that subview appears on screen by translating its frame within its superview into viewController.view
For more: Apple Documentation
Since I can not comment I am writing as an Answer
Changing the answer for the requirement.
Here is how I think it can be done, you need to have your custom UITableViewCell which has a function which can take in co-ordinates (again based on your logic if you just want an intersection where a cell just touches a boundary or if it has to be at a precise position in a frame), so your function would take the co-ordinates and will return a true and a false if it will tell you if the condition is met, and in your cellForTable function you call the function of UITableView cell to check if your condition is met, if it is in your view you create a subview at the exact location. You can also modify the function to return you the exact frame-cordinates so you can use them to create a subview\
Here's a simple approach, which you can use if you have only one section without section header.
Add this to your implementation:
CGFloat lastContentOffSet;
And then add this delegate method of scrollview as tableview is also a scrollview.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat cellHeight = 50;
CGFloat touchingPoint = 44.0f;
NSInteger rowNo = floor(scrollView.contentOffset.y / cellHeight);
NSInteger startPoint = (rowNo * cellHeight);
if (scrollView.contentOffset.y > lastContentOffSet) {
NSLog(#"Row %ld scrolled down", (long)rowNo);
if (scrollView.contentOffset.y > startPoint + touchingPoint) {
// Do something here
NSLog(#"Do something here");
}
}
else {
NSLog(#"Row %ld scrolled up", (long)rowNo);
if (scrollView.contentOffset.y > startPoint + touchingPoint) {
// Do something here
NSLog(#"Do something here");
}
}
lastContentOffSet = scrollView.contentOffset.y;
}
Change value of the cellheight according to your tableview cell and the distance of that subview with the cell.
Let me know if this code helped. :)
Related
I have uitableview in my iphone application.
My UITableViewCell has only one imageView.
There are such 7 uitableviewcells available with my uitableview.
Upto now I have done correctly. Below is the image where I have problem. The first cell not getting stacked to top edge of UITableView. See below image. Thus Not getting actual zooming effect when pulls down and drag scroll in downward direction (i.e. contentOffset.Y negative). Part of first cell hides behind second cell.
below is my code
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == _tvMaharajPics) {
if (scrollView.contentOffset.y <= 0) {
UITableViewCell *cell = [_tvMaharajPics cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
CGRect frame = cell.frame;
frame.size.height = (self.view.frame.size.width * (3.0 / 4.0)) - scrollView.contentOffset.y;
cell.frame = frame;
}
}
// To disable bounce in bottom direction of UITableView
if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.size.height) {
CGPoint offset = scrollView.contentOffset;
offset.y = scrollView.contentSize.height - scrollView.frame.size.height;
scrollView.contentOffset = offset;
}
}
I only want to bounce and show zoom image effect in first cell only when user pulls down and drag scroll in downward direction (i.e. contentOffset.Y negative).
"SIMILAR EFFECT IS DEMONSTRATED IN 'OYO' APP WHEN USER LOOKS INTO ROOM IMAGES"
You can swap the contents of the tableview cell (1st) to the tableview header and use this library to achieve this effect easily.
GSKStretchyHeaderView
Or if you want to code yourself then use this tutorial to create this effect
Tuturial
. Just remove the masking part from it.
I'm creating a view which comprises of a UITableView with customised cells - I'm overriding drawRect in my custom view. I've tried overriding UITableViewCell and adding my custom view as an IBOutlet, I've tried not overriding it and just referring to it by [[cell subviews] objectAtIndex:0]; both yield the same results.
When I first look at the view, all is fine. If I scroll slowly, all is fine. As soon as I scroll quickly the reused cells are clearly not re-drawing because I end up with the custom drawing, being wrong for the particular cells.
The cell configuration method...
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"DialogCell"];
MaskedRoundedCornerDIalogCell* dialogCell = (MaskedRoundedCornerDIalogCell*)[[[cell contentView] subviews] objectAtIndex:0];
[dialogCell setPadding:10];
if (indexPath.row % 2 == 0) {
[dialogCell setAlignLeft:YES];
[dialogCell setMaskTopLeftOnly];
[[dialogCell textContent] setText:#"LEFT ALIGNED TEXT"];
[[dialogCell textContent] setTextAlignment:NSTextAlignmentLeft];
} else {
[dialogCell setAlignLeft:NO];
[dialogCell setMaskBottomRightOnly];
[[dialogCell textContent] setText:#"RIGHT ALIGNED TEXT"];
[[dialogCell textContent] setTextAlignment:NSTextAlignmentRight];
}
return cell;
}
My Custom drawing code in my MaskedRoundedCornerDIalogCell implementation (A class which extends UIView and is added to the UITableViewCell):
- (void) drawRect:(CGRect)rect {
int maxWidth = rect.size.width - 50;
CGRect container;
if (_alignLeft) {
container = CGRectMake(rect.origin.x + _padding, rect.origin.y + _padding, maxWidth - (2* _padding), rect.size.height - (2*_padding));
} else {
container = CGRectMake((rect.size.width - _padding) - (maxWidth - (2* _padding)), rect.origin.y + _padding, maxWidth - (2* _padding), rect.size.height - (2*_padding));
}
UIRectCorner roundedCorners;
if (!_maskTopLeft) {
roundedCorners = roundedCorners | UIRectCornerTopLeft;
}
if (!_maskTopRight) {
roundedCorners = roundedCorners | UIRectCornerTopRight;
}
if (!_maskBottomLeft) {
roundedCorners = roundedCorners | UIRectCornerBottomLeft;
}
if (!_maskBottomRight) {
roundedCorners = roundedCorners | UIRectCornerBottomRight;
}
UIBezierPath* containerBezierPath = [UIBezierPath bezierPathWithRoundedRect:container byRoundingCorners: roundedCorners cornerRadii:CGSizeMake(25.0F, 25.0F)];
[[UIColor lightGrayColor] setFill];
[containerBezierPath fillWithBlendMode: kCGBlendModeNormal alpha:1.0f];
}
The way it looks when I first launch it:
The way it looks after scrolling a few times:
Any advice, gratefully received...
I'm not sure if you're overriding drawRect in your UITableViewCell subclass or if it's in a custom UIView class. My suggestion would be to do your drawing in a custom UIView class and then add that view as a subview of your cell - just in case UITableViewCell is doing something in drawRect that you're accidentally overriding.
In any case, the reason you're seeing this behaviour is because drawRect is only called when the view first comes on the screen or if it's invalidated. From the docs:
This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay or setNeedsDisplayInRect: method instead.
In your cell's setMask.. methods, try calling [self.customDrawingView setNeedsDisplay] to invalidate the drawing and force an update.
You can use this method or the setNeedsDisplayInRect: to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.
You should use this method to request that a view be redrawn only when the content or appearance of the view change. If you simply change the geometry of the view, the view is typically not redrawn. Instead, its existing content is adjusted based on the value in the view’s contentMode property. Redisplaying the existing content improves performance by avoiding the need to redraw content that has not changed.
At first, I don't think, that overriding - (void)drawRect:(CGRect)rect is the right resolution for your problem. My advice would be to override it only if you exactly know what are you doing and what are you trying to achieve.
According to images, you have provided, the problem you are facing is related with UITableViewCell reusing, but not with the drawing.
The problem. When table view scrolling hits the boundary of UITableView to present new content, UITableView doesn't create a new cell instance. It takes the currently invisible cell (the one which has been to scrolled out to present new one on screen) and reuses it (UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"DialogCell"];). In your example, lines of code executed after the cell is being reused only sets the cell content text up. From the images you provided, I see, that the text is sat up correctly. The problem is with cells background. Since there is not a single line of code to setup cells background after it is being reused, background before reuse is being presented.
Recommendations. Obviously, you need to override cells background each time it is being reused. If I were you, I would create a subclass of class UITableViewCell, lets say MyTableViewCell. The cell would have a method like:
- (void)setupWithSide:(Side)side {
if (side == SideRight) {
[self setupRightAlignedCintent];
[self setupLeftBackground];
}
}
Meanwhile, in UITableViewController you have to
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DialogCell"];
Side side;
if (indexPath.row % 2 == 0) {
side = SideRight;
} else {
side = SideLeft;
}
[cell setupWithSide:side];
return cell;
}
I am adding new rows in my table view and resizing it according to its contents, but after the resize, the cell contents, like a button and didSelectRowAtIndexPath: are not being invoked.
Here is the resizing code:
CGRect frame = cell.replayTableView.frame;
int height = (model.replyComments.count*61)+3;
frame.size = CGSizeMake(cell.replayTableView.frame.size.width, height);
cell.tableviewReplay.refTable.frame=frame;
Assign a height on heightForRowAtIndexPath
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.Row == 1)
return 50.0;
else if (indexPath.Row == 2)
return 60;
}
First if you are using auto layouts you have to update constraints also .
In your code you are getting frame from a different view
CGRect frame = cell.replayTableView.frame;
after changing height
CGRect frame = cell.replayTableView.frame;
int height = (model.replyComments.count*61)+3;
frame.size = CGSizeMake(cell.replayTableView.frame.size.width, height);
but then setting it to different view
cell.tableviewReplay.refTable.frame=frame;
This may be creating issue for you.
// Declare a variable in yourController.h CGFloat tableHeight;
// set height in heightforRowAtindex
// run loop for number of rows in ur tableview data array
// put this code when ur data array is not empty i.e after responce of webservice etc
// as your tableview is and custom cells are not created programatically , so you need viewDidLayoutSubviews to reset frames
tableHeight = 0.0f;
for (int i = 0; i < [dataArray count]; i ++) {
tableHeight += [self tableView:self.multipleArticletableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self viewDidLayoutSubviews];
// put code below in viewDidLayoutSubviews method . it will work
self.multipleArticletableView.frame = CGRectMake(self.multipleArticletableView.frame.origin.x, self.multipleArticletableView.frame.origin.y,self.multipleArticletableView.frame.size.width, tableHeight);
Most common cause for this kind of issues are cells having some UI components that intercepts the user tap and do not pass on the tap to underneath cell. You can fix this by disabling user interaction on those components.
For example if you had added a UIView in your cell then call
otherView.userInteractionEnabled = NO;
I have a tableView with dynamically generated custom cells and one of the custom cell contains a scrollView that can scroll vertically and a page control is attached to it.
The user interface structure of the tableview can be seen in the photo.
I am refreshing the position of the page control in the scrollViewDidScroll method as follows.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSIndexPath *indexPathPageControl=[NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell * cell = (UITableViewCell*)[self.sonDakikaTableView cellForRowAtIndexPath:indexPathPageControl];
CGFloat xOffset = scrollView.contentOffset.x;
CGFloat frameWidth = scrollView.frame.size.width;
int page = floor((xOffset - frameWidth / 2) / frameWidth) + 1;
UIPageControl *pageControl = (UIPageControl *)[cell.contentView viewWithTag:14];
pageControl.currentPage = page; // assigning to pagecontroll
}
It is working very fine when the user is scrolling vertical in the scrollView.
However when the user scrolls down on the tableView and the cell with vertical scrollView is not visible anymore, the integer page variable is being updated with 0 and when the user scrolls up and table view cell is visible again, the location of page control is being reset.
I will appreciate if someone can give me an idea about how to prevent this.
Thanks in advance.
i added this code
(void)scrollViewDidScroll:(UIScrollView *)_scrollView
{
if (_scrollView == self.tableView) return;
and in your case
(void)scrollViewDidScroll:(UIScrollView *)_scrollView
{
if (_scrollView == self.sonDakikaTableView) return;
I am using collection view for the first time and I'm not sure how to do something..
I have a UICollectionView with 12 cells. I set the collectView to scroll horizontally only and cells are lined up next to each other. I also turned on paging so I could use UIPageControll to indicate scrolling is active.
I want the collection view to only show four cells on the screen at any time. When the view loads, I get four cells, no problem. However when I scroll horizontally, I get 4 and a half cells. never just four.
Is there a way to tell the collection view only to show four cells at a time?
you can statically add number of cell(items)in collection view,if not require dynamic.
here I am using Scroll Direction Horizontal you can do it same way in vertical.
hope this will help
you can do this way also. Just copy this code into your view controller and make some changes.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSIndexPath *indexPath;
for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {
indexPath = [self.collectionView indexPathForCell:cell];
NSLog(#"%#",indexPath);
}
UICollectionViewCell *cell =(UICollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
//finally get the rect for the cell
CGRect cellRect = cell.frame;
self.collectionView.contentOffset = CGPointMake(self.collectionView.contentOffset.x, cellRect.origin.y);
}
As Marc said, you could simply control the size of your collection view.
If changing the size is not practical, then you can set content inset on the collection view.
CGFloat cellWidth = … // Cell width
CGFloat collectionViewWidth = … // Collection View Width
CGFloat desiredCollectionViewWidth = cellWidth * 4.0;
CGFloat horizontalInset = collectionViewWidth - desiredCollectionViewWidth;
// To center the collection view
UIEdgeInsets inset = UIEdgeInsetsMake(0, horizontalInset/2, 0, horizontalInset/2);
self.collectionView.contentInset = inset;
// Or, to left justify the collection view
UIEdgeInsets inset = UIEdgeInsetsMake(0, 0, 0, horizontalInset);
self.collectionView.contentInset = inset;