ios - How to prevent "bounce" effect when a custom view redraws after zooming? -
note casual readers: despite title, question has nothing uiscrollview
properties bounces
(scrolling related) or bounceszoom
.
i using uiscrollview
add zooming custom view. custom view uses sublayers draw content. each sublayer calayer
instance added view's main layer [calayer addsublayer:]
. sublayers use coregraphics render content.
after each zoom completes, custom view needs redraw content @ new zoom scale content appears crisp , sharp again. trying approach work shown in this question, i.e. reset scroll view's zoomscale
property 1.0 after each zoom operation, adjust minimumzoomscale
, maximumzoomscale
properties user cannot zoom in/out more intended.
the content redrawing works correctly (!), missing smooth gui update zoomed content redrawn in place without appearing move. current solution (code example follows @ bottom of question), observe kind of "bounce" effect: zoom operation ends, zoomed content briefly moves different location, moves original location.
i not entirely sure reason "bounce" effect is: either there 2 gui update cycles (one resetting zoomscale
1.0, , setneedsdisplay
), or sort of animation taking place makes both changes visible, 1 after other.
my question is: how can prevent "bounce" effect described above?
update: following minimal complete code example can copy&paste observe effect talking about.
- create new xcode project using "empty application" template.
- add code below
appdelegate.h
,appdelegate.m
, respectively. - in project's link build phase, add reference
quartzcore.framework
.
stuff goes appdelegate.h
:
#import <uikit/uikit.h> @class layerview; @interface appdelegate : uiresponder <uiapplicationdelegate, uiscrollviewdelegate> @property (nonatomic, retain) uiwindow* window; @property (nonatomic, retain) layerview* layerview; @end
stuff goes appdelegate.m
:
#import "appdelegate.h" #import <quartzcore/quartzcore.h> @class layerdelegate; @interface layerview : uiview @property (nonatomic, retain) layerdelegate* layerdelegate; @end @interface layerdelegate : nsobject @property(nonatomic, retain) calayer* layer; @property (nonatomic, assign) cgfloat zoomscale; @end static cgfloat kminimumzoomscale = 1.0; static cgfloat kmaximumzoomscale = 5.0; @implementation appdelegate - (void) dealloc { self.window = nil; self.layerview = nil; [super dealloc]; } - (bool) application:(uiapplication*)application didfinishlaunchingwithoptions:(nsdictionary*)launchoptions { [uiapplication sharedapplication].statusbarhidden = yes; self.window = [[[uiwindow alloc] initwithframe:[[uiscreen mainscreen] bounds]] autorelease]; self.window.backgroundcolor = [uicolor whitecolor]; uiscrollview* scrollview = [[[uiscrollview alloc] initwithframe:self.window.bounds] autorelease]; [self.window addsubview:scrollview]; scrollview.contentsize = scrollview.bounds.size; scrollview.delegate = self; scrollview.minimumzoomscale = kminimumzoomscale; scrollview.maximumzoomscale = kmaximumzoomscale; scrollview.zoomscale = 1.0f; scrollview.bounceszoom = no; self.layerview = [[[layerview alloc] initwithframe:scrollview.bounds] autorelease]; [scrollview addsubview:self.layerview]; [self.window makekeyandvisible]; return yes; } - (uiview*) viewforzoominginscrollview:(uiscrollview*)scrollview { return self.layerview; } - (void) scrollviewdidendzooming:(uiscrollview*)scrollview withview:(uiview*)view atscale:(float)scale { cgpoint contentoffset = scrollview.contentoffset; cgsize contentsize = scrollview.contentsize; scrollview.maximumzoomscale = scrollview.maximumzoomscale / scale; scrollview.minimumzoomscale = scrollview.minimumzoomscale / scale; // big change here: resets scroll view's contentsize , // contentoffset, , layerview's frame, bounds , transform // properties scrollview.zoomscale = 1.0f; cgfloat newzoomscale = self.layerview.layerdelegate.zoomscale * scale; self.layerview.layerdelegate.zoomscale = newzoomscale; self.layerview.frame = cgrectmake(0, 0, contentsize.width, contentsize.height); scrollview.contentsize = contentsize; [scrollview setcontentoffset:contentoffset animated:no]; [self.layerview setneedsdisplay]; } @end @implementation layerview - (id) initwithframe:(cgrect)frame { self = [super initwithframe:frame]; if (self) { self.layerdelegate = [[[layerdelegate alloc] init] autorelease]; [self.layer addsublayer:self.layerdelegate.layer]; // super's initwithframe invoked setneedsdisplay, need // repeat because @ time our layerdelegate property still empty [self setneedsdisplay]; } return self; } - (void) dealloc { self.layerdelegate = nil; [super dealloc]; } - (void) setneedsdisplay { [super setneedsdisplay]; // zooming changes view's frame, not frame of layer self.layerdelegate.layer.frame = self.bounds; [self.layerdelegate.layer setneedsdisplay]; } @end @implementation layerdelegate - (id) init { self = [super init]; if (self) { self.layer = [calayer layer]; self.layer.delegate = self; self.zoomscale = 1.0f; } return self; } - (void) dealloc { self.layer = nil; [super dealloc]; } - (void) drawlayer:(calayer*)layer incontext:(cgcontextref)context { cgrect layerrect = self.layer.bounds; cgfloat radius = 25 * self.zoomscale; cgfloat centerdistancefromedge = 5 * self.zoomscale + radius; cgpoint topleftcenter = cgpointmake(cgrectgetminx(layerrect) + centerdistancefromedge, cgrectgetminy(layerrect) + centerdistancefromedge); [self drawcirclewithcenter:topleftcenter radius:radius fillcolor:[uicolor redcolor] incontext:context]; cgpoint layercenter = cgpointmake(cgrectgetmidx(layerrect), cgrectgetmidy(layerrect)); [self drawcirclewithcenter:layercenter radius:radius fillcolor:[uicolor greencolor] incontext:context]; cgpoint bottomrightcenter = cgpointmake(cgrectgetmaxx(layerrect) - centerdistancefromedge, cgrectgetmaxy(layerrect) - centerdistancefromedge); [self drawcirclewithcenter:bottomrightcenter radius:radius fillcolor:[uicolor bluecolor] incontext:context]; } - (void) drawcirclewithcenter:(cgpoint)center radius:(cgfloat)radius fillcolor:(uicolor*)color incontext:(cgcontextref)context { const int startradius = [self radians:0]; const int endradius = [self radians:360]; const int clockwise = 0; cgcontextaddarc(context, center.x, center.y, radius, startradius, endradius, clockwise); cgcontextsetfillcolorwithcolor(context, color.cgcolor); cgcontextfillpath(context); } - (double) radians:(double)degrees { return degrees * m_pi / 180; } @end
based on sample project, key you're manipulating calayer directly. default, setting calayer properties, such frame, cause animations. suggestion use [uiview setanimationsenabled:no]
on right track, affects uiview-based animations. if calayer equivalent, in setneedsdisplay: method:
[catransaction begin]; [catransaction setdisableactions:yes]; self.layerdelegate.layer.frame = self.bounds; [catransaction commit];
it prevents implicit frame-changing animation , looks right me. can disable these implicit animations via calayerdelegate method in layerdelegate class:
- (id<caaction>)actionforlayer:(calayer *)layer forkey:(nsstring *)event { return (id)[nsnull null]; // nsnull means "don't implicit animations" }
original suggestions:
perhaps in animation block without knowing it? or, perhaps 1 of methods you're calling setting animation block? if [uiview setanimationsenabled:no]
before code , re-enable them after?
if it's not animation, it's suspect; 2 view updates of kind. (perhaps 1 scroll view, , 1 code somehow?) runnable sample code great in case.
(out of curiosity, have tried using calayer's shouldrasterize , rasterizationscale rather faking out zoom level?)
Comments
Post a Comment