ここでは、ブロックを使用して一連の操作を制御する便利さを見ていきます。
5. UIViewアニメーション、アニメーションのシーケンス。
まず、アニメーション(ブロックなし)を使用してボタンを移動する簡単な例を作成します。 次に、アニメーションの順序を変更して、コードに必要な変更を確認します。
最初のステップ
「ボタンを上に動かす」、「...下げる」、「...右」、「...左」の4つのアニメーションを作成しましょう。 したがって、メソッド:moveUpAnimation、moveDownAnimation、moveRightAnimation、およびmoveLeftAnimation。
アニメーションの1つの例を次に示します。
static const CGFloat button_offset_ = 20.f;
-( void )moveUpAnimation
{
[ UIView beginAnimations: nil context: nil ];
CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );
[ UIView commitAnimations ];
}
* This source code was highlighted with Source Code Highlighter .
次に、これらの4つのアニメーションがボタンを画面上で時計回りに動かすようにコードを記述します。 これらのアニメーションの実行は単純にシーケンシャルです:
-(IBAction)animateButtonAction:( id )sender_
{
[ self moveUpAnimation ];
[ self moveRightAnimation ];
[ self moveDownAnimation ];
[ self moveLeftAnimation ];
}
* This source code was highlighted with Source Code Highlighter .
何も与えません。 最新のアニメーションのみが表示されます。 正しい解決策は、前のアニメーションが完了したら次のアニメーションを開始することです。 計画を実装するには、アニメーションデリゲートを確立し、アニメーションのコンテキストで、次のアニメーションに関する情報を送信する必要があります。 デリゲートで、コンテキストから次のアニメーションを実行します。 たとえば、次のように:
// ()
@ interface JFFNextAnimation : NSObject
@property ( nonatomic, retain ) UIViewAnimationsExampleViewController* controller;
@property ( nonatomic, assign ) SEL nextAnimationSelector;
@end
-( void )moveUpAnimation
{
JFFNextAnimation* next_animation_ = [ JFFNextAnimation new ];
//
next_animation_.controller = self;
next_animation_.nextAnimationSelector = @selector( moveRightAnimation );
//
[ UIView beginAnimations: nil context: next_animation_ ];
CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );
//
[ UIView setAnimationDelegate: self ];
[ UIView commitAnimations ];
}
// moveDownAnimation, moveRightAnimation moveLeftAnimation
-( void )animationDidStop:( NSString* )animation_id_
finished:( NSNumber* )finished_
context:( void * )context_
{
//
JFFNextAnimation* context_object_ = context_;
[ context_object_.controller performSelector: context_object_.nextAnimationSelector ];
[ context_object_ release ];
}
-(IBAction)animateButtonAction:( id )sender_
{
//
[ self moveUpAnimation ];
}
* This source code was highlighted with Source Code Highlighter .
これですべてが正常に機能します。 しかし、アニメーションでボタンを時計回りではなく反時計回りに動かしたいとします。 次に、各メソッドmoveUpAnimation、moveDownAnimation、moveRightAnimation、moveLeftAnimationのコードを変更する必要があります。 これはあまり便利ではないので、このタスクをより簡単に解決できるようにコードを書き直します。
第二段階
アニメーションの呼び出しシーケンスを変更します。 まず、次のアニメーションのセレクターではなく、現在のアニメーションの後に実行する必要があるすべてのアニメーションをコンテキストに保存しましょう。
@ interface JFFNextAnimation : NSObject
@property ( nonatomic, retain ) UIViewAnimationsExampleViewController* controller;
// ,
@property ( nonatomic, retain ) NSMutableArray* nextAnimations;
@end
* This source code was highlighted with Source Code Highlighter .
メソッドmoveUpAnimation、moveDownAnimation、moveRightAnimationおよびmoveLeftAnimationのコードも変更する必要があります。
// ,
//
-( void )moveUpAnimationWithNextAnimation:( JFFNextAnimation* )next_animation_
{
[ UIView beginAnimations: nil context: next_animation_ ];
CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );
[ UIView setAnimationDelegate: self ];
[ UIView commitAnimations ];
}
// moveDownAnimation, moveRightAnimation moveLeftAnimation
* This source code was highlighted with Source Code Highlighter .
アニメーションデリゲートもやり直す必要があります。
-( void )animationDidStop:( NSString* )animation_id_
finished:( NSNumber* )finished_
context:( void * )context_
{
// -
if ( !context_ )
return ;
JFFNextAnimation* context_object_ = context_;
//
NSString* next_animation_string_ = [ context_object_.nextAnimations objectAtIndex: 0 ];
next_animation_string_ = [ [ next_animation_string_ retain ] autorelease ];
//
[ context_object_.nextAnimations removeObjectAtIndex: 0 ];
SEL next_animation_sel_ = NSSelectorFromString( next_animation_string_ );
if ( [ context_object_.nextAnimations count ] == 0 )
{
//
//
[ context_object_.controller performSelector: next_animation_sel_
withObject: nil ];
//
[ context_object_ release ];
}
else
{
//
[ context_object_.controller performSelector: next_animation_sel_
withObject: context_object_ ];
}
}
* This source code was highlighted with Source Code Highlighter .
そしてもちろん、私たちが取り組んだ結果は、アニメーションのシーケンスを簡単に変更できるようになりました。
-(IBAction)animateButtonAction:( id )sender_
{
JFFNextAnimation* next_animation_ = [ JFFNextAnimation new ];
next_animation_.controller = self;
//
next_animation_.nextAnimations = [ NSMutableArray arrayWithObjects:
@"moveUpAnimationWithNextAnimation:"
, @"moveLeftAnimationWithNextAnimation:"
, @"moveDownAnimationWithNextAnimation:"
, nil ];
//
[ self moveRightAnimationWithNextAnimation: next_animation_ ];
}
* This source code was highlighted with Source Code Highlighter .
結果のすべてのコードはgihubにあります。
要約:
もちろん、トピックの冒頭でタスクを解決しました。 しかし、ソリューションの価格は高く、コードは安全ではなく(セレクターではなく文字列)、複雑です(セレクターデリゲートとコンテキストメモリ管理の混乱するロジック)、エラーが発生しやすいです。 (もちろん、トピックの名前に応じて)ブロックを使用して、状況を部分的に修正しようとします。 など...
第三段階
ブロックAPIを使用してアニメーションを書き換えます。 まず、アニメーションコンテキストクラス(JFFNextAnimationおよびデリゲートアニメーションメソッド)を削除できます。これらはもはや役に立ちません。 moveUpAnimationメソッドはこれを単純化します:
-(JFFSimpleBlock)moveUpAnimationBlock
{
return [ [ ^
{
CGFloat new_y_ = self.animatedButton.frame.origin.y
- ( self.view.frame.size.height - button_offset_ * 2 )
+ self.animatedButton.frame.size.height;
self.animatedButton.frame = CGRectMake( self.animatedButton.frame.origin.x
, new_y_
, self.animatedButton.frame.size.width
, self.animatedButton.frame.size.height );
} copy ] autorelease ];
}
* This source code was highlighted with Source Code Highlighter .
ヘルパーメソッドを追加して、アニメーションを実行するブロックを作成します。
//
//
-(JFFSimpleBlock)animationBlockWithAnimations:( JFFSimpleBlock )animations_
completion:( JFFSimpleBlock )completion_
{
// ,
//
completion_ = [ [ completion_ copy ] autorelease ];
return [ [ ^
{
[ UIView animateWithDuration: 0.2
animations: animations_
completion: ^( BOOL finished_ )
{
if ( completion_ )
completion_();
} ];
} copy ] autorelease ];
}
* This source code was highlighted with Source Code Highlighter .
そして、アニメーション自体のシーケンスを定義します。
-(IBAction)animateButtonAction:( id )sender_
{
// , ,
JFFSimpleBlock move_left_animation_block_ = [ self moveLeftAnimationBlock ];
//completion: - ,
move_left_animation_block_ = [ self animationBlockWithAnimations: move_left_animation_block_
completion: nil ];
JFFSimpleBlock move_down_animation_block_ = [ self moveDownAnimationBlock ];
//completion: - - "move left"
move_down_animation_block_ = [ self animationBlockWithAnimations: move_down_animation_block_
completion: move_left_animation_block_ ];
JFFSimpleBlock move_right_animation_block_ = [ self moveRightAnimationBlock ];
//completion: - - "move down"
move_right_animation_block_ = [ self animationBlockWithAnimations: move_right_animation_block_
completion: move_down_animation_block_ ];
//
JFFSimpleBlock move_up_animation_block_ = [ self moveUpAnimationBlock ];
//completion: - - "move right"
move_up_animation_block_ = [ self animationBlockWithAnimations: [ self moveUpAnimationBlock ]
completion: move_right_animation_block_ ];
//
move_up_animation_block_();
}
* This source code was highlighted with Source Code Highlighter .
ここで、前の例(ブロックのないアニメーション)のように、アニメーションの呼び出しのシーケンスを変更しようとします。 幸いなことに、これは最初の例ほど難しくはありませんが、これで終わりではありません。
第4ステップ
少し進んで、 sequenceOfAsyncOperations関数を見てみましょう。 この関数は、非同期操作を引数の形式で抽象化するいくつかのブロックを取り、新しいブロックを返します。呼び出されると、指定された順序でこの関数の引数のブロックを実行します。 非同期操作ブロック自体はJFFAsyncOperation型であるため、この型に応じてanimationBlockWithAnimations:関数をわずかに変更します。
-(JFFAsyncOperation)animationBlockWithAnimations:( JFFSimpleBlock )animations_
{
return [ [ ^( JFFAsyncOperationProgressHandler progress_callback_
, JFFCancelHandler cancel_callback_
, JFFDidFinishAsyncOperationHandler done_callback_ )
{
done_callback_ = [ [ done_callback_ copy ] autorelease ];
[ UIView animateWithDuration: 0.2
animations: animations_
completion: ^( BOOL finished_ )
{
if ( done_callback_ )
done_callback_( [ NSNull null ], nil );
} ];
//
JFFCancelAsyncOpration cancel_block_ = ^{ /*do nothing*/ };
return [ [ cancel_block_ copy ] autorelease ];
} copy ] autorelease ];
}
* This source code was highlighted with Source Code Highlighter .
そして結果が得られます:
-(IBAction)animateButtonAction:( id )sender_
{
JFFSimpleBlock move_right_animation_block_ = [ self moveRightAnimationBlock ];
JFFAsyncOperation move_right_async_block_ = [ self animationBlockWithAnimations: move_right_animation_block_ ];
JFFSimpleBlock move_up_animation_block_ = [ self moveUpAnimationBlock ];
JFFAsyncOperation move_up_async_block_ = [ self animationBlockWithAnimations: move_up_animation_block_ ];
JFFSimpleBlock move_left_animation_block_ = [ self moveLeftAnimationBlock ];
JFFAsyncOperation move_left_async_block_ = [ self animationBlockWithAnimations: move_left_animation_block_ ];
JFFSimpleBlock move_down_animation_block_ = [ self moveDownAnimationBlock ];
JFFAsyncOperation move_down_async_block_ = [ self animationBlockWithAnimations: move_down_animation_block_ ];
//
// - sequenceOfAsyncOperations
JFFAsyncOperation result_animation_block_ = sequenceOfAsyncOperations(
move_right_async_block_
, move_up_async_block_
, move_left_async_block_
, move_down_async_block_
, nil );
// ,
result_animation_block_( nil, nil, nil );
}
* This source code was highlighted with Source Code Highlighter .
gihubでは 、 結果のコードをすべて見ることができます。
今のところすべてです。 ご清聴ありがとうございました。 このトピックが興味深い場合は、次の記事でブロックを使用した非同期操作の管理についてお話します。