Zooming with a CATiledLayer in a UIScrollView - iphone

I'm implementing a map application similar to the maps app and I'm try to add zooming. I have multiple detail levels, e.g. 6, and I want to use the appropriate level for the current zoom scale. I'm using a CATiledLayer in a UIScrollView. So far I can set the min/max zoom in the UIScrollView, drawLayer gets called and I draw the appropriate tile, but this only uses my first map level. Now I want to set levelsOfDetail in the CATiledLayer so I can use the appropriate detail level.
My question is, how do I know at what level I should draw? The tileSize of CATiledLayer is always the same, and so is the clipping rect. I can see when drawLayer gets called for the new level of detail, but how to tell what level that is.

Set the levelsOfDetail and Bias up front. Then when called to draw, look at the frame of the view being zoomed by the scroll view, and compare it against the bounds. That should give you the information needed to determine what you draw. Also look at the clip path bounds to see "where" the CATiledLayer is working on next.

Related

UIScrollView pinch zoom into CGContextStrokePath without changing line width

I have a UIScrollView that contains a custom UIView. In my UIView I am overriding drawRect to draw a path using CGContextStrokePath. I would like to slightly alter the way the zoom works. Pinch zooming out will show more of the paths on the screen. This is what I want but i want the line width to stay the same not shrink as you zoom out so that they are still clear to the user.
I thought I would just do this (myUIView zoom target is called _lineView)
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView
{
_lineView.zoomScale = _scrollView.zoomScale;
[_lineView setNeedsDisplay];
}
and then just calculate an appropriate line stroke size in my _lineView drawRect method to give the effect of constant line width as you zoom out.
This is really slow and I have read that this is expected as drawrect is not optimised to be called many times a second.
I then started looking at using a GLKView instead and just rendering the whole thing in opengl directly. The problem with this is I will have to implement all of the zooming and panning myself (with all the lovely zoom and pan bounce effects you get for free in UiScrollView). I will also have to implement all the controls I want to use in opengl, buttons etc.
Is there a way to do this whilst still using Quartz2d? I feel like opengl will give me lots of power but it will take me much longer to get the rest of my app done if I go down that route.
I figured this out. I found a simple way to do this that does not slow down the zoom / bouncing animations at all and is very fast and fluid.
Quartz has a class called CAShapeLayer that lets you do some pretty cool stuff. Among these is being able to set a CGPath property and specify a linewidth. Changes are reflected in the view.
So i basically call shapeLayer.lineWidth from my scrollViewDidZoom method and it does exactly what i need.
What I would do if I were you is to use default zooming behavior while zooming (which does not redraw, but instead just applies a transform to the zoomed view, which can be done by the GPU very, very quickly, but as you have noticed can lead to inferior quality).
Then, when the user finished zooming, redraw the whole view as you do now. The appropriate delegate method is scrollViewDidEndZooming:withView:atScale:.
This way, you have fast (but slightly ugly) zooming, and nice (but slightly, probably unnoticeably, slower) display after the zoom is finished.

UIScrollView with multiple zoom levels?

Is there a way that I can control which subviews of a UIScrollView are scaled and which are not?
For example, I have a map as the bottom layer (not MapKit, just a flat image) which can be zoomed and panned. Depending on user selections, markers are dropped on the map to indicate specific buildings/places etc. These markers are are also UIImageView, using pixel co-ordinates of the map image (eq Building X is at (934, 842), marker is placed here). Zoomed out however these markers are somewhat difficult to see as they also scale/zoom out to the same level as the map.
So is there a way that I can tell the UIScrollView NOT to scale the marker images, but still allow it to pan/reposition them when the map image is zoomed?
I think the method you may be looking for is the UIScrollViewDelegate's
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
This lets you return the view which you want the zoom to be applied to, in your case this will be the background map image.
If you don't want the markers to be scaled at the same time, you will need to add the markers directly as subviews of the UIScrollView, instead of adding them as subviews of your background map UIImageView.
As you mentioned, the markers are placed relatively to the map background UIImageView, this means if you add the markers to the UIScrollView instead, they won't be placed in the same position on the map once you change the scroll.
To solve this, you will need to calculate the equivalent coordinates relative to the UIScrollView.
While this may seem rather daunting, it is actually quite trivial to do once you know the zoom factor.
To get the zoom factor, use the UIScrollViewDelegate method
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
Then you can get the zoom factor by calling :
scrollView.zoomScale
and recalculate the marker coordinates by applying this zoomScale to the original values, and reposition your markers accordingly.
Reference:
UIScrollView Class Reference
UIScrollViewDelegate Protocol Reference
Hope this helps :)

iOS zoom algorithm

I am creating a graphing calculator application in OpenGL on iOS, and I would like to implement pinch zooming that works like the google maps application or uiscrollview zooming. I'm pretty sure I can't use a uiscrollview because the content of the graph is being generated dynamically.
Implementing zooming where the center of the screen is assumed to be the center of the zoom is easy, but in other cases it is not obvious to me how its being implemented. Can anyone point me in the right direction?
The view that does your custom drawing should be capable of drawing at a certain scale. When your user zooms in or out of the scroll view, send the appropriate zoom factor and let the view redraw itself. The changing bounds is not a problem. The scroll view adjusts its bounds as you pan inside, so its subviews shouldn't have any concern over the bounds of the scroll view.
Of course, you'll probably have to consider performance. For example, it may be too slow to redraw the graph at every frame as a user zooms. Instead, you may only want to redraw when the user stops zooming, which is what Maps.app does.
There are many other considerations for this problem, but that would be where I would start.
EDIT: Ah, I overlooked the OpenGL aspect. Still, with a GL layer-backed view, you still should be able to make the appropriate translations based on both the current zoom factor as well as the scroll view's bounds.

What is actually changing when we zoom out a subView inside a UIScrollView?

I have a subview inside a uiscrollview. Then I zoom it out. So it becomes bigger and allows me to scroll through it.
So what is actually changing here? ContentSize of UIScrollView?
If you are not manually responding to changes in the zoom scale (like I describe in this answer), the view that you return from the -viewForZoomingInScrollView: delegate method is simply having a scaling transform applied to it by the UIScrollView. The frame size of the view is not changing, it is just being graphically transformed (which is why you see blurriness at higher scale factors).
The content size of the scrollview remains logically the same. If you check the frame of the scroll view it remains the same.
I think all that is changing is the scaling of the CGLayers. When you zoom in, it shrinks the clipping region frame smaller but then scales the CGLayer transform upwards. In other words, all the logical elements are still present it is simply choosing to draw and display a different part of it.
In the iPhone Application Programming Guide they have a good explanation about the relationship between frames, clipping regions and various transforms on views.

How do I reset (i.e. un-zoom) a UIScrollView?

I have a UIScrollView that contains an image and a segmented control that allows the user to switch the image inside of the ScrollView. If I just swap the image out inside of the UIImageView, it will display the new image in the zoomed-in state. How do I reset the UIScrollView back to its un-zoomed-in state?
I have a detailed discussion of how (and why) UIScrollView zooming works at github.com/andreyvit/ScrollingMadness/.
(The link also contains a description of how to programmatically zoom UIScrollView, how to emulate Photo Library-style paging+zooming+scrolling, an example project and ZoomScrollView class that encapsulates some of the zooming magic.)
Quote:
UIScrollView does not have a notion of a “current zoom level”, because each subview it contains may have its own current zoom level. Note that there is no field in UIScrollView to keep the current zoom level. However we know that someone stores that zoom level, because if you pinch-zoom a subview, then reset its transform to CGAffineTransformIdentity, and then pinch again, you will notice that the previous zoom level of the subview has been restored.
Indeed, if you look at the disassembly, it is UIView that stores its own zoom level (inside UIGestureInfo object pointed to by the _gestureInfo field). It also has a set of nice undocumented methods like zoomScale and setZoomScale:animated:. (Mind you, it also has a bunch of rotation-related methods, maybe we're getting rotation gesture support some day soon.)
However, if we create a new UIView just for zooming and add our real zoomable view as its child, we will always start with zoom level 1.0. My implementation of programmatic zooming is based on this trick.
If you're not redrawing your view on the completion of the pinch zooming event, then the zoom factor is being set by the transform property of the view you return from the viewForZoomingInScrollView: delegate method. To reset this zoom, set the value of the view's transform property to CGAffineTransformIdentity.
Beware, though, that your next pinch-zooming operation will start where the previous pinch-zoom left off (that is, your new scale will be ignored). To work around this, you may need to implement some of what I describe here.