App development

UIViewController offsets scrollview subviews

I was just wondering why my Scrollview Content would always beoffset to what i set it up in Storyboard (Xcode). I use a UIViewController and changed it’s view’s class to UIScrollView.

The layout of my scrollview in Xcode Storyboard
The layout of my scrollview in Xcode Storyboard

The problem was, that all the content which i played out in Xcode was offset by the height of the toolbar during runtime. This did not happen when the view was of class UIView, as by default. I therefore tried to set the subviews frames during runtime but this seems to be restricted by the loaded nibs somehow.

Subviews have a offset in y direction with a height of the toolbar
Subviews have a offset in y direction with a height of the toolbar

for(UIView* subview in self.view.subviews){ [subview setFrame:CGRectOffset(subview.frame, 0, -[self navigationController].toolbar.frame.size.height)];}

I finally found the reason for my problem in the attributes inspector of my ViewController.

Turn "Adjust Scrollview Insets off"
Turn “Adjust Scrollview Insets off”

The Option “Adjust Scroll View Insets” must be disabled for this purpose. It’s probably something which tries to prevent the offset from being hidden by the toolbar. But using the layout in interface builder to manage your subviews obviously interferes with this option. You should therefore turn it off!

offset removed by turning off "Adjust Scrollview Insets"
offset removed by turning off “Adjust Scrollview Insets”



App development

Solution: UIScrollview that doesn’t interfere with content touches

Hi there, i’m currently working on a drawing-application which uses a scrollview to navigate threw the content. With the normal UIScrollview you will definitely run into problems!

The problem is that UIScrollView will only deliver taps but no movement gestures to it’s content.

1) So i was looking for a solution to pan and zoom with 2 fingers and interact with the content-view with one finger.

2) Also it should be possible to have a period of time in which the 1 finger touch event can be canceled by the two finger events. This is useful since people tend to start with one finger followed by the second one a little later.

3)Also it should be possible to touch the screen with a second finger without canceling the content interaction!

The idea was to filter all the touch-events, but since UIScrollView uses gesture recognizers (namely UIScrollViewPanGestureRecognizer which inherits from UIPanGestureRecognizer) you can’t filter these touches since there aren’t any but the first touchesBegan…

To solve these problems i did the following:

  1. Set setCanCancelContentTouches:NO when you detect a 1 touch event in touchesBegan: works but you still have the problem with 2).
    • Solution: Use a timer to set setCanCancelContentTouches:NO after a period like 0.1 seconds or so
      • Problem: In the first moments the gesture recognizer will interfere with your touch movements and use them instead
        • Solution: Reconfigure all pangesture recognizers to only accept 2 touch events
Note that i check the gesture recognizers against isKindOfClass not isMemberOfClass since UIScrollViewPanGestureRecognizer inherits from UIPanGestureRecognizer.
Also dont worry about touchFilter! This is the contentview, which in fact doesn’t filter the touches but uses the prefiltered ones!
When the user starts with a one finger gesture and (before timer fired) continues with a second one, beganTouches: and canceledTouches: will be called in this order. The number of touches in canceledTouches will therefore be 2; your contentview must be aware of this, cause if it ignores two touch events it won’t get the canceled event which could result in quite strange behavior.
  1. #import “JWTwoFingerScrollView.h”
  2. @implementation JWTwoFingerScrollView
  3. #pragma mark –
  4. #pragma mark Event Passing
  5. (id)initWithCoder:(NSCoder *)coder {
  6. self = [super initWithCoder:coder];
  7. if (self) {
  8. for (UIGestureRecognizer* r in self.gestureRecognizers) {
  9. NSLog(@“%@”,[r class]);
  10. if ([r isKindOfClass:[UIPanGestureRecognizer class]]) {
  11. [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2];
  12. [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2];
  13. }
  14. }
  15. }
  16. return self;
  17. }
  18. (void)firstTouchTimerFired:(NSTimer*)timer {
  19. [self setCanCancelContentTouches:NO];
  20. }
  21. (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  22. [self setCanCancelContentTouches:YES];
  23. if ([event allTouches].count == 1){
  24. touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
  25. [touchesBeganTimer retain];
  26. [touchFilter touchesBegan:touches withEvent:event];
  27. }
  28. }
  29. (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  30. [touchFilter touchesMoved:touches withEvent:event];
  31. }
  32. (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  33. NSLog(@“ended %i”,[event allTouches].count);
  34. [touchFilter touchesEnded:touches withEvent:event];
  35. }
  36. (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  37. NSLog(@“canceled %i”,[event allTouches].count);
  38. [touchFilter touchesCancelled:touches withEvent:event];
  39. }

It really took me ages to get this running, there were plenty other solutions which all didnt meet my needs. Some used a timer but delayed the first touch event, others allowed only one touch event but interrupted as soon as there was a two finger touch event. Some threads on stackoverflow might be interesting for you, since it also helped me to get this solution working!

if you want, download  the files!