4
votes

I have a UIScrollView which I'm using to represent an axis on a graph. I'd like the user to be able to zoom in on the axis using the usual pinch motion, but for it to only scale in the vertical direction, not horizontally.

My question is similar to this one, but I've tried the solution suggested there (overriding the subview's SetTransform method so that it ignores scaling in one direction) and it works perfectly when constraining scaling horizontally, but not vertically. When I try implementing it vertically the first pinch action works fine, but subsequent pinches seem to reset the zoom scale to one before having any effect.

Does anyone know what might be causing this behaviour, and more importantly how I can get around it?

I'm using MonoTouch but answers using Objective-C are fine.

4
Instead of abusing UIScrollView you should maybe add a pinch gesture recognizer to your view and zoom yourself using a scaling translation. When zomming is done, update your view (you will have to re-render it anyway if it is a graph), resize the UIScrollView's content size and you're don.Krumelur
Also post some code, because the problem may be in how you are putting things togetherQED
@Krumelur I don't think this is an abuse of UIScrollView. I'd guess a scroll view is the right way to do this, the momentum physics and all that in UIScrollView make it a mistake to roll things yourself, but but I don't know how to do it. Perhaps watch some WWDC session videos (for every year of the conference, since they don't repeat previously covered topics).Abhi Beckert

4 Answers

2
votes

I know this question was posted quite a while ago, but here's an answer for anyone stuck on this problem.

I looked over the question you linked to, rankAmateur, and I think the simple way to fix the solution found there to suit your needs is to replace the CGAffineTransform's "a" property with its "d" property in the setTransform: method.

- (void)setTransform:(CGAffineTransform)newValue;
{
 CGAffineTransform constrainedTransform = CGAffineTransformIdentity;
 // constrainedTransform.a = newValue.a;
 constrainedTransform.d = newValue.d;
 [super setTransform:constrainedTransform];
}

I'm not very well versed in CGAffineTransorm, but this worked for me and after browsing the documentation it seems the "a" property corresponds to a view's x-axis and the "d" property corresponds to a view's y-axis.

EDIT

So after going back and realizing what the question really was, I did some more digging into this and I'm a bit stumped, but having experienced the same behavior that rankAmateur mentions above, it seems incredibly unusual for the CGAffineTransform to work perfectly well with zoomScale when zooming is constrained to only horizontally, but not when constrained to only vertically.

The only hypothesis I can offer, is that it might have something to do with the differing default coordinate systems of Core Graphics and UIKit, since in those coordinate systems the x-axis functions in the same way, while the y-axis functions oppositely. Perhaps somehow this gets muddled up in the previously mentioned overriding of setTransform.

0
votes

After struggling with the same issue, I was able to come up with a workaround. Use this code for the setTransform method.

-(void)setTransform:(CGAffineTransform)transform
{
    CGAffineTransform constrainedTransform = CGAffineTransformIdentity;
    constrainedTransform.d = self.initialScale * transform.d;
    constrainedTransform.d = (constrainedTransform.d < MINIMUM_ZOOM_SCALE) ? MINIMUM_ZOOM_SCALE : constrainedTransform.d;
    
    [super setTransform:constrainedTransform];
}
                                                                          

Set the initialScale property from within the scrollViewWillBeginZooming delegate method.

0
votes

This answer depends heavily on the answer from starryVere (thumbs up!)

this is starryVere's code in Swift. It is in the zoomed UIView subclass:

var initialScale: CGFloat = 1.0
override var transform: CGAffineTransform {
    set{
        //print("1 transform... \(newValue), frame=\(self.frame), bounds=\(self.bounds)")
        var constrainedTransform = CGAffineTransformIdentity
        constrainedTransform.d = self.initialScale * newValue.d // vertical zoom
        //constrainedTransform.a = newValue.a // horizontal zoom
        super.transform = constrainedTransform
        //print("2 transform... \(constrainedTransform), frame=\(self.frame), bounds=\(self.bounds)")
    }
    get{
        return super.transform
    }
}

The commented out prints are very helpful to understand what happens with bounds and frame during the transformation.

Now to the scale problem:

the method scrollViewDidEndZooming of the containing UIScrollViewDelegate has a parameter scale. According to my tests this parameter scale contains the value zoomedView.transform.a which is the horizontal scale factor that we set to 1.0 using CGAffineTransformIdentity. So scale is always 1.0.

The fix is easy:

func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
    let myScale = zoomView.transform.d

}

use myScale like you would use scale in cases with horizontal zoom.

-2
votes

It will be more helpful if you provide a sample code of what you are trying, but i am giving you some lines to try. Actually you have to make the content size width equal to "320" i.e. equal to the the screen size of iPhone.

scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 45,320,480)];   
scrollView.contentSize = CGSizeMake(320,1000);
scrollView.showsVerticalScrollIndicator = YES;

The MonoTouch version follows:

scrollView = new UIScrollView (new RectangleF (0, 45, 320, 480)) {
    ContentSize = new SizeF (320, 1000),
    ShowVerticalScrollIndicator = true
};

Hope it helps.. :) and Yes dont forget to accept the answer if it helps :D