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!
Related
I use scrollToItemAtIndexPath:atScrollPosition:animated to scroll UICollectionView. I have to disable some functionality while this automatic scrolling is happening and then re-enable it, when scrolling ends. The problem occurs, when I try to scroll to some cell which is already visible and there is no scroll necessary (for example, it's the last cell and collection view is already scrolled until the very end). In this case scrollViewDidEndDragging nor scrollViewDidEndDecelerating are called.
Is there a way to know if scrollToItemAtIndexPath will actually scroll UICollectionView?
I Think you can save the latest scroll view offset & track changes in it every time the scrollView scrolls & apply actions after significant changes only.
func scrollViewDidScroll(scrollView: UIScrollView) {
let scrollOffset = scrollView.contentOffset.y;
if scrollOffset < (previousValue-100) || scrollOffset > (previousValue+100)
{
// Scrolled up or down with 100 points which you can change
previousValue = scrollOffset
}
}
You can determine whether scrollToItemAtIndexPath will scroll based on the cell's frame before calling scrollToItemAtIndexPath. For example, if you just want the cell to be visible (UICollectionViewScrollPositionNone):
UICollectionViewLayoutAttributes* attrs = [collectionView layoutAttributesForItemAtIndexPath:indexPath];
CGRect frame = [collectionView convertRect:attrs.frame toView:self.view];
BOOL willScroll = !CGRectContainsRect(collectionView.frame, frame);
It's unfortunate that scrollToItemAtIndexPath doesn't simply tell you whether it will scroll.
I have a UITableView with a UIView on top. I want the UIView to stick to the top as the tableView cells scroll over it.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (self.tableView.contentOffset.y > 0) {
CGRect newframe = self.publicTopView.frame;
newframe.origin.y = -self.tableView.contentOffset.y;
self.publicTopView.frame = newframe;
NSLog(#"After: %f", self.publicTopView.frame.origin.y);
}
}
You need to set your table view header view to the view you want on top.
Add this code to you viewDidLoad
self.tableView.tableHeaderView = self.publicTopView
I'm not certain what you're trying to accomplish, but I have a guess at what is wrong. As you scroll your contentOffset will continue to change and let's say your tableView has a content size height of 1500, then your contentOffset will eventually be larger than the height of your view controllers view. Now see that you are putting that contentOffset into the origin.y of your publicTopView. So your publicTopView could possibly be moving too much, even offscreen depending on how large your tableview's content size is.
How can I have a UITableView that permits scrolling above a certain index row, but prevents it when it below a certain point? For example, if I have rows 1 through 100, where only 5 appear in the view at a given time, I want to allow a user to scroll among rows 1-50, but prevent any further scrolling down when row 50 is visible.
You can use the property contentInset of UITableView just for that. Just remember to set them with minus values, cause we are shrinking the content size:
CGFloat insetTop = topAllowedRow * c_CELL_HEIGHT * -1;
CGFloat insetBottom = bottomAllowedRow * c_CELL_HEIGHT * -1;
[self.tableView setContentInset:UIEdgeInsetsMake(insetTop, 0, insetBottom, 0)];
The other solution is to implement UIScrollViewDelegate method scrollViewDidScroll: and when scrollView.contentOffset is too huge, set it back to the max value that user can scroll to:
- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
CGPoint scrollViewOffset = scrollView.contentOffset;
if (scrollViewOffset.x > MAX_VALUE) {
scrollViewOffset.x = MAX_VALUE;
}
[scrollView setContentOffset:scrollViewOffset];
}
First solution has both the advantage and disadvantage since then UIScrollView will manage the bouncing (just like in pull to refresh). It's more natural and HIG-compatible, but if you really need user not to see below the certain row, use delegate method.
UITableView is a subclass of UIScrollView. This means you can use all methods from UIScrollViewDelegate. Try adding the scrollView.contentOffset logic in the following delegate method:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
There is a button at the bottom of my view controller. When the user scrolls down the button has to be attached to the scrollview at certain height.
I need to attach a button to the scrollview, immediately when the contentOffset.y reaches a particular value. -(void) scrollviewDidScroll doesn't help me as there might be a jump in contentOffset when the user is scrolling fast. Any leads on this are helpful.
Also, whenever I add a subview to the scrollview, -(void) viewDidLayoutSubviews is called. Which in turn sets the contentOffset to {0,0}. How can I achieve the functionality I need?
I needed to do the same thing with a UITableView and for me using scrollViewDidScroll worked.
I created a view called staticBar and added it as a subview of the tableView, but I had to rearrange the tableview subviews for it to appear in the right place. I don't have my code in front of me, but in -scrollViewDidScroll: it looked something like this:
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
CGFloat staticBarAdjustedY = _staticBarY - scrollView.contentOffset.y;
CGFloat scrollViewYFloor = scrollView.frame.size.height - _staticBar.frame.size.height;
// This way maximum Y the view can have is at the base of the scrollView
CGFloat newY = MIN( staticBarAdjustedY, scrollViewYFloor);
_staticBar.frame = (CGRect){ { _staticBar.frame.origin.x, newY}, _staticBar.frame.size}
}
I will check my code later today and add more details here.
Also, you said the scrollviewDidScroll has jumps in contentOffset, but it's worth mentioning that these jumps are the same that the scrollView uses to scroll its own view. So it's not like you are "losing" frames on this delegate method.
Hope it helps.
PS: So, here is the rest of my code.
//I place my custom view as a subview of the tableView below it's last subview
//The last subview is for scroll indicators.
WTButtonsBar *buttonBar = [[WTButtonsBar alloc] init];
[self.tableView insertSubview:buttonBar belowSubview:self.tableView.subviews.lastObject];
In scrollViewDidScroll:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//In my app I needed my view to stick to the top of the screen
//thats why I use MAX here
//self.buttonsBarOriginalY is the view's position in the scrollView when it isn't attached to the top.
CGFloat newY = MAX(scrollView.contentOffset.y, self.buttonsBarOriginalY)
[_buttonsBar setFrame:(CGRect){{0, newY}, _buttonsBar.frame.size}];
}
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];
}