I want to create an animation similar to app opens in iPhone in iOS7. In this animation it just shows that app is opening from which point and closing at same point.
Can anyone please help me?
#property (weak, nonatomic) IBOutlet UIImageView *bg;
#property (weak, nonatomic) IBOutlet UIImageView *cal;
…
bool nowZoomed = NO;
CGRect iconPosition = {16,113,60,60}; // customize icon position
- (CGRect)zoomedRect // just a helper function, to get the new background screen size
{
float screenWidth = UIScreen.mainScreen.bounds.size.width;
float screenHeight = UIScreen.mainScreen.bounds.size.height;
float size = screenWidth / iconPosition.size.width;
float x = screenWidth/2 - (CGRectGetMidX(iconPosition) * size);
float y = screenHeight/2 - (CGRectGetMidY(iconPosition) * size);
return CGRectMake(x, y, screenWidth * size, screenHeight * size);
}
- (IBAction)test
{
float animationDuration = 0.3f; //default
if (nowZoomed) // zoom OUT
{
[UIView animateWithDuration:animationDuration animations:^{ // animate to original frame
_cal.frame = iconPosition;
_bg.frame = UIScreen.mainScreen.bounds;
} completion:^(BOOL finished) {
[UIView animateWithDuration:animationDuration/2.0f animations:^{ // then fade out
_cal.alpha = 0.0f;
} completion:^(BOOL finished) {
_cal.hidden = YES;
}];
}];
}
else // zoom IN
{
_cal.alpha = 0.0f;
_cal.hidden = NO;
[UIView animateWithDuration:animationDuration/2.0f animations:^{ // fade in faster
_cal.alpha = 1.0f;
}];
[UIView animateWithDuration:animationDuration animations:^{ // while expanding view
_cal.frame = UIScreen.mainScreen.bounds;
_bg.frame = [self zoomedRect];
}];
}
nowZoomed = !nowZoomed;
}
you can test it, by creating a sample project like this:
make two screenshots from simulator like I did (homescreen and calendar view) or grab these two: homescreen / calendar
add 2 image views and 1 button into storyboard
make the background image view as big as the whole screen
and the other image view with this dimensions: {16,113,60,60}
create an IBOutlet for both (the very first two lines of code)
set the button action target to -(void)test
the storyboard picture (left) and animation transition (right)
I personally prefer to use CGAffineTransformMakeScale() and setting -[CALayer affineTransform] in this case.
affineTransform is super easy to use and comes with a few nice, implicit benefits from Core Animation. Examples being that does things like handling of changing the frame's origin for you implicitly and making it really easy to reset back to the initial size if needed -- you never lost it in the first place!
[UIView animateWithDuration:0.3 animations:^{
view.layer.affineTransform = CGAffineTransformMakeScale(10.0, 10.0); // To make a view larger:
otherView.layer.affineTransform = CGAffineTransformMakeScale(0.0, 0.0); // to make a view smaller
}];
and
// To reset views back to their initial size after changing their sizes:
[UIView animateWithDuration:0.3 animations:^{
view.layer.affineTransform = CGAffineTransformIdentity;
otherView.layer.affineTransform = CGAffineTransformIdentity;
}];
As far as I know, that animation is made using screenshots. It updates the frame of the view and simultaneously makes a smooth transition from the app logo to the screenshot from the app. I have imitated the opening of the iPod (music) application from the bottom right corner of the device to the screen size:
UIView * v = [[UIView alloc]init];
CGSize size = self.view.bounds.size;
CGRect frameInitial = CGRectMake(size.width - 30, size.height - 30, 20, 20);
CGRect frameFinal = CGRectMake(0,0, size.width, size.height);
[v setFrame:frameInitial];
Then use the lines below when you want to animate the frame size:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[v setFrame:frameFinal];
} completion:nil];
Edit: Did not realize that the zooming also included the background. The code below is not tested (I am not at work) so expect some defects and typos.
Imagine you have two layers on the view controller's view. Directly on the vc there is the app you want to be opened, lets call it finalView. And on the top layer there is the window with all apps, which will zoom and fade into your app, which is a view behind it. Lets call it firstView.
Initial cond: firstView has a frame of 320 x 480 (It is a window with all the app icons). It has an alpha of 1. finalView has the same frame and alpha, but it is behind firstView.
Final cond: finalView will still have the same frame and alpha. But firstView will zoom into bottom right corner (will have a huge frame) and fade out (alpha -> 0).
//Initial cond: (Or better yet use IB)
CGRect frameInitial = CGRectMake(0,0, self.view.size.width, self.view.size;
CGRect frameFinal = CGRectMake(self.view.size.width * -4 ,self.view.size.height * -5, self.view.size.width * -5,self.view.size.width * -6);
[v setFrame:frameInitial];
Then use the lines below when you want to animate the frame size:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[v setFrame:frameFinal];
} completion:nil];
I have small repo that uses a UICollectionViewFloatLayout to create the zoom effect, https://github.com/MichaelQuan/ios7ZoomEffect. It is still a work in progress but the basic idea is there
The layout code is:
#interface ExpandingCollectionViewLayout ()
#property (nonatomic, assign) CGRect selectedCellFrame;
#property (nonatomic, strong) NSIndexPath *selectedIndexPath;
#end
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect];
[layoutAttributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL *stop) {
[self _transformLayoutAttributes:obj];
}];
return layoutAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
[self _transformLayoutAttributes:layoutAttributes];
return layoutAttributes;
}
- (void)_transformLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
if (self.selectedIndexPath != nil)
{
if ([layoutAttributes.indexPath isEqual:self.selectedIndexPath]) {
// set the frame to be the bounds of the collectionView to expand to the entire collectionView
layoutAttributes.frame = self.collectionView.bounds;
} else {
//scale = collectionView.size / cell_selected.size
//translate = (scale - 1)(cell_i.center - cell_selected.center) + (collectionView.center - cell_selected.center)
CGRect collectionViewBounds = self.collectionView.bounds;
CGRect selectedFrame = self.selectedCellFrame;
CGRect notSelectedFrame = layoutAttributes.frame;
// Calculate the scale transform based on the ratio between the selected cell's frame and the collection views bound
// Scale on that because we want everything to look scaled by the same amount, and the scale is dependent on how much the selected cell has to expand
CGFloat x_scale = collectionViewBounds.size.width / selectedFrame.size.width;
CGFloat y_scale = collectionViewBounds.size.height / selectedFrame.size.height;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(x_scale, y_scale);
// Translation based on how much the selected cell has been scaled
// translate based on the (scale - 1) and delta between the centers
CGFloat x_zoomTranslate = (x_scale - 1) * (CGRectGetMidX(notSelectedFrame) - CGRectGetMidX(selectedFrame));
CGFloat y_zoomTranslate = (y_scale - 1) * (CGRectGetMidY(notSelectedFrame) - CGRectGetMidY(selectedFrame));
CGAffineTransform zoomTranslate = CGAffineTransformMakeTranslation(x_zoomTranslate, y_zoomTranslate); //Translation based on how much the cells are scaled
// Translation based on where the selected cells center is
// since we move the center of the selected cell when expanded to full screen, all other cells must move by that amount as well
CGFloat x_offsetTranslate = CGRectGetMidX(collectionViewBounds) - CGRectGetMidX(selectedFrame);
CGFloat y_offsetTranslate = CGRectGetMidY(collectionViewBounds) - CGRectGetMidY(selectedFrame);
CGAffineTransform offsetTranslate = CGAffineTransformMakeTranslation(x_offsetTranslate, y_offsetTranslate);
// multiply translations first
CGAffineTransform transform = CGAffineTransformConcat(zoomTranslate, offsetTranslate);
transform = CGAffineTransformConcat(scaleTransform, transform);
layoutAttributes.transform = transform;
}
}
}
To expand a cell using this layout code, set both the selectedCellFrame and selectedIndexPath to the cell you want expanded and call performBatchUpdates:completion: on the collection view. To collapse set selectedCellFrame = CGRectNull and selectedIndexPath = nil and call performBatchUpdates:completion:
Related
I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
pageControlUsed = NO;
}
It doesn't work like I want. What can I do?
Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):
#implementation UIInfiniteCollectionView
- (void) recenterIfNecessary {
CGPoint currentOffset = [self contentOffset];
CGFloat contentWidth = [self contentSize].width;
// don't just snap to center, since this might be done in the middle of a drag and not aligned. Make sure we account for that offset
CGFloat offset = kCenterOffset - currentOffset.x;
int delta = -round(offset / kCellSize);
CGFloat shift = (offset + delta * kCellSize);
offset += shift;
CGFloat distanceFromCenter = fabs(offset);
// don't always recenter, just if we get too far from the center. Eliza recommends a quarter of the content width
if (distanceFromCenter > (contentWidth / 4.0)) {
self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
// move subviews back to make it appear to stay still
for (UIView *subview in self.subviews) {
CGPoint center = subview.center;
center.x += offset;
subview.center = center;
}
// add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
if (currentOffset.x > 0) {
int delta = -round(offset / kCellSize);
// MODEL UPDATE GOES HERE
}
}
}
- (void) layoutSubviews { // called at every frame of scrolling
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
Hope this helps someone.
I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[super scrollViewDidEndDecelerating:scrollView];
CGPoint currentOffset = [self contentOffset];
// find the subview that is the closest to the currentOffset.
CGFloat closestOriginX = 999999;
UIView *closestView = nil;
for (UIView *v in self.visibleImageViews) {
CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
if (distanceToCurrentOffset <= closestOriginX) {
closestView = v;
closestOriginX = distanceToCurrentOffset;
}
}
// found the closest view, now find the correct offset
CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
CGFloat offsetX = currentOffset.x - origin.x;
// adjust the centers of the subviews
[UIView animateWithDuration:0.1 animations:^{
for (UIView *v in self.visibleImageViews) {
v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
}
}];
}
I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/
I want to reset a UIView's frame size so I wrote:
view.frame.size.width = x;
but it doesn't work, can anyone tell me why?
you can't set width directly, do this
CGRect frm = view.frame;
frm.size.width = x;
view.frame = frm;
When you call view.frame you get a copy of the frame rect property so setting it is with frame.size.width changes the width of the copy and not the view's frame size
Here's one pass at why. Your code translates into
[view frame] // <- This hands you a CGRect struct, which is not an object.
// At this point, view is out of the game.
.size // <- On the struct you were handed
.width // "
= x; // <- the struct you were handed changed, but view was untouched
One other way of thinking of it is that there is an invisible variable there, that you can't access:
CGRect _ = [view frame]; // hands you a struct
_.size.width = x;
-(void)changeWidth:(UIView*)view wid:(int)newWid{
CGRect rc=view.frame;
view.frame=CGRectMake(rc.origin.x, rc.origin.y, newWid, rc.size.height);
}
- (void) adjustViewtForNewOrientation: (UIInterfaceOrientation) orientation {
//if (UIInterfaceOrientationIsLandscape(orientation)) {
loadMoreView.frame=CGRectMake(0, 0, WIDTH, 50);
headerView.frame=CGRectMake(0, [MLTool getPaddingHeight:self], WIDTH, HEI_SEGMENT);
[self changeWidth:webview wid:WIDTH];
[self changeWidth:tableView wid:WIDTH];
[self changeWidth: segmentedControl wid:WIDTH-SEG_LEFT*2];
}
i have a scollview and i want to display suppose 10 images. frame width is 320 for iphone and 768 for ipad.
something like this image
middle image will be our current selection(mountain image according to this snapshot).
Now i want to scroll left or right to check all the images.
If waterfall is at position 1 mountain at position 2 and hut at position 3, when i scroll right i want to show mountain at position 1 hut at position2 and a new image at position 3.
PLZ tell me how to do this
You have to adjust and set the contentOffset of the scrollView based on the size of the image. You must also set the necessary contentSize for the scrollView which will enable the scrolling.
hey use content offset.
and use this code u get what you want.
this is a scrollview for two image at a time seen on iPhone and when a image scroll at middle of iPhone the label set midlle image label.. as you set width 320 change to 320
-(void)scrollViewDidScroll:(UIScrollView *)inscrollView{
p = scrollView.contentOffset;
NSLog(#"x = %f, y = %f", p.x, p.y);
[self loadDescLbl];
}
-(void)loadDescLbl
{
int point;
point = p.x+160;
int i;
i = point / 160;
i = i-1;
if(i > fetArray.count-1){
i = fetArray.count -1;
}
if(i == -1)
{
i = 0;
}
imageView[i].highlighted = YES;
if([yourArray objectAtIndex:i] isEqualToString:#""]){
priceLbl.text = #"";
}
else {
priceLbl.text = [NSString stringWithFormat:#"%#", [yourArray objectAtIndex:i]];
}
[scroll addSubview:priceLbl];
// }
}
just set point = p.x
and i = point;
if you get problem then tell me...
f all of your images are of the same size you could add 2 buttons (left right) ... and when you tap one of them change the content offset's frame with the width of one picture ...
if you don't want buttons you can obtain the same effect with UISwipeGestureRecognizer
-(IBAction)changeVisibleRect:(UIButton *)btn {
[UIView beginAnimation:#"animation name" context:nil];
[UIView setAnimationDuration:0.3]; //or how much you want in seconds
[scrollview.contentOffset setFrame:CGRectMake(pictureSize * i, 0, pictureSizeWidth, pictureSizeHeight)]; //i = the number of the picture
[UIView commitAnimations];
}
CGRectMake(pictureSize * i, 0, pictureSizeWidth, pictureSizeHeight)] ... instead of pictureSize * i ... you could asign you left or right buttons tags and ... make a
if(btn.tag == 1) {
[UIView beginAnimation:#"animation name" context:nil];
[UIView setAnimationDuration:0.3]; //or how much you want in seconds
[scrollview.contentOffset setFrame:CGRectMake(scrollView.contentOffset.frame.origin.x - pictureSizeWidth, 0, pictureSizeWidth, pictureSizeHeight)]; //i = the number of the picture
[UIView commitAnimations];
}
assuming your left button has set tag = 1;
for the SwipeGesture it would be the same animation code / idea
The Problem
I have a UIWebView inside my iPad application which I need to zoom programmatically, but without the use of gestures/taps. The app. has a "+" and a "-" button for zooming in-and-out in user-defined increments (it is an app. for the visually impaired).
This +/- zoom-button functionality previously worked 100% fine when my app. used a UIImageView inside of a UIScrollView (instead of the WebView). The ImageView was the actual view which was zoomed, and the CenterPoint was calculated for the ScrollView's zoomToRect method.
I now have a WebView, which I know contains a ScrollView as a subview. I tried adapting the code which previously worked with the ImageView/ScrollView combo to instead zoom the WebView's ScrollView, but it is no longer calculating the CenterPoint correctly for zoomToRect:.
What Happens:
The WebView zooms correctly in-terms of the zoom-level, but the center point is always wrong. For some reason, the screen always zooms-in on the top-left every time.
Another odd problem, is that after the very first time you zoom-in, you cannot scroll in the WebView past the visible portion of the screen. If you try to, it shows a bit of the content past the bounds of the currently visible area, but it instantly snaps-back.
What I have tried:
I am trying to zoom the UIWebView's ScrollView.
I create a pointer to the ScrollView, and set "self" as its delegate. I then setup various variables, such as scrSize (the size of the view to zoom) and ZoomHandler (explained below):
- (void)viewDidLoad {
// ... Various UIWebView setup ...
[self LoadURL];
// Zooming & scrollview setup
zoomHandler = [[ZoomHandler alloc] initWithZoomLevel: ZOOM_STEP];
scrSize = CGPointMake(self.WebView.frame.size.width, self.WebView.frame.size.height);
scrollView = [WebView.subviews objectAtIndex:0];
[scrollView setTag:ZOOM_VIEW_TAG];
[scrollView setMinimumZoomScale:MINIMUM_SCALE];
[scrollView setZoomScale:1];
[scrollView setMaximumZoomScale:10];
scrollView.bounces = FALSE;
scrollView.bouncesZoom = FALSE;
scrollView.clipsToBounds = NO;
[scrollView setDelegate:self];
[super viewDidLoad];
}
To override the WebView's default zooming limitations, I inject this Javascript into the loaded webpage in the webViewDidFinishLoad: method:
function increaseMaxZoomFactor() {
var element = document.createElement('meta');
element.name = "viewport";
element.content = "maximum-scale=10 minimum-scale=1 initial-scale=1 user-scalable=yes width=device-width height=device-height;"
var head = document.getElementsByTagName('head')[0];
head.appendChild(element);
}
CenterPoint Code:
This code is used to calculate the CenterPoint to pass into zoomToRect:. This worked 100% fine when I was zooming an ImageView inside of a ScrollView.
-(IBAction)zoomOut {
float newScale = [scrollView zoomScale] / ZOOM_STEP;
if( [scrollView zoomScale] > MINIMUM_SCALE) {
[self handleZoomWith:newScale andZoomType: FALSE];
}
}
-(IBAction)zoomIn {
float newScale = [scrollView zoomScale] * ZOOM_STEP;
if( [scrollView zoomScale] < MAXIMUM_SCALE){
[self handleZoomWith:newScale andZoomType: TRUE];
}
}
-(void)handleZoomWith: (float) newScale andZoomType:(BOOL) isZoomIn {
CGPoint newOrigin = [zoomHandler getNewOriginFromViewLocation: [scrollView contentOffset]
viewSize: scrSize andZoomType: isZoomIn];
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:newOrigin];
[scrollView zoomToRect:zoomRect animated:YES];
}
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// At a zoom scale of 1.0, it would be the size of the scrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [WebView frame].size.height / scale;
zoomRect.size.width = [WebView frame].size.width / scale;
// Choose an origin so as to get the right center.
zoomRect.origin.x = center.x / scale;
zoomRect.origin.y = center.y / scale;
return zoomRect;
}
/**
Determine the origin [THIS IS INSIDE ZOOMHANDLER]
*/
-(CGPoint) getNewOriginFromViewLocation: (CGPoint) oldOrigin viewSize: (CGPoint) viewSize andZoomType:(BOOL) isZoomIn {
// Calculate original center (add the half of the width/height of the screen)
float oldCenterX = oldOrigin.x + (viewSize.x / 2);
float oldCenterY = oldOrigin.y + (viewSize.y / 2);
// Xalculate the new center
CGPoint newCenter;
if(isZoomIn) {
newCenter = CGPointMake(oldCenterX * zoomLevel, oldCenterY * zoomLevel);
} else {
newCenter = CGPointMake(oldCenterX / zoomLevel, oldCenterY / zoomLevel);
}
// Calculate the new origin (deduct the half of the width/height of the screen)
float newOriginX = newCenter.x - (viewSize.x / 2);
float newOriginY = newCenter.y - (viewSize.y / 2);
return CGPointMake(newOriginX, newOriginY);
}
Does anyone have any idea why the CenterPoint is not being calculated correctly? Any help would be GREATLY appreciated; I have been stuck on this for a week now.
Thanks,
Alex
Have you tried implementing the zoom using JavaScript rather than the UIWebView's scrollview ?
I believe you can zoom the webview by calling:
[webView stringByEvaluatingJavaScriptFromString:#"document.body.style.zoom = 5.0;"]; (use your ZOOM_STEP as the value)
You could also calculate the center of the browser window using JS:
posY = getScreenCenterY();
posX = getScreenCenterX();
function getScreenCenterY() {
var y = 0;
y = getScrollOffset()+(getInnerHeight()/2);
return(y);
}
function getScreenCenterX() {
return(document.body.clientWidth/2);
}
function getInnerHeight() {
var y;
if (self.innerHeight) // all except Explorer
{
y = self.innerHeight;
}
else if (document.documentElement && document.documentElement.clientHeight)
// Explorer 6 Strict Mode
{
y = document.documentElement.clientHeight;
}
else if (document.body) // other Explorers
{
y = document.body.clientHeight;
}
return(y);
}
function getScrollOffset() {
var y;
if (self.pageYOffset) // all except Explorer
{
y = self.pageYOffset;
}
else if (document.documentElement &&
document.documentElement.scrollTop) // Explorer 6 Strict
{
y = document.documentElement.scrollTop;
}
else if (document.body) // all other Explorers
{
y = document.body.scrollTop;
}
return(y);
}
(source: http://sliceofcake.wordpress.com/2007/09/13/use-javascript-to-find-the-center-of-the-browser/)
for (UIScrollView *scroll in [myPDFView subviews]) {
//Set the zoom level.
[scroll setZoomScale:2.5f animated:YES];
}
I have a UIView inside a UIScrollView. Whenever the UIScrollView zoom changes, I want to redraw the entire UIView at the new zoom level.
In iOS < 3.2, I was doing this by resizing the UIView within the UIScrollView to make it the new size, and then setting the transform back to Identity, so that it wouldn't try to resize it further. However, with iOS >= 3.2, changing the identity also changes the UIScrollView's zoomScale property.
The result is that whenever I zoom (say 2x), I adjust the embedded UIView to be the appropriate size, and redraw it. However now (since I reset the transform to Identity), the UIScrollView thinks its once again at zoomScale 1, rather than zoomScale 2. So if I have my maxZoomScale set at 2, it will still try zooming further, which is wrong.
I thought about using the CATiledLayer, but I don't think this is sufficient for me, since I want to redraw after every zoom, not just at certain zoom thresholds like it tries to do.
Does anyone know how to do the proper redrawing of the UIView on a zoom?
Tom,
Your question is a bit old, but I came up with a solution for this, so I figured I'd put in an answer in case it helps you or anyone else. The basic trick is to reset the scroll view's zoomScale to 1, and then adjust the minimumZoomScale and maximumZoomScale so that the user can still zoom in and out as expected.
In my implementation, I've subclassed UIScrollView and set it to be its own delegate. In my subclass, I implement the two delegate methods you need for zooming (shown below). contentView is a property I added to my UIScrollView subclass that in order to give it the view that actually displays the content.
So, my init method looks something like this (kMinimumZoomScale and kMaximumZoomScale are #define's at the top of the class):
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.autoresizesSubviews = YES;
self.showsVerticalScrollIndicator = YES;
self.showsHorizontalScrollIndicator = NO;
self.bouncesZoom = YES;
self.alwaysBounceVertical = YES;
self.delegate = self;
self.minimumZoomScale = kMinimumZoomScale;
self.maximumZoomScale = kMaximumZoomScale;
}
return self;
}
Then I implement the standard UIScrollView delegate methods for zooming. My ContentView class has a property called zoomScale that tells it what scale to use for displaying its content. It uses that in its drawRect method to resize the content as appropriate.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)aScrollView {
return contentView;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)aScrollView withView:(UIView *)view atScale:(float)scale {
CGFloat oldZoomScale = contentView.zoomScale;
CGSize size = self.bounds.size;
// Figure out where the scroll view was centered so that we can
// fix up its offset after adjusting the scaling
CGPoint contentCenter = self.contentOffset;
contentCenter.x += size.width / (oldZoomScale * scale) / 2;
contentCenter.y += size.height / (oldZoomScale * scale) / 2;
CGFloat newZoomScale = scale * oldZoomScale;
newZoomScale = MAX(newZoomScale, kMinimumZoomscale);
newZoomScale = MIN(newZoomScale, kMaximumZoomscale);
// Set the scroll view's zoom scale back to 1 and adjust its minimum and maximum
// to allow the expected amount of zooming.
self.zoomScale = 1.0;
self.minimumZoomScale = kMinimumZoomScale / newZoomScale;
self.maximumZoomScale = kMaximumZoomScale / newZoomScale;
// Tell the contentView about its new scale. My contentView.zoomScale setter method
// calls setNeedsDisplay, but you could also call it here
contentView.zoomScale = newZoomScale;
// My ContentView class overrides sizeThatFits to give its expected size with
// zoomScale taken into account
CGRect newContentSize = [contentView sizeThatFits];
// update the content view's frame and the scroll view's contentSize with the new size
contentView.frame = CGRectMake(0, 0, newContentSize.width, newContentSize.height);
self.contentSize = newContentSize;
// Figure out the new contentOffset so that the contentView doesn't appear to move
CGPoint newContentOffset = CGPointMake(contentCenter.x - size.width / newZoomScale / 2,
contentCenter.y - size.height / newZoomScale / 2);
newContentOffset.x = MIN(newContentOffset.x, newContentSize.width - size.width);
newContentOffset.x = MAX(0, newContentOffset.x);
newContentOffset.y = MIN(newContentOffset.y, newContentSize.height - .size.height);
newContentOffset.y = MAX(0, newContentOffset.y);
[self setContentOffset:newContentOffset animated:NO];
}
In my case, I have an image view and on top of the image view I have several other imageviews. This is the implementation I came up with and works fine:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(double)scale{
NSLog(#"finished zooming");
NSLog(#"position x :%f",pin1.frame.origin.x);
NSLog(#"position y :%f",pin1.frame.origin.y);
CGRect frame = pin1.frame;
frame.origin.x = pin1.frame.origin.x * scale;
frame.origin.y = pin1.frame.origin.y * scale;
pin1.frame = frame;
NSLog(#"position x new :%f",pin1.frame.origin.x);
NSLog(#"position y new:%f",pin1.frame.origin.y);
}