Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 8841931
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: June 14, 20262026-06-14T10:50:22+00:00 2026-06-14T10:50:22+00:00

Typically, the scrollView’s content view is a rectangle. But I would like to implement

  • 0

Typically, the scrollView’s content view is a rectangle. But I would like to implement that is not a rectangle…. For example….
enter image description here

The yellow, Grid 6 is the current position…Here is the example flow:

  1. User swipe to left. (cannot scroll to left) Current: 6.
  2. User swipe to right. (scroll to right) Current: 7.
  3. User swipe to down. (scroll to down) Current: 8.
  4. User swipe to down. (cannot scroll to down) Current: 8.

As you can see, the Content view of the scrollView is not rectangle. Any ideas on how to implement it? Thanks.

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-06-14T10:50:23+00:00Added an answer on June 14, 2026 at 10:50 am

    This is an interesting idea to implement. I can think of a few approaches that might work. I tried out one, and you can find my implementation in my github repository here. Download it and try it out for yourself.

    My approach is to use a normal UIScrollView, and constrain its contentOffset in the delegate’s scrollViewDidScroll: method (and a few other delegate methods).

    Preliminaries

    First, we’re going to need a constant for the page size:

    static const CGSize kPageSize = { 200, 300 };
    

    And we’re going to need a data structure to hold the current x/y position in the grid of pages:

    typedef struct {
        int x;
        int y;
    } MapPosition;
    

    We need to declare that our view controller conforms to the UIScrollViewDelegate protocol:

    @interface ViewController () <UIScrollViewDelegate>
    
    @end
    

    And we’re going to need instance variables to hold the grid (map) of pages, the current position in that grid, and the scroll view:

    @implementation ViewController {
        NSArray *map_;
        MapPosition mapPosition_;
        UIScrollView *scrollView_;
    }
    

    Initializing the map

    My map is just an array of arrays, with a string name for each accessible page and [NSNull null] at inaccessible grid positions. I’ll initialize the map from my view controller’s init method:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
            [self initMap];
        }
        return self;
    }
    
    - (void)initMap {
        NSNull *null = [NSNull null];
        map_ = @[
        @[ @"1", null, @"2"],
        @[ @"3", @"4", @"5" ],
        @[ null, @"6", @"7" ],
        @[ null, null, @"8" ],
        ];
        mapPosition_ = (MapPosition){ 0, 0 };
    }
    

    Setting up the view hierarchy

    My view hierarchy will look like this:

    • top-level view (gray background)
      • scroll view (transparent background)
        • content view (tan background)
          • page 1 view (white with a shadow)
          • page 2 view (white with a shadow)
          • page 3 view (white with a shadow)
          • etc.

    Normally I’d set up some of my views in a xib, but since it’s hard to show xibs in a stackoverflow answer, I’ll do it all in code. So in my loadView method, I first set up a “content view” that will live inside the scroll view. The content view will contain a subviews for each page:

    - (void)loadView {
        UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [map_[0] count] * kPageSize.width, map_.count * kPageSize.height)];
        contentView.backgroundColor = [UIColor colorWithHue:0.1 saturation:0.1 brightness:0.9 alpha:1];
        [self addPageViewsToContentView:contentView];
    

    Then I’ll create my scroll view:

        scrollView_ = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, kPageSize.width, kPageSize.height)];
        scrollView_.delegate = self;
        scrollView_.bounces = NO;
        scrollView_.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin
                                        | UIViewAutoresizingFlexibleRightMargin
                                        | UIViewAutoresizingFlexibleTopMargin
                                        | UIViewAutoresizingFlexibleBottomMargin);
    

    I add the content view as a subview of the scroll view and set up the scroll view’s content size and offset:

        scrollView_.contentSize = contentView.frame.size;
        [scrollView_ addSubview:contentView];
        scrollView_.contentOffset = [self contentOffsetForCurrentMapPosition];
    

    Finally, I create my top-level view and give it the scroll view as a subview:

        UIView *myView = [[UIView alloc] initWithFrame:scrollView_.frame];
        [myView addSubview:scrollView_];
        myView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1];
        self.view = myView;
    }
    

    Here’s how I compute the scroll view’s content offset for the current map position, and for any map position:

    - (CGPoint)contentOffsetForCurrentMapPosition {
        return [self contentOffsetForMapPosition:mapPosition_];
    }
    
    - (CGPoint)contentOffsetForMapPosition:(MapPosition)position {
        return CGPointMake(position.x * kPageSize.width, position.y * kPageSize.height);
    }
    

    To create subviews of the content view for each accessible page, I loop over the map:

    - (void)addPageViewsToContentView:(UIView *)contentView {
        for (int y = 0, yMax = map_.count; y < yMax; ++y) {
            NSArray *mapRow = map_[y];
            for (int x = 0, xMax = mapRow.count; x < xMax; ++x) {
                id page = mapRow[x];
                if (![page isKindOfClass:[NSNull class]]) {
                    [self addPageViewForPage:page x:x y:y toContentView:contentView];
                }
            }
        }
    }
    

    And here’s how I create each page view:

    - (void)addPageViewForPage:(NSString *)page x:(int)x y:(int)y toContentView:(UIView *)contentView {
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectInset(CGRectMake(x * kPageSize.width, y * kPageSize.height, kPageSize.width, kPageSize.height), 10, 10)];
        label.text = page;
        label.textAlignment = NSTextAlignmentCenter;
        label.layer.shadowOffset = CGSizeMake(0, 2);
        label.layer.shadowRadius = 2;
        label.layer.shadowOpacity = 0.3;
        label.layer.shadowPath = [UIBezierPath bezierPathWithRect:label.bounds].CGPath;
        label.clipsToBounds = NO;
        [contentView addSubview:label];
    }
    

    Constraining the scroll view’s contentOffset

    As the user moves his finger around, I want to prevent the scroll view from showing an area of its content that doesn’t contain a page. Whenever the scroll view scrolls (by updating its contentOffset), it sends scrollViewDidScroll: to its delegate, so I can implement scrollViewDidScroll: to reset the contentOffset if it goes out of bounds:

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        CGPoint contentOffset = scrollView_.contentOffset;
    

    First, I want to constrain contentOffset so the user can only scroll horizontally or vertically, not diagonally:

        CGPoint constrainedContentOffset = [self contentOffsetByConstrainingMovementToOneDimension:contentOffset];
    

    Next, I want to constrain contentOffset so that it only shows parts of the scroll view that contain pages:

        constrainedContentOffset = [self contentOffsetByConstrainingToAccessiblePoint:constrainedContentOffset];
    

    If my constraints modified contentOffset, I need to tell the scroll view about it:

        if (!CGPointEqualToPoint(contentOffset, constrainedContentOffset)) {
            scrollView_.contentOffset = constrainedContentOffset;
        }
    

    Finally, I update my idea of the current map position based on the (constrained) contentOffset:

        mapPosition_ = [self mapPositionForContentOffset:constrainedContentOffset];
    }
    

    Here’s how I compute the map position for a given contentOffset:

    - (MapPosition)mapPositionForContentOffset:(CGPoint)contentOffset {
        return (MapPosition){ roundf(contentOffset.x / kPageSize.width),
            roundf(contentOffset.y / kPageSize.height) };
    }
    

    Here’s how I constrain the movement to just horizontal or vertical and prevent diagonal movement:

    - (CGPoint)contentOffsetByConstrainingMovementToOneDimension:(CGPoint)contentOffset {
        CGPoint baseContentOffset = [self contentOffsetForCurrentMapPosition];
        CGFloat dx = contentOffset.x - baseContentOffset.x;
        CGFloat dy = contentOffset.y - baseContentOffset.y;
        if (fabsf(dx) < fabsf(dy)) {
            contentOffset.x = baseContentOffset.x;
        } else {
            contentOffset.y = baseContentOffset.y;
        }
        return contentOffset;
    }
    

    Here’s how I constrain contentOffset to only go where there are pages:

    - (CGPoint)contentOffsetByConstrainingToAccessiblePoint:(CGPoint)contentOffset {
        return [self isAccessiblePoint:contentOffset]
            ? contentOffset
            : [self contentOffsetForCurrentMapPosition];
    }
    

    Deciding whether a point is accessible turns out to be the tricky bit. It’s not enough to just round the point’s coordinates to the nearest potential page center and see if that rounded point represents an actual page. That would, for example, let the user drag left/scroll right from page 1, revealing the empty space between pages 1 and 2, until page 1 is half off the screen. We need to round the point down and up to potential page centers, and see if both rounded points represent valid pages. Here’s how:

    - (BOOL)isAccessiblePoint:(CGPoint)point {
        CGFloat x = point.x / kPageSize.width;
        CGFloat y = point.y / kPageSize.height;
        return [self isAccessibleMapPosition:(MapPosition){ floorf(x), floorf(y) }]
            && [self isAccessibleMapPosition:(MapPosition){ ceilf(x), ceilf(y) }];
    }
    

    Checking whether a map position is accessible means checking that it’s in the bounds of the grid and that there’s actually a page at that position:

    - (BOOL)isAccessibleMapPosition:(MapPosition)p {
        if (p.y < 0 || p.y >= map_.count)
            return NO;
        NSArray *mapRow = map_[p.y];
        if (p.x < 0 || p.x >= mapRow.count)
            return NO;
        return ![mapRow[p.x] isKindOfClass:[NSNull class]];
    }
    

    Forcing the scroll view to rest at page boundaries

    If you don’t need to force the scroll view to rest at page boundaries, you can skip the rest of this. Everything I described above will work without the rest of this.

    I tried setting pagingEnabled on the scroll view to force it to come to rest at page boundaries, but it didn’t work reliably, so I have to enforce it by implementing more delegate methods.

    We’ll need a couple of utility functions. The first function just takes a CGFloat and returns 1 if it’s positive and -1 otherwise:

    static int sign(CGFloat value) {
        return value > 0 ? 1 : -1;
    }
    

    The second function takes a velocity. It returns 0 if the absolute value of the velocity is below a threshold. Otherwise, it returns the sign of the velocity:

    static int directionForVelocity(CGFloat velocity) {
        static const CGFloat kVelocityThreshold = 0.1;
        return fabsf(velocity) < kVelocityThreshold ? 0 : sign(velocity);
    }
    

    Now I can implement one of the delegate methods that the scroll view calls when the user stops dragging. In this method, I set the targetContentOffset of the scroll view to the nearest page boundary in the direction that the user was scrolling:

    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
        if (fabsf(velocity.x) > fabsf(velocity.y)) {
            *targetContentOffset = [self contentOffsetForPageInHorizontalDirection:directionForVelocity(velocity.x)];
        } else {
            *targetContentOffset = [self contentOffsetForPageInVerticalDirection:directionForVelocity(velocity.y)];
        }
    }
    

    Here’s how I find the nearest page boundary in a horizontal direction. It relies on the isAccessibleMapPosition: method, which I already defined earlier for use by scrollViewDidScroll::

    - (CGPoint)contentOffsetForPageInHorizontalDirection:(int)direction {
        MapPosition newPosition = (MapPosition){ mapPosition_.x + direction, mapPosition_.y };
        return [self isAccessibleMapPosition:newPosition] ? [self contentOffsetForMapPosition:newPosition] : [self contentOffsetForCurrentMapPosition];
    }
    

    And here’s how I find the nearest page boundary in a vertical direction:

    - (CGPoint)contentOffsetForPageInVerticalDirection:(int)direction {
        MapPosition newPosition = (MapPosition){ mapPosition_.x, mapPosition_.y + direction };
        return [self isAccessibleMapPosition:newPosition] ? [self contentOffsetForMapPosition:newPosition] : [self contentOffsetForCurrentMapPosition];
    }
    

    I discovered in testing that setting targetContentOffset did not reliably force the scroll view to come to rest on a page boundary. For example, in the iOS 5 simulator, I could drag right/scroll left from page 5, stopping halfway to page 4, and even though I was setting targetContentOffset to page 4’s boundary, the scroll view would just stop scrolling with the 4/5 boundary in the middle of the screen.

    To work around this bug, we have to implement two more UIScrollViewDelegate methods. This one is called when the touch ends:

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        if (!decelerate) {
            [scrollView_ setContentOffset:[self contentOffsetForCurrentMapPosition] animated:YES];
        }
    }
    

    And this one is called when the scroll view stops decelerating:

    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        CGPoint goodContentOffset = [self contentOffsetForCurrentMapPosition];
        if (!CGPointEqualToPoint(scrollView_.contentOffset, goodContentOffset)) {
            [scrollView_ setContentOffset:goodContentOffset animated:YES];
        }
    }
    

    The End

    As I said at the beginning, you can download my test implementation from my github repository and try it out for yourself.

    That’s all, folks!

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I would like to create a scrollview inside of another view. I put a
Typically you start a service like this Intent i = new Intent(context,MessageService.class); context.startService(i); but
Typically I implement classes (C#, C++) via many private functions that serve no purpose
Typically, in PowerShell you would use env:VARIABLE = Some kind of value But my
Typically interfaces that let you add listeners also include a remove method something like
Typically, I would call another class method like this: MyClass *class = [[MyClass alloc]
Typically, I've seen people use the class literal like this: Class<Foo> cls = Foo.class;
Typically views appear to output anchors without 'title' attributes. How would you add 'title'
I have a webview displayed in a scrollview and flip view and need to
I am following Apple's ScrollSuite example, however pinch to zoom is not working. I

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.