I just spent the past two days wrestling with trying to get a working implementation of tap to zoom working correctly in a UIScrollView.
I’m writing an iPhone application that generates a report view, and I really need to allow users to tap to zoom in, tap to zoom out, and still be able to use pinch zooming and panning.
A long search of the Apple demo code, and internet articles did not turn anything definitive up, besides the fact that almost everyone else seems to be having similar problems, including but not limited to: fuzzy renderings when expanding, confused and incorrectly zooming UIScrollViews after manual tap zooming, and much else besides.
I ran into all these problems, and eventually I concluded that the layers underneath a UIView are used to cache zoom renderings, and also carry other state, that it just is not possible to get at through the published api. There *is* a private api, but it is just that, private, and I’d like to get this app on the App Store, so I can’t use it.
Ok, so the approach I created is simply to recreate the scroll view and content view whenever I need to. It turns out that this works well enough for me, both in the simulator, and on the device.
The following is my TapZoomDemo I created to show this solution:
TapZoomAppDelegate.h
//
// TapZoomDemoAppDelegate.h
// TapZoomDemo
//
// Created by Jonathan Watmough on 12/10/08.
// Copyright __MyCompanyName__ 2008. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TapZoomDemoAppDelegate : NSObject <UIApplicationDelegate,UIScrollViewDelegate>
{
UIWindow *window;
}
// properties
@property (nonatomic, retain) IBOutlet UIWindow *window;
// methods
- (void)createScrollView:(CGRect)scrollFrame contentOffset:(CGPoint)contentOffset
contentFrame:(CGRect)contentFrame
scalingFactor:(float)scalingFactor;
@end
TapZoomAppDelegate.m
//
// TapZoomDemoAppDelegate.m
// TapZoomDemo
//
// Created by Jonathan Watmough on 12/10/08.
// Copyright __MyCompanyName__ 2008. All rights reserved.
//
#import "TapZoomDemoAppDelegate.h"
#import "MyContentView.h"
@implementation TapZoomDemoAppDelegate
@synthesize window;
// content min and max widths (used to calculate min/max scale factors)
// hardcoded to same as and 2x width of scroll view
static const float kMinWidth = 320;
static const float kMaxWidth = 640;
- (void)dealloc
{
[window release];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after application launch
// put up a backing checked view
UIView * checks = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
checks.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"4x4check.png"]];
[window addSubview:checks];
// use app frame for scroll view
CGRect scrollFrame = [[UIScreen mainScreen] applicationFrame];
// display our view initially at max size (2x), offset to be centered
CGRect contentFrame = CGRectMake(0, 0, scrollFrame.size.width*2, scrollFrame.size.height*2);
CGPoint contentOffset = CGPointMake((contentFrame.size.width-scrollFrame.size.width)/2,
(contentFrame.size.height-scrollFrame.size.height)/2);
// create scroll view and content view
[self createScrollView:scrollFrame contentOffset:contentOffset contentFrame:contentFrame
scalingFactor:0.0];
// register for double-tap on content view
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doubleTapZoom:)
name:@"DoubleTapZoom" object:nil];
// bring up app window
[window makeKeyAndVisible];
}
// helper method for creating a scroll view and content view
- (void)createScrollView:(CGRect)scrollFrame contentOffset:(CGPoint)contentOffset
contentFrame:(CGRect)contentFrame
scalingFactor:(float)scalingFactor
{
// create content frame at required size, with optional scaling
MyContentView * content = [[MyContentView alloc] initWithFrame:contentFrame];
if( scalingFactor!=0 )
[content setTransform:CGAffineTransformMakeScale(scalingFactor, scalingFactor)];
// create a new scroll view and add to window
UIScrollView * scrollView = [[UIScrollView alloc] initWithFrame:scrollFrame];
[window addSubview:scrollView];
// reset min and max scaling
[scrollView setMinimumZoomScale:kMinWidth/contentFrame.size.width];
[scrollView setMaximumZoomScale:kMaxWidth/contentFrame.size.width];
// constrain the content offset
float offsetX = contentOffset.x;
if( offsetX<0 )
offsetX = 0;
if( offsetX>contentFrame.size.width-scrollFrame.size.width )
offsetX = contentFrame.size.width-scrollFrame.size.width;
float offsetY = contentOffset.y;
if( offsetY<0 )
offsetY = 0;
if( offsetY>contentFrame.size.height-scrollFrame.size.height )
offsetY = contentFrame.size.height-scrollFrame.size.height;
// setup the scroll view, size is passed, (possible bad) offset is passed
// we'll go ahead and use the passed offset, but animate back to constrained
[scrollView addSubview:content];
[scrollView setContentSize:contentFrame.size];
[scrollView setContentOffset:contentOffset];
[scrollView setDelegate:self];
// be nice and animate back to constrained offset, with optional scaling back to 1.0 (tap zoom)
[UIView beginAnimations:@"" context:nil];
[scrollView setContentOffset:CGPointMake(offsetX,offsetY)];
if( scalingFactor!=0.0 )
[content setTransform:CGAffineTransformIdentity];
[UIView commitAnimations];
// rather than cluttering up autorelease pool
[content release];
[scrollView release];
}
// scroll delegate methods
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
// return the first subview of the scroll view
return [scrollView.subviews objectAtIndex:0];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
// get the view
UIView * content = [scrollView.subviews objectAtIndex:0];
// get size and offset of (dynamic scaled) content view
CGPoint contentOffset = [scrollView contentOffset];
CGSize contentSize = [scrollView contentSize];
// dump the old views
[content removeFromSuperview];
[scrollView removeFromSuperview];
// scale the size of the frame to scaled size
CGRect contentFrame = CGRectMake(0, 0, contentSize.width, contentSize.height);
// create a new scroll view
CGRect scrollFrame = [[UIScreen mainScreen] applicationFrame];
// create our new content view and containing scroll view
[self createScrollView:scrollFrame contentOffset:contentOffset contentFrame:contentFrame scalingFactor:0.0];
}
// Double Tap Zoom
- (void)doubleTapZoom:(NSNotification *)notification
{
// scroll view passed as object, get content subview
UIScrollView * scrollView = [notification object];
UIView * content = [[scrollView subviews] objectAtIndex:0];
// get dictionary holding event, and event
NSDictionary * dict = [notification userInfo];
UIEvent * event = [dict objectForKey:@"event"];
// get touch from set of touches for view
UITouch * touch = [[event touchesForView:content] anyObject];
// need scroll frame, content frame and content offset
CGRect scrollFrame = scrollView.frame;
CGRect contentFrame;
CGPoint contentOffset;
// zoom in if not at max size, other zoom out to fit to page
float scalingFactor = 0.0;
float currentWidth = [content frame].size.width;
if( currentWidth < kMaxWidth )
{
contentFrame = CGRectMake(0, 0, scrollFrame.size.width*2, scrollFrame.size.height*2);
scalingFactor = kMaxWidth / currentWidth;
CGPoint touchPoint = [touch locationInView:content];
contentOffset = CGPointMake(touchPoint.x*scalingFactor/2, touchPoint.y*scalingFactor/2);
}
else
{
scalingFactor = kMinWidth / currentWidth;
contentFrame = CGRectMake(0, 0, scrollFrame.size.width, scrollFrame.size.height);
contentOffset = CGPointMake(0,0);
}
// dump the old views
[content removeFromSuperview];
[scrollView removeFromSuperview];
// create scroll view and content view
[self createScrollView:scrollFrame contentOffset:contentOffset
contentFrame:contentFrame scalingFactor:1.0/scalingFactor];
}
@end
MyContentView.m
//
// MyContentView.m
// TapZoomDemo
//
// Created by Jonathan Watmough on 12/10/08.
// Copyright 2008 __MyCompanyName__. All rights reserved.
//
#import "MyContentView.h"
@implementation MyContentView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
[self setBackgroundColor:[UIColor whiteColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Drawing code - This will be called with different bounds at the end of each
// tap/pinch zoom operation.
// For illustration, I'm scaling so that a '1' glyph fills the area.
// context
CGContextRef context = UIGraphicsGetCurrentContext();
// Fill view with a 20-point '1'
UIFont * f = [UIFont systemFontOfSize:20];
[[UIColor darkGrayColor] set];
// get bounds
CGRect b = [self bounds];
// draw a string
NSString * text = @"1";
CGSize sz = [text sizeWithFont:f];
// scale to bounds / text size
CGContextScaleCTM(context, b.size.width/sz.width, b.size.height/sz.height);
// draw
[text drawAtPoint:CGPointMake(0,0) withFont:f];
}
// look for double taps
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// get any touch
UITouch * t = [touches anyObject];
if( [t tapCount]>1 )
{
NSDictionary *dict = [NSDictionary dictionaryWithObject:event forKey:@"event"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DoubleTapZoom"
object:[self superview]
userInfo:dict];
}
}
- (void)dealloc {
[super dealloc];
}
@end
相关推荐
Source Code for 2009 Supercomputing Paper Implementing Sparse Matrix-Vector Multiplication on Throughput-Oriented Processors
Implementing OpenShift will walk the reader through how to easily develop and deploy upon an open source OpenShift Platform-as-a-Service. We will then discuss the architecture of the platform so that ...
Pro Machine Learning Algorithms: A Hands-On Approach to Implementing Algorithms in Python and R by V Kishore Ayyadevara Bridge the gap between a high-level understanding of how an algorithm works and...
Pro Machine Learning Algorithms: A Hands-On Approach to Implementing Algorithms in Python and R by V Kishore Ayyadevara Bridge the gap between a high-level understanding of how an algorithm works and...
Then move into detailed coverage of implementing and configuring the AlwaysOn feature set in order to meet the business objectives set by your organization. SQL Server AlwaysOn Revealed offers real-...
* Evolving the enterprise architecture towards an SOA while continuing to deliver business value on a project-by-project basis * Understanding the fundamentals of SOA and distributed systems, the ...
This combined program distils the strengths of the two: Lean with its focus on eliminating waste and making processes faster, and Six Sigma with its focus on reducing variation and defects....
Knowing how to adopt DevOps in your organization is becoming an increasingly important skill for developers, whether you work for a startup, SMB, or an enterprise. This book will help you to ...
Implementing_Security_in_Siebel_7 Learn siebel
VMware vRealize Automation Handbook Implementing Cloud Management in the Enterprise Environment 英文azw3 本资源转载自网络,如有侵权,请联系上传者或csdn删除 查看此书详细信息请在美国亚马逊官网搜索...
With Implementing Domain-Driven Design, Vaughn has made an important contribution not only to the literature of the Domain-Driven Design community, but also to the literature of the broader enterprise...
Responsive design is not just another technique--it is the beginning of the maturation of a medium and a fundamental shift in the way we think about the web. "Implementing Responsive Design" is a ...
RSS模型全称为责任敏感安全模型(Responsibility-Sensitive Safety),该模型的核心目标在于将人类对于驾驶情境和决策的判断形式化和具体化,以确保自动车辆(AV)在未来能够安全地与人类驾驶者共享道路。...
Written as a tutorial on how to think about, organise and implement programs in scientific computing, this book achieves its goal through an eclectic and wide-ranging collection of projects....
### 实施业务添加接口(BAdI)在增强项目中的关键知识点 #### 1. 概述 本教程详细介绍了如何在客户修改(CMOD)环境中实施业务添加接口(BAdI),并从中获得BAdI所带来的灵活性,同时避免与CMOD相关的所有限制。...