2009-05-03 8 views
6

मुझे एक टाइल वाली छवि दिखाने के लिए एक कस्टम UIView मिला है।एनीमेशन के दौरान UIView स्केलिंग

- (void)drawRect:(CGRect)rect 
    { 
     ... 
     CGContextRef context = UIGraphicsGetCurrentContext();  
     CGContextClipToRect(context, 
      CGRectMake(0.0, 0.0, rect.size.width, rect.size.height));  
     CGContextDrawTiledImage(context, imageRect, imageRef); 
     ... 
    } 

अब मैं उस दृश्य के आकार को एनिमेट करने की कोशिश कर रहा हूं।

[UIView beginAnimations:nil context:nil]; 
[UIView setAnimationDuration:0.3]; 

// change frame of view 

[UIView commitAnimations]; 

टाइल आकार को लगातार छोड़कर, टाइल वाले क्षेत्र के लिए बस मुझे उम्मीद है कि मैं क्या अपेक्षा करूंगा। इसके बजाए क्या होता है कि जब क्षेत्र दृश्य की मूल सामग्री बढ़ता है तो नए आकार में स्केल किया जाता है। कम से कम एनीमेशन के दौरान। तो एनीमेशन की शुरुआत और अंत में सब कुछ अच्छा है। एनीमेशन के दौरान टाइल्स विकृत हो जाते हैं।

सीए स्केल करने की कोशिश क्यों कर रहा है? मैं इसे ऐसा करने से कैसे रोक सकता हूं? मुझसे क्या छूट गया?

+0

ऐसा लगता है कि यह दृश्य की सामग्री मोड से संबंधित है। लेकिन इसे UIViewContentModeLeft पर सेट करने से वास्तव में इसे हल नहीं किया जाता है। यह तब स्केल नहीं करता है लेकिन तुरंत नए फ्रेम आकार में पॉप हो जाता है। इसे UIViewContentModeRedraw पर सेट करना एनीमेशन के दौरान बिल्कुल आकर्षित करने के लिए कॉल नहीं कर रहा है। – tcurdt

उत्तर

15

कोर एनिमेशन वापस हर एनीमेशन के लिए अपने कोड के लिए कॉल फ्रेम यह जितनी जल्दी यह है के रूप में कभी नहीं होगा, और कस्टम गुण का एनीमेशन एक लंबे अनुरोध किया सुविधा और सीए पर मैक के लिए पूछे जाने वाले प्रश्न किया गया है करने के लिए था।

का उपयोग UIViewContentModeRedraw सही रास्ते पर है, और यह भी सबसे अच्छा आप सीए से मिलेगा है समस्या UIKit बिंदु से देखने के लिए है कि फ्रेम में केवल दो मान हैं: संक्रमण की शुरुआत में मूल्य और संक्रमण के अंत में मूल्य, और यही वह है जिसे आप देख रहे हैं। यदि आप कोर एनीमेशन आर्किटेक्चर दस्तावेज़ों को देखते हैं तो आप देखेंगे कि सीए के पास सभी परत गुणों का निजी प्रतिनिधित्व और समय के साथ उनके मूल्य बदलते हैं। यही वह जगह है जहां फ्रेम इंटरपोलेशन हो रहा है, और आपको ऐसा होने पर परिवर्तनों की अधिसूचना नहीं दी जा सकती है।

तो समय के साथ दृश्य फ्रेम को बदलने के लिए(या performSelector:withObject:afterDelay:) का उपयोग करने का एकमात्र तरीका है, पुराने तरीके से।

+0

धन्यवाद, मैंने ऐसा करने के लिए एनएसटीमर का उपयोग किया (एनीमेशन के साथ ड्रॉरेक्ट कॉल करें)। यह मेरे लिए काम कर रहा है। – Strong

+0

@ jazou2012 आपको 'drawRect' को कॉल नहीं करना चाहिए, आपको 'setNeedsDisplay' को कॉल करना चाहिए। –

+0

@ जीन-फिलिपपेलेट हाँ, आप सही हैं। 'setNeedsDisplay'' drawRect' को कॉल करेगा! और यही वह है जो मैं कहना चाहता था। – Strong

2

डंकन इंगित करता है, कोर एनिमेशन हर फ्रेम अपने UIView की परत की सामग्री पुनः बनाने नहीं होगा के रूप में यह आकार दिया जाता है। आपको टाइमर का उपयोग करके खुद को करने की ज़रूरत है।

ओमनी पर लोग कैसे अपने स्वयं के कस्टम गुण है कि अपने मामले पर लागू हो सकने पर आधारित एनिमेशन करने के लिए का एक अच्छा उदाहरण था। यह उदाहरण, यह कैसे काम करता है इसके स्पष्टीकरण के साथ, here पाया जा सकता है।

4

मैं एक अलग संदर्भ में समान समस्या का सामना करना पड़ रहा था, मैं इस सूत्र पर ठोकर खाई और NSTimer में फ्रेम की स्थापना के डंकन के सुझाव मिल गया।

CGFloat kDurationForFullScreenAnimation = 1.0; 
CGFloat kNumberOfSteps = 100.0; // (kDurationForFullScreenAnimation/kNumberOfSteps) will be the interval with which NSTimer will be triggered. 
int gCurrentStep = 0; 
-(void)animateFrameOfView:(UIView*)inView toNewFrame:(CGRect)inNewFrameRect withAnimationDuration:(NSTimeInterval)inAnimationDuration numberOfSteps:(int)inSteps 
{ 
    CGRect originalFrame = [inView frame]; 

    CGFloat differenceInXOrigin = originalFrame.origin.x - inNewFrameRect.origin.x; 
    CGFloat differenceInYOrigin = originalFrame.origin.y - inNewFrameRect.origin.y; 
    CGFloat stepValueForXAxis = differenceInXOrigin/inSteps; 
    CGFloat stepValueForYAxis = differenceInYOrigin/inSteps; 

    CGFloat differenceInWidth = originalFrame.size.width - inNewFrameRect.size.width; 
    CGFloat differenceInHeight = originalFrame.size.height - inNewFrameRect.size.height; 
    CGFloat stepValueForWidth = differenceInWidth/inSteps; 
    CGFloat stepValueForHeight = differenceInHeight/inSteps; 

    gCurrentStep = 0; 
    NSArray *info = [NSArray arrayWithObjects: inView, [NSNumber numberWithInt:inSteps], [NSNumber numberWithFloat:stepValueForXAxis], [NSNumber numberWithFloat:stepValueForYAxis], [NSNumber numberWithFloat:stepValueForWidth], [NSNumber numberWithFloat:stepValueForHeight], nil]; 
    NSTimer *aniTimer = [NSTimer timerWithTimeInterval:(kDurationForFullScreenAnimation/kNumberOfSteps) 
               target:self 
               selector:@selector(changeFrameWithAnimation:) 
               userInfo:info 
               repeats:YES]; 
    [self setAnimationTimer:aniTimer]; 
    [[NSRunLoop currentRunLoop] addTimer:aniTimer 
           forMode:NSDefaultRunLoopMode]; 
} 

-(void)changeFrameWithAnimation:(NSTimer*)inTimer 
{ 
    NSArray *userInfo = (NSArray*)[inTimer userInfo]; 
    UIView *inView = [userInfo objectAtIndex:0]; 
    int totalNumberOfSteps = [(NSNumber*)[userInfo objectAtIndex:1] intValue]; 
    if (gCurrentStep<totalNumberOfSteps) 
    { 
     CGFloat stepValueOfXAxis = [(NSNumber*)[userInfo objectAtIndex:2] floatValue]; 
     CGFloat stepValueOfYAxis = [(NSNumber*)[userInfo objectAtIndex:3] floatValue]; 
     CGFloat stepValueForWidth = [(NSNumber*)[userInfo objectAtIndex:4] floatValue]; 
     CGFloat stepValueForHeight = [(NSNumber*)[userInfo objectAtIndex:5] floatValue]; 

     CGRect currentFrame = [inView frame]; 
     CGRect newFrame; 
     newFrame.origin.x = currentFrame.origin.x - stepValueOfXAxis; 
     newFrame.origin.y = currentFrame.origin.y - stepValueOfYAxis; 
     newFrame.size.width = currentFrame.size.width - stepValueForWidth; 
     newFrame.size.height = currentFrame.size.height - stepValueForHeight; 

     [inView setFrame:newFrame]; 

     gCurrentStep++; 
    } 
    else 
    { 
     [[self animationTimer] invalidate]; 
    } 
} 

मुझे पता चला है कि यह एक लंबे समय के फ्रेम और उसके कार्य को पूरा करने टाइमर सेट करने लगते हैं: मैं एक ही प्राप्त करने के लिए इस कोड को लागू किया। यह संभवतः इस दृष्टिकोण का उपयोग करते हुए दृश्य पदानुक्रम पर कितना गहरा है इस पर निर्भर करता है। और शायद यही कारण है कि दृश्य को फिर से आकार दिया गया है और फिर एसडीके में उत्पत्ति के लिए एनिमेटेड है। या शायद, क्या मेरे कोड में टाइमर तंत्र में कुछ समस्या है जिसके कारण प्रदर्शन में बाधा आ गई है?

यह काम करता है, लेकिन यह बहुत धीमी है, हो सकता है, क्योंकि मेरे विचार पदानुक्रम बहुत गहरा है।

+2

आपका कोड मेरे विचार (पृष्ठभूमि दृश्य और एक टेक्स्ट ओवरले) के लिए बहुत अच्छा काम करता है। परिवर्तन के शीर्ष पर 2 परिवर्तन आवश्यक थे: 1. [self.animationTimer अमान्य] परिवर्तन के शीर्ष पर फ़्रेमविथएनीमेशन, और 2. एनीमेशन की अवधि 0.3 को बदलने के साथ-साथ चरणों की संख्या बदलना। चूंकि यह अभी है, आप टाइमर को 10ms की अवधि में कॉल कर रहे हैं, लेकिन आपको 60Hz रीफ्रेश दर के लिए केवल 16ms करना होगा। – Jon

+0

धन्यवाद @ जोन, हैवेन्ट ने 60 हर्ट्ज रीफ्रेश दर के बारे में सोचा था, लेकिन मेरे मामले में पूरी प्रक्रिया को धीमा कर दिया जाएगा, मेरा दृश्य पदानुक्रम भारी था और इसलिए यह अपेक्षा से अधिक समय बात कर रहा था। तो, खुद को आवश्यकता को बदलना पड़ा! –

1

पर एक नजर है करने के लिए चाहते हो सकता है एनिमेशन की अवधि के लिए कोई मुद्दा नहीं है, तो आप एक CADisplayLink उपयोग कर सकते हैं। उदाहरण के लिए यह एक कस्टम UIView ड्राइंग UIBezierPaths के स्केलिंग की एनीमेशन को सुगम बनाता है। नीचे एक नमूना कोड है जिसे आप अपनी छवि के लिए अनुकूलित कर सकते हैं।

मेरे कस्टम व्यू के इवर्स में डिस्प्ले लिंक एक कैडिसप्ले लिंक के रूप में होता है, और दो सीजीआरईटी: टूफ्रेम और फ्रैम, साथ ही अवधि और स्टार्टटाइम भी शामिल है।

- (void)yourAnimationMethodCall 
{ 
    toFrame = <get the destination frame> 
    fromFrame = self.frame; // current frame. 

    [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // just in case  
    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateFrame:)]; 
    startTime = CACurrentMediaTime(); 
    duration = 0.25; 
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
} 

- (void)animateFrame:(CADisplayLink *)link 
{ 
    CGFloat dt = ([link timestamp] - startTime)/duration; 

    if (dt >= 1.0) { 
     self.frame = toFrame; 
     [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
     displayLink = nil; 
     return; 
    } 

    CGRect f = toFrame; 
    f.size.height = (toFrame.size.height - fromFrame.size.height) * dt + fromFrame.size.height; 
    self.frame = f; 
} 

ध्यान दें कि मैंने अपने प्रदर्शन का परीक्षण नहीं किया है, लेकिन यह आसानी से काम करता है।

1

मैंने सीमा के साथ UIView पर आकार परिवर्तन को एनिमेट करने का प्रयास करते समय इस प्रश्न पर ठोकर खाई। मुझे आशा है कि जो लोग समान रूप से यहां पहुंचेंगे वे इस जवाब से लाभ उठा सकते हैं। मूल रूप से मैं एक कस्टम UIView उपवर्ग बनाने गया था और drawRect विधि अधिभावी इस प्रकार है:

- (void)drawRect:(CGRect)rect 
{ 
    CGContextRef ctx = UIGraphicsGetCurrentContext(); 

    CGContextSetLineWidth(ctx, 2.0f); 
    CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor); 
    CGContextStrokeRect(ctx, rect); 

    [super drawRect:rect]; 
} 

यह समस्याओं स्केलिंग जब एनिमेशन के साथ संयोजन के रूप में दूसरों का उल्लेख किया है का नेतृत्व किया। सीमा के ऊपर और नीचे बहुत मोटा या दो पतला हो जाएगा और स्केल किया जाएगा। धीरे-धीरे एनिमेशन पर टॉगल करते समय यह प्रभाव आसानी से ध्यान देने योग्य होता है।

[_borderView.layer setBorderWidth:2.0f]; 
[_borderView.layer setBorderColor:[UIColor orangeColor].CGColor]; 

इस एनिमेशन के दौरान एक UIView पर एक सीमा स्केलिंग से जुड़ी समस्या सुलझा:

समाधान कस्टम उपवर्ग परित्याग करने के लिए और बदले में इस दृष्टिकोण का उपयोग किया गया था।