Bounds automatically changes on UIScrollView with content insets
Asked Answered
T

5

8

I'm using a UIScrollView as my paging scroll view, pagesScrollView. Inside that, I put individual UIScrollViews which are used exclusively for zooming. Inside each of those, I have one view which is the page item which should be zoomable. All of that is inside a UINavigationController with a translucent navbar.

My pagesScrollView has contentInset.top = 64 and bounds.origin.y = -64 (that seems weird to me, but that's what the system is setting automatically for me), and this works just fine. My screen looks great!

However, after I scroll the pagesScrollView even a tiny bit, as soon as scrollViewWillEndDragging is called, the pagesScrollView begins an animated change from bounds.origin.y = -64 to bounds.origin.y = 0 which causes my page items to be obscured by the navbar.

On the left is what it looks like when it loads, on the right is what it looks like after I drag just a few pixels and then let go, it slides up under the navbar (because the bounds.origin.y goes to 0).

enter image description here

The problem is that I don't have any code that is altering the bounds and I don't have any code in the various scroll delegate methods that do anything. I've added a bunch of scroll delegate methods and just added NSLog()s so I can figure out when/where the change is happening, but it's not happening anywhere in my code.

So, I don't know what code I can show you to help you help me.

EDIT: I built a new project from scratch to remove all other variables.. I put a bare UIViewController into a UINavigationController. I put a UIScrollView into my View the entire size of the view. The following code is the entire project.

It turns out the issue (described below) only appears once PAGING IS ENABLED on the UIScrollView! Wtf? :)

Here is a link to download a basic project with only a few lines of code which demonstrates the problem. Just click in the scrollview and you'll see it shift up as the bounds change. http://inadaydevelopment.com/stackoverflow/WeirdScrollViews.zip

How can I have paging enabled on my scrollview without the bounds freaking out during scrolling and shifting everything under the nav bar?

It's possible to set the navbar to opaque and the problem is avoided, but the ideal is to have standard iOS7 behavior so that after the content view is zoomed, THEN the content is allowed to be under the navbar and should show through the translucency normally.

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    NSArray *colors = @[
                        [UIColor blueColor],
                        [UIColor orangeColor],
                        [UIColor magentaColor],
                        ];

    NSArray *zoomerColors = @[
                        [UIColor greenColor],
                        [UIColor yellowColor],
                        [UIColor purpleColor],
                        ];


    self.scroller.pagingEnabled = YES;

    [self.scroller setContentSize:CGSizeMake(self.scroller.frame.size.width*colors.count, self.scroller.frame.size.height)];

    CGRect subviewFrame = CGRectMake(0, 0, 160, 240);
    for (int index=0; index < colors.count; index++) {
        UIColor *color = [colors objectAtIndex:index];
        UIColor *zoomerColor = [zoomerColors objectAtIndex:index];

        UIView *subview = [[UIView alloc] initWithFrame:subviewFrame];
        subview.backgroundColor = color;

        CGRect zoomerFrame = CGRectMake(index*self.scroller.frame.size.width, 0, self.scroller.frame.size.width, self.scroller.frame.size.height);

        UIScrollView *zoomer = [[UIScrollView alloc] initWithFrame:zoomerFrame];
        [zoomer addSubview:subview];
        zoomer.backgroundColor = zoomerColor;

        [self.scroller addSubview:zoomer];

    }
}
Timtima answered 6/5, 2014 at 6:20 Comment(4)
if you use bounds it x and y point is 0,0 you need to use frame.Angelangela
put code of scrollViewWillEndDragging or put code of individual UIScrollViewsSingleaction
@nitinkachhadiya, I'm not changing the bounds at all, and the bounds is starting off at -64... which looks wrong to me, but that's what the system is setting on its own.Timtima
@iPatel, I've added the code you requested, but as I said, it's empty. I didn't even that code method in my class until I added it JUST so I could put an NSLog() in it.Timtima
P
11

It's an iOS bug. I created the following subclass of UIScrollView to get a log of what happens to y over time and who was pushing it:

@implementation CSScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    NSLog(@"%0.0f %@", contentOffset.y, [NSThread callStackSymbols]);
    NSLog(@"[%@]", self.layer.animationKeys);
    [super setContentOffset:contentOffset];
}

@end

(and changed the view class in the storyboard)

When you release your finger, a method called UIScrollView _smoothScrollDisplayLink: starts animating the scroll view to its final position. As per the second log, there's no CAAnimation involved, the scroll view uses its own display link to do its own transition. That custom code appears to make the mistake of animating from y = whatever to y = 0, failing to take the content offset into account.

As a proof-of-concept hack I changed the code to:

@implementation CSScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    contentOffset.y = -64.0f;
    [super setContentOffset:contentOffset];
}

@end

And, unsurprisingly, the problem went away.

You probably don't want to hard code the -64.0f but I'd conclude:

  • it's an iOS bug;
  • work around it by rejecting nonsensical values via a subclass of UIScrollView with a suitable custom implementation of - setContentOffset:.

A sensible generic means might be to check the state of self.panGestureRecognizer — that'll allow you to differentiate between scrolls the user is responsible for and other scrolls without relying on any undocumented API or complicated capturing of delegate events. Then if necessary crib the correct contentOffset.y from the current value rather than hardcoding it.

Peonage answered 10/5, 2014 at 0:4 Comment(1)
This is great! I couldn't figure out what was wrong, although I did have a suspicion it was a bug in iOS 8. Works perfectly with your solution, thanksIntrastate
T
16

Just switch off Adjust Scroll View Insets

enter image description here

Transmittance answered 12/5, 2014 at 11:43 Comment(0)
P
11

It's an iOS bug. I created the following subclass of UIScrollView to get a log of what happens to y over time and who was pushing it:

@implementation CSScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    NSLog(@"%0.0f %@", contentOffset.y, [NSThread callStackSymbols]);
    NSLog(@"[%@]", self.layer.animationKeys);
    [super setContentOffset:contentOffset];
}

@end

(and changed the view class in the storyboard)

When you release your finger, a method called UIScrollView _smoothScrollDisplayLink: starts animating the scroll view to its final position. As per the second log, there's no CAAnimation involved, the scroll view uses its own display link to do its own transition. That custom code appears to make the mistake of animating from y = whatever to y = 0, failing to take the content offset into account.

As a proof-of-concept hack I changed the code to:

@implementation CSScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    contentOffset.y = -64.0f;
    [super setContentOffset:contentOffset];
}

@end

And, unsurprisingly, the problem went away.

You probably don't want to hard code the -64.0f but I'd conclude:

  • it's an iOS bug;
  • work around it by rejecting nonsensical values via a subclass of UIScrollView with a suitable custom implementation of - setContentOffset:.

A sensible generic means might be to check the state of self.panGestureRecognizer — that'll allow you to differentiate between scrolls the user is responsible for and other scrolls without relying on any undocumented API or complicated capturing of delegate events. Then if necessary crib the correct contentOffset.y from the current value rather than hardcoding it.

Peonage answered 10/5, 2014 at 0:4 Comment(1)
This is great! I couldn't figure out what was wrong, although I did have a suspicion it was a bug in iOS 8. Works perfectly with your solution, thanksIntrastate
H
0

My pagesScrollView has contentInset.top = 64 and bounds.origin.y = -64 (that seems weird to me, but that's what the system is setting automatically for me), and this works just fine. My screen looks great!

It because of iOS 7 sets contentInset.top to 64 on all scrollviews. Just add this line of code into your view controller and all will work as expected:

-(UIRectEdge)edgesForExtendedLayout {
return UIRectEdgeNone;

}

I checked on your example project.

Hyperbola answered 9/5, 2014 at 15:56 Comment(1)
Thanks for the response. Unfortunately, this answer just avoids the problem instead of truly solving it. When the content view is zoomed and panned, the content should ideally be under the navbar at THAT point and showing through in a standard iOS7 fashion. I can make the navbar opaque and avoid the problem at well, but that's not truly the ideal. This may actually be an iOS bug.Timtima
H
0

I have checked you example use below code in viewController.m file

-(void)viewDidLoad
{
    if ([[UIDevice currentDevice] systemVersion].floatValue>=7.0) {
        self.edgesForExtendedLayout = UIRectEdgeNone;
    }
}

It's working fine...

Hallow answered 12/5, 2014 at 11:55 Comment(1)
It does prevent the bug from showing up, but that's because it disables the feature that is core to the ui of iOS7. It should ALSO behave properly when the UI is extended.Timtima
I
0

It turns out the issue (described below) only appears once PAGING IS ENABLED on the UIScrollView! Wtf? :)

As you said that, If you enable the scroll paging, the UIScrollView will stop at a paging edge after a dragging or any movement, which is promised by the framework. Bounds.origin.y set by zero means that the first page edge matched the scroll view frame edge, cuz you have 64 contentInsets there. So that's not bug, that is what it is. And since your bar is translucent, remember where is your scroll view's frame edge, it's under the bar. In a word, this is not a bug, I think, but a effect of scroll paging.

Iconium answered 14/5, 2020 at 6:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.