I am just playing around with UITableView and I created 2 buttons.
One of the buttons will cause the table view to scroll up by an amount
while the other will cause the table view to scroll down by an amount.
Scroll up:
[self.tableView setContentOffset:CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + up) animated:YES];
up += 10;
Scroll down:
[self.tableView setContentOffset:CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + down) animated:YES];
down -= 10;
All is well and fun. But the problem is I don't know how to prevent the scroll from scrolling off screen both on top and the bottom. So far, it will keep scrolling upward or downward even showing white space, the same goes for the bottom.
I have tried using the contentSize property but it doesn't seem to work.
By not working, it means:
if(up < self.tableView.contentSize.height)
{
[self.tableView setContentOffset:CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + up) animated:YES];
up += 10;
}
The above is an attempt to disallow scrolling downward when the amount (up) is greater than the contentSize. But it doesn't seem to work.
Is there anyway I could detect the scrolling is off screen both on top and bottom? Other words, I would like to prevent it from scrolling above the first cell and for the bottom past the last cell.
Instead of keeping variables up and down in your class, and shifting the existing contentOffset by those amounts (which you then change, this doesn't make sense to me), why not keep it all local and shift by a constant amount:
- (void)buttonClicked:(id)sender
{
CGPoint offset = self.tableView.contentOffset;
CGSize visibleSize = self.tableView.bounds.size;
if (sender == btnUp)
{
offset = CGPointMake(offset.x, offset.y + 10);
if (offset.y < (self.tableView.contentSize.height - visibleSize.height))
[self.tableView setContentOffset: offset animated:YES];
}
else if (sender == btnDown) { /* you get the idea */ }
}
That part's just an idea...the real solution I think lies in including tableView.bounds in the "should I scroll" check.
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.
So I realize that having nested scroll views at all it sort of a red flag, but the current setup of everything actually works quite well besides one small problem. One scroll view manages scrolling through a collection, while another handles zooming and panning on the entire collection view. This all works, but the small problem comes from when zooming in and panning downward, the scrollview pans while the collectionview scrolls, causing the view to scroll twice as fast, and not feel connected to your finger.
What I ideally want to happen is vertical scrolling is managed by the outer scroll view when panning is possible, and then handled by the inner scroll view when the outer one can no longer pan. I got very close by writing something like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == _outerScrollView) {
CGPoint offset = _outerScrollView.contentOffset;
CGFloat height = _outerScrollView.frame.size.height;
CGFloat contentHeight = _outerScrollView.contentSize.height;
ScrollDirection scrollDirection;
if (offset > _lastContentOffset){
scrollDirection = ScrollDirectionUp;
} else {
scrollDirection = ScrollDirectionDown;
}
BOOL scrollIsAtTop = offset.y <= 0;
BOOL scrollIsAtBottom = offset.y + height >= contentHeight;
//If there is a pan upward and we aren't at the top of the outer
//scrollview cancel the gesture on the inner view
//downward vice versa
if (!((scrollIsAtTop && scrollDirection == ScrollDirectionUp)
|| (scrollIsAtBottom && scrollDirection == ScrollDirectionDown))) {
_innerCollectionView.panGestureRecognizer.enabled = NO;
_innerCollectionView.panGestureRecognizer.enabled = YES;
}
}
_lastContentOffset = offset.y;
}
This ALMOST works, with one side effect of a big pan downward stops when it hits the bottom and requires the user to start a new gesture to continue scrolling with the inner collection. Ideally this transition would be smooth, but I'm having a hard time figuring out a way to do this. Again I realize scroll view inside scroll view is not ideal, but if I can fix this small problem everything will be good, rather than attempt to redesign the whole thing.
Any ideas on how I can handle the double scroll in a way that lets the pan gesture win, but cleanly transitions to the inner collection when the outer scroll view can no longer pan vertically?
So, since I never got any answers, this is the solution I've been going with. Essentially if the inner collection view isn't at the top or bottom, I reset the y offset change the outer scroll view has in scrollViewDidScroll. Code looks like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == _outerScrollView) {
if (![self innerScrollIsAtTop] && ![self innerScrollIsAtBottom] && !self.allowOuterScroll) {
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, self.lastContentOffset)];
}
self.lastContentOffset = scrollView.contentOffset.y;
}
}
using these 2 conveniences:
- (BOOL)innerScrollIsAtTop {
return _innerCollectionView.contentOffset.y <= 0;
}
- (BOOL)innerScrollIsAtBottom {
CGFloat zoom = self.zoomScale;
CGFloat height = _innerCollectionView.frame.size.height;
CGFloat contentHeight = _innerCollectionView.contentSize.height;
CGPoint offset = _innerCollectionView.contentOffset;
return offset.y + height / zoom >= contentHeight;
}
And you'll need 2 class variables, a float to hold the previous y content offset of the outer scroll, and a BOOL to hold whether you want to allow the outer scroll view to scroll, which you can set to YES while zooming or programatically scrolling. This solution fixes the double scroll, but does have a cumbersome hack within scrollviewDidScroll that may bite you later and you constantly need to work around, but for now this is the solution I've been using.
In our app you can build a question by searching for options from multiple third party sources. Most of these search results are displayed as full-width tableview cells, as their data suits that format (has a bunch of metadata text I can display next to the thumbnail).
In the case of images however, a collection view makes much more sense. But now I run into the problem of a vertical scrolling tableview containing a vertical scrolling collection view.
http://www.screencast.com/t/7Z48zkkW
I can make it work somewhat by capturing the viewDidScroll on the collection view and updating the parent scroll view instead at the appropriate offsets, but it only works well when the user is actively dragging the collection view.
self.collectionVC.scrollViewDidScroll = ^(UIScrollView *scrollView) {
#strongify(self);
if (self.tableView.contentOffset.y < self.scrollingHeightOffset && scrollView.contentOffset.y > 0) {
CGFloat maxheight = MIN(self.scrollingHeightOffset, self.tableView.contentOffset.y + scrollView.contentOffset.y);
self.tableView.contentOffset = CGPointMake(0, maxheight);
scrollView.contentOffset = CGPointMake(0, 0);
} else if (scrollView.contentOffset.y < 0 && self.tableView.contentOffset.y > -topGuide) {
CGFloat minheight = MAX(-topGuide, self.tableView.contentOffset.y + scrollView.contentOffset.y);
self.tableView.contentOffset = CGPointMake(0, minheight);
scrollView.contentOffset = CGPointMake(0, 0);
}
};
When 'flinging' the collection view, the scrolling stops abruptly, losing the inertia of the collection view. Touching the tableview to scroll has a different problem, as I'm not capturing that when it hits the end and scrolling the collection view instead.
Right now the collection view lives in a cell of the tableview, but it could also be a peer if necessary. I'm trying to determine the best way to make these two scrollviews appear as one.
As far as I know you did the right thing. viewDidScroll should be called several times even after user lift the finger and scroll view is returning to some position.
I use exact same approach to scroll couple scrollViews in sync when user is dragging one of them. Works perfectly.
You might want to check your calculation logic inside this method. See if it calls for all changes after user lift a finger, but you're positioning other scroll view in a wrong way.
I did this, let me know if it works for you
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView1.panGestureRecognizer addTarget:self action:#selector(didRecognizePanGesture:)];
[self.tableView2.panGestureRecognizer addTarget:self action:#selector(didRecognizePanGesture:)];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (self.selectedTableView == scrollView)
{
if (scrollView == self.tableView1)
{
self.tableView2.contentOffset = scrollView.contentOffset;
}
else if (scrollView == self.tableView2)
{
self.tableView1.contentOffset = scrollView.contentOffset;
}
}
}
- (void)didRecognizePanGesture:(UIPanGestureRecognizer*)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
if (self.selectedTableView != nil) //this is gonna make stop the previous scrollView
{
[self.selectedTableView setContentOffset:self.selectedTableView.contentOffset animated:NO];
}
self.selectedTableView = (UITableView*)gesture.view;
}
}
I have a horizontal UITableView where I would like to set the paging distance. I tried this approach,
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
_tableView.decelerationRate = UIScrollViewDecelerationRateFast;
CGFloat pageSize = 320.f / 3.f;
CGPoint contentOffset = _tableView.contentOffset;
contentOffset.y = roundf(contentOffset.y / pageSize) * pageSize;
[_tableView setContentOffset:contentOffset animated:YES];
}
This works when you scroll slowly and let go but if you scroll fast, there's a lot of popping. So after wrestling with it for a bit, I'm trying a different approach...
I'm simply enabling paging on the tableView and then setting the width of the table to my desired paging size (a 3rd of the screen's width). I also set clipsToBounds = NO. When I use this approach, the scrolling works as expected but now cells outside of my smaller table width do not draw. What I would like is to force the cell on the left and right of my current cell to draw, even though they are outside of the UITableView's frame. I know cellForRowAtIndexPath get's called from a deeper level but is there some way I can trigger it myself for the cell's I selected?
I've tried using,
[[_tableView cellForRowAtIndexPath:indexPath] setNeedsDisplay];
but it does nothinggggggg!
Forgive me to the obtuse title, as I'm unsure how to describe this question.
Recently many iOS apps utilise a scrolling UI design pattern which helps to maximise screen real-estate, typically hiding the header when the user scrolls downwards.
For example, Instragram's main view has the Instragram header at the top. Scrolling upwards on this view keeps the header fixed at the top, and the view bounces back normally to the top of the content. But scroll down and the header acts as part of the content, making way for an extra 44 points of vertical space.
Its probably that I haven't done much iOS work in a while, but I can't easily figure out how best to impliment this? Apologies for the terrible description.
If the header stays put no matter what, use a separate view on top of the scroll view.
If you use UITableView, you can use section headers.
EDIT Use this code:
- (void)scrollViewDidScroll:(UIScrollView*) scrollView
{
CGPoint offset = scrollView.contentOffset;
CGRect headerFrame = _headerView.frame;
if(offset.y > 0){
headerFrame.origin.y = offset.y;
}
else{
headerFrame.origin.y = 0.0;
}
[_headerView setFrame:headerFrame];
}
(Assumes _headerView is your header, sitting on top of the scroll view, not inside it. Also, both scroll view and header begin at the top of their parent view, y==0. Also, your view controller must be set up as delegate of the scroll view)
I just wrote this code from memory; haven't tested it but at most it should only need tweaking.
I tried ranReloaded's answer above but it seems that calling setFrame: on a UIScrollView stops the view from bouncing when going beyond its bounds.
Instead I set the scroll view to fit inside another UIView called scrollerWrapper. Applying the calculated origin and height to this view gives me effect I'm after plus retains the bounce behaviour.
- (void)scrollViewDidScroll:(UIScrollView*) scrollView
{
CGPoint offset = scrollView.contentOffset;
CGRect headerFrame = header.frame;
CGRect wrapperFrame = scrollerWrapper.frame;
if(offset.y > 0){
headerFrame.origin.y = -offset.y;
wrapperFrame.origin.y = MAX(0, headerFrame.size.height - offset.y);
}
else{
headerFrame.origin.y = 0.0;
wrapperFrame.origin.y = headerFrame.size.height;
}
wrapperFrame.size.height = self.view.frame.size.height - wrapperFrame.origin.y;
[header setFrame:headerFrame];
[scrollerWrapper setFrame:wrapperFrame];
}