In iOS 4.0, why does UIScrollView zoomToRect:animated: not trigger the scrollViewDidScroll or scrollViewDidZoom delegates while animating?
Asked Answered
S

4

29

I need to closely monitor the scale of the scroll view so that I can update the content view's elements (a subview managing multiple CALayers) according to the scroll view's animated zoom.

On iOS 3.1, everything works as expected, I use zoomToRect:animated: and the scrollViewDidScroll: message of the UIScrollViewDelegate gets called repeatedly while the animation takes place, letting me update the subview elements according to actual zoom.

The same code on iOS 4.0 does not exibit the same behavior. When I call zoomToRect:animated:, the delegates (scrollViewDidScroll: and scrollViewDidZoom) only get called once, which makes my sub elements desinchronized until the animation is finished. In fact, the sub elements immediately jump and then get caught up by the zoom animation until everything is in the correct place. It's as if the animation is not picking up the modifications on the subviews CALayers.

I have tried animating manually with animation blocks, but the situation is the same, no progressive callback calls. I have also tried KVO, but it is not clear to me how I would tap into a UIScrollView-managed animation.

Is there a workaround on iOS 4 that would allow me to push the scale value to my subviews as the UIScrollView scale is animated?

Sisterhood answered 22/10, 2010 at 2:47 Comment(0)
C
10

Brutal but works.

Define some properties:

@property (nonatomic, strong) UIView *zoomedView;    
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) CGFloat currentScale;

Inform UIScrollView which UIView is going to be zoomed:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
     return self.zoomedView;
}

Register for a CADisplayLink ticks. It runs Core Animation as well, so it will be kicked on same intervals:

self.displayLink  = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

Check zoomedView's presentationLayer for current values.

- (void)displayLinkTick
{
    CALayer *zoomedLayer = self.zoomedView.layer.presentationLayer;
    CGFloat scale = zoomedLayer.transform.m11;

    if (scale != self.currentScale) {
         self.currentScale = scale; // update property

         // the scale has changed!
    }
}
Canonicals answered 19/3, 2013 at 23:8 Comment(0)
H
1

In IOS 4.0, animations are done by the OS - I assume to make use of GPU based hardware acceleration as much as possible. As a disadvantage of that, it becomes harder to animate a values that is derived from another (animated) value. As in your case, the positions of the subviews that depend on the zoom level of the UIScrollView. In order to make that happen, you should setup the animation of the subviews to go in parallel with the animation of the zooming. Try something like:

[UIView animateWithDuration:0.5 delay:0
                    options:UIViewAnimationOptionLayoutSubviews
                 animations:^{
    theScrollView.zoomScale = zoomScale;
    // layout the subviews here
} completion:^(BOOL finished) {}];

This should set the frame properties of the subviews from within the same animation context, and therefore, they should be animated together by the OS.

See also the answers to this question: How to make UIScrollView send scrollViewDidScroll messages during animations

Heckle answered 26/9, 2011 at 10:50 Comment(0)
N
-1

Is it viable to update all the subviews within an animation block (or begin/commitAnimation)? Something like:

[self zoomToRect:zoomRect animated:YES];
[UIView animateWithDuration:1.0
    animations:^ {
        // change your subviews
    }
];
Nader answered 12/11, 2010 at 15:6 Comment(2)
I have tried this approach, but the problem is that I need to access the current value of the zoomlevel while the zoomming animation is ran, which is not available.Sisterhood
Could you setup a timer that fires up several times while the zoom animation is going on and make your calculations there?Nader
H
-2

Register for KVO of the zoomScale property of the UIScrollView after you call zoomToRect:animated:.

[scrollView addObserver:self
            forKeyPath:@"zoomScale"
               options:(NSKeyValueObservingOptionNew |
                        NSKeyValueObservingOptionOld)
                context:NULL];

Then, implement this method:

- (void)observeValueForKeyPath:(NSString *)keyPath
              ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if ([keyPath isEqual:@"zoomScale"]) {
    // use the new zoomScale value stored
    // in the NSKeyValueChangeNewKey change
    // dictionary key  to resize your subviews
    }
    // be sure to call the super implementation
    // if the superclass implements it
    [super observeValueForKeyPath:keyPath
                ofObject:object
                 change:change
                 context:context];
}

Check out the KVO documentation.

Humanize answered 15/11, 2010 at 20:15 Comment(5)
Bumping a really old answer, but I can't seem to get this to work at all. I register myself as an observer of scrollView for the key path of @"zoomScale", but observeValueForKeyPath:ofObject:change:context: never gets called.Brocatel
Adam Mika, why don't you use the scrollViewDidZoom from the UIScrollViewDelegate Protocol developer.apple.com/library/ios/#documentation/UIKit/Reference/… (its iOS 3.2+ though)Rivalee
It's been a long time, but I need to downvote this because it does not work. The observeValueForKeyPath is never called. Not when the zoom ends nor while the zoom occurs.Sisterhood
does not work here either - but i would need it as scrollViewDidZoom does not get called during an animation.Heartfelt
this is completely wrong, the entire point is that you need the values during the animationRibbonwood

© 2022 - 2024 — McMap. All rights reserved.