Search code examples
iphonetextuiscrollviewdrawingcgaffinetransform

How can I redraw zoomed text on a transformed UIView?


I'm currently implementing an expanding timeline. When I pinch zoom into the timeline, I need my drawn text to stay at the same relative locations on the UIView they're drawn on inside the UIScrollView that handles the zooming. (Essentially like pins on GoogleMaps) However, I don't want to zoom vertically, so I apply a transform by overriding:

- (void)setTransform:(CGAffineTransform)newValue;
{
    newValue.d = 1.0;
    [super setTransform:newValue];
}

This works great in keeping the timeline fixed vertically and allowing it to expand horizontally. However, I am drawing my text labels as such in a method called during setNeedsDisplay:

for (int i = 1; i < 11; i++)
{
    CGRect newFrame = CGRectMake(i * (512.0/11.0) - (512.0/11.0/2.0), self.frame.size.height - 16.0, 512.0/11.0, 32.0);
    NSString *label = [NSString stringWithFormat:@"%d", i+1];
    [label drawInRect:newFrame withFont:[UIFont systemFontOfSize:14.0]];
}

This draws my text at the correct position in the scrollview, and nearly works perfectly. However, because of my transform to keep the zooming view static vertically, the text expands horizontally and not vertically, and so stretches out horribly. I can't seem to get the text to redraw at the correct aspect ratio. Using UILabels works, however I am going to be rendering and manipulating upwards of 1,000 such labels, so I'd preferably like to draw static images in drawRect or something similar.

I've tried changing the CGRect I'm drawing the text in (was worth a shot), and applying CGAffineTransformIdentity isn't possible because I'm already transforming the view to keep it from zooming vertically. I've also tried drawing the text in various Views to no avail, and again, I'd rather not populate an obscene amount of objects if I can avoid it.

Thanks for any help!


Solution

  • Instead of applying a transform inside the 'setTransform:' method, I intercepts the scale at which it is being transformed, and resize the frame of the view being transformed. The code (roughly) follows:

    - (void)setTransform:(CGAffineTransform)newValue;
    {   
        // The 'a' value of the transform is the transform's new scale of the view, which is reset after the zooming action completes
        //  newZoomScale should therefore be kept while zooming, and then zoomScale should be updated upon completion
        _newZoomScale = _zoomScale * newValue.a;
    
        if (_newZoomScale < 1.0)
            _newZoomScale = 1.0;
    
        // Resize self
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _originalFrame.size.width * _newZoomScale, self.frame.size.height);
    }
    

    As mentioned in the comments, the transform value of the CGAffineTransform is reset each time a new zooming action occurs (however, it is kept for the duration of the zooming action). So, I keep two instance variables in my UIView subclass (not sure if it's incredibly elegant, but it's not insanely terrible): the original frame the the view was instantiated with, and the "current" zoom scale of the view (prior to the current zooming action).

    The _originalFrame is what is referenced in order to determine the proper sizing of the now zoomed frame, and the _zoomScale(the scale of the view prior to the current zooming action) is set to the value of _newZoomScale when the didFinishZooming callback is called in the UIScrollView containing this UIView.

    All of this allows for the coordinate system of the UIView to not be transformed during zooming, so text, etc. may be drawn on the view without any distortion. Looking back at this solution, I'd wager a guess that you could also account for the transform and draw based on a stretched coordinate system. Not sure which is more effective. I had a slight concern by not calling super in setTransform:, but I haven't noticed any ill effects after about 6 months of use and development.