見出し画像

SwiftUIでいこう!- スタンフォード大学Lecture 8: Animation Demonstration

カードを並べて2枚ずつ開けて行ってマッチすればカードを消していくという操作ができてきました。次にアニメーション をつけて見栄えを良くしていきます。

最終的にはカードの配置をする画面、カードを配るボタン、シャッフルボタン、リスタートのボタンを以下のように配置していきその内容を書いていきます。

画像1

gameBodyにはランダムに並んだカード。

deckBodyにはカードを配るように一枚が配置してあり、HStackにはランダムにカードを配置するShuffleボタンと再スタートするrestartボタンを配置しています。

表示部分のコードですが、

 var body: some View {
       VStack{
           gameBody
           deckBody
           HStack{
               shuffle
               Spacer()
               restart
           }
           .padding(.horizontal)
         
       }
       .padding()
   }

VStack{}で縦に積んで、最後 HStack{}で平積みしています。

それぞれのコードは gameBody

var gameBody:some View{
       AspectVGrid(items:game.cards,aspectRatio:2/3) {card in
           if isundeal(card) || card.isMatched && !card.isFaceUp{
               Color.clear
           }else{
               CardView(card:card)
                   .matchedGeometryEffect(id:card.id, in :dealingNamespace)
                   .padding(4)
                   .transition(AnyTransition.asymmetric(insertion: .identity, removal:.scale))
                   .zIndex(zIndex(of:card))
                   .onTapGesture {
                       withAnimation{
                           game.choose(card)
                       }
                       
                    }
           }
       }
     
       .foregroundColor(CardConstants.color)
   }

トランジションでアニメーション 効果を出しています。

.transition(AnyTransition.asymmetric(insertion: .identity, removal:.scale))

asymmetricメソッドを使うと、Viewの表示と非表示で異なるトランジション効果を適用できます。

第1引数(insertion)で、表示時のトランジション効果を指定。
第2引数(removal)で、非表示時のトランジションを指定します。

View間で以下を使って同調させてアニメーション します。

.matchedGeometryEffect(id:card.id, in :dealingNamespace)

ちなみに、アニメーション には適用するものにより以下

Viewの形状やエフェクトの変化に対して、アニメーションを適用するには、animationモディファイア
Viewではなく、特定のプロパティの変化対してアニメーションを適用するには、withAnimation関数

を使い分けます。

deckBody

 var deckBody:some View{
       ZStack{
           ForEach(game.cards.filter(isundeal)){card in
               CardView(card: card)
                   .matchedGeometryEffect(id:card.id, in :dealingNamespace)
                   .transition(AnyTransition.asymmetric(insertion:.opacity , removal: .identity))
           }
       }
       .frame(width: CardConstants.undealWidth, height: CardConstants.undealHeight)
       .foregroundColor(CardConstants.color)
       .onTapGesture {
       
           for card in game.cards{
               
               withAnimation(dealAnimation(for: card)){
                   deal(card)
               }
           }
           
       }
   }

shuffle

   var shuffle:some View{
       Button("Shuffle"){
           withAnimation{
               game.shuffle()
           }
          
       }
   }

game.shuffle()は

  func shuffle(){
       model.shuffle()
   }
    mutating func shuffle(){
       cards.shuffle()
   }

に紐付いていいます。

restart

 var restart:some View{
       Button("restart"){
           withAnimation{
               dealt = []
               game.restart()
           }
       }
   }

game.restart()も同様に

 func restart(){
       model = EmojiGame.createMemoryGame()
   }

となっており同じファイルの上部で定義している

 static func createMemoryGame() -> Game<String>{
       Game<String>(numberOfpairOfCards:10){pairIndex in
           EmojiGame.emojis[pairIndex]
       }
   }

に紐付いています。

これらをサポートする構造体を作って使っていきます。

 private struct CardConstants{
       static let color = Color.red
       static let aspectRatio:CGFloat = 2/3
       static let dealDuration:Double = 0.5
       static let totalDealDuration:Double = 2
       static let undealHeight:CGFloat = 90
       static let undealWidth:CGFloat = undealHeight * aspectRatio
       
   }


この記事が気に入ったらサポートをしてみませんか?