見出し画像

【じっくりSw1ftUI59】実践編29〜第41章 SwiftUI アニメーションとトランジション(ここは自分で組み込みながら動作検証してね〜〜〜画像じゃ無理)

さてと、いつもの温泉にも入ってさっぱりしたところで〜〜〜前回、

で、

Shapeの基本

についてはやったので〜〜〜今回は

アニメーションとトランジションの基本

に入ってく🕺

毎度、オイラの学びなんざ関係ないって人は、

でサンプルなんかも載ってるみたいだし、そっちでやればいいんじゃね?(知らんけど)
んだば、今回も早速


じっくり第40章を読んでく👓

概要としては、

ま、ビューに動きを出すのがアニメーションやトランジションやっでね

てことを言いたいだけだから、

早速、動かす

まずは、

import SwiftUI

struct Essentials41ContentsView: View {
    var body: some View {
        Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
    }
}

#Preview {
    Essentials41ContentsView()
}
てな感じ

今回用のSwiftUIファイルを追記したところから開始〜〜〜〜🕺

まずは、アニメーションの一番シンプルなやつ

struct Essentials41ContentsView: View {
    var body: some View {
        VStack{
            Essentials41AnimationStandardView()
        }
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("りんごちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1))
            }
            Text("タップ \(tapCount) 回目")
        }
    }
}

#Preview {
    Essentials41ContentsView()
}

てな感じにして〜〜〜

てな感じ😛
これだと、9の倍数のタップ目で初期値に戻るね笑🤣
サンプルコードがそーなってるからしゃーない💦

でこれにスケールを入れてくと〜〜〜

struct Essentials41ContentsView: View {
    var body: some View {
        VStack{
            Essentials41AnimationStandardView()
        }
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1))
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

#Preview {
    Essentials41ContentsView()
}

てな感じにすると

てな感じになって〜〜〜
てな感じ

ここでポイント①:アニメーションの基本

  • linear – アニメーションは指定された期間、一定の速度で実行され、上記のコード例で宣言されたオプション。

  • easyOut – アニメーションは最初は速く始まり、シーケンスの終わりに近づくにつれて遅くなる。

  • easyIn – アニメーション シーケンスはゆっくりと始まり、終わりに近づくにつれて速度が上がる。

  • easyInOut – アニメーションはゆっくりと始まり、加速し、その後再び遅くなる。

てな感じらしい

さらに変化に富んだアニメーションにしたいなら

struct Essentials41ContentsView: View {
    var body: some View {
        ScrollView{
            VStack{
                Essentials41AnimationStandardView()
                Essentials41AnimationVarietyView()
            }
        }
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1))
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1))
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

#Preview {
    Essentials41ContentsView()
}

てな感じにして動かすと、、、

かなり面白い動きになってんだけど、
画像では伝わらないので〜〜〜
ま、実際にここは自分でコードをコピペして動かして藁😝

スケールサイズなしで楽しみたいなら

struct Essentials41AnimationVarietyNoScaleView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("りんごちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1))
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

てな感じで、

にもできる😛

セーフエリアのはみ出しが嫌いな(オイラも日本人ではあるが、几帳面で生真面目な日本人に多い)人なら

struct Essentials41ContentsView: View {
    var body: some View {
        ScrollView{
            VStack{
                Essentials41AnimationStandardView()
                Essentials41AnimationVarietyView()
                Essentials41AnimationVarietyNoScaleView()
            }
        }
        .safeAreaPadding()
    }
}

でセーフエリアパッディングを入れるだけで

てな感じの調整もできる〜〜〜

繰り返しも〜〜〜

struct Essentials41AnimationRepeatingView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("みかんちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}
ここもかなり面白い動きになっているので試してみてね〜〜〜

さらにAutoreverseで遊ぶと

struct Essentials41ContentsView: View {
    var body: some View {
        ScrollView{
            VStack{
                Essentials41AnimationStandardView()
                Essentials41AnimationVarietyView()
                Essentials41AnimationVarietyNoScaleView()
                Essentials41AnimationRepeatingView()
                Essentials41AnimationRepeatingAutoreverseFalseView()
                Essentials41AnimationRepeatingAutoreverseTrueView()
            }
        }
        .safeAreaPadding()
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1))
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1))
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyNoScaleView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("りんごちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1))
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("みかんちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseFalseView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: false),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseTrueView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: true),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

#Preview {
    Essentials41ContentsView()
}
動かしてみると、あんまり違いはないけど
かなり面白い動きに目視でもなってるね😛

明示アニメーション

struct Essentials41ExplicitAnimationView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{ withAnimation(.linear (duration:1.4)){
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
            }
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("ブドウ先輩")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

#Preview {
    Essentials41ContentsView()
}

てな感じでビューを追加して〜〜〜

てな感じ

ジェスチャーなんかで組み合わせてさらに面白そうなこともできそうなので〜〜〜

struct Essentials41ExplicitAnimationTapGestureView: View {
    @State private var redCircle = false
    
    var body: some View {
        Circle()
            .fill(redCircle ? .red: .orange)
            .frame(width: 200, height: 150)
            .onTapGesture {
                withAnimation{
                    redCircle.toggle()
                }
            }
    }
}

てな感じにして、

タップすると
てな感じになった〜〜〜

トグルを使ってアニメーションを効果的に〜〜〜

struct Essentials41AnimationStateAndBindingView: View {
    @State private var visibility = false
    
    var body: some View {
        VStack{
            Toggle(isOn: $visibility.animation(.linear(duration: 4))){
                Text("桃からりんごへ")
            }
            .padding()
            
            if visibility{
                Image("りんごちゃん")
            } else {
                Image("ピーチ姫笑")
            }
        }
    }
}

てな感じで

トグルOff(初期値)
トグルOn
りんごにゆっくり変わった〜〜〜

自動開始アニメーション

struct Essentials41AnimationAutomaticallyStartView: View {
    @State private var rotate: Double = 0
    @State private var isSpinning: Bool = true
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth: 2)
                .foregroundColor(Color.blue)
                .frame(width: 360, height: 360)
            
            Image(systemName: "apple.logo")
                .font(.largeTitle)
                .offset(y: -180)
                .rotationEffect(.degrees(isSpinning ? 0 : 360))
                .animation(Animation.linear(duration: 10.5)
                    .repeatForever(autoreverses: false))
        }
        .onAppear() {
            self.isSpinning.toggle()
        }
    }
}

てな感じで〜〜〜

画像じゃ分からないけど、
クルクル回っております笑👀💦

最後にトランジションを〜〜〜

struct Essentials41TransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.fadeAndMove)
                //エクステンションを使うのでコメントアウト
                //.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
            }
        }
    }
}

extension AnyTransition{
    static var fadeAndMove: AnyTransition{
        AnyTransition.opacity.combined(with: .move(edge: .top))
    }
}

てな感じで遊ばせておいて〜〜〜

一番下のトグルボタンをタップでOnに〜〜〜
あらかわいい😍
ま、画像では分からないけどねん

そして、非対称トランジション〜〜〜

struct Essentials41AsymmetricalTransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.asymmetric(insertion: .scale, removal: .move(edge: .bottom)))
            }
        }
    }
}
一番下のトグルをタップ
面白い感じで現れて
オフにすると、
また面白い感じで静かに去っていく😛

以上。

今回のコードまとめ

struct Essentials41ContentsView: View {
    var body: some View {
        ScrollView{
            VStack{
                Essentials41AnimationStandardView()
                Essentials41AnimationVarietyView()
                Essentials41AnimationVarietyNoScaleView()
                Essentials41AnimationRepeatingView()
                Essentials41AnimationRepeatingAutoreverseFalseView()
                Essentials41AnimationRepeatingAutoreverseTrueView()
                Essentials41ExplicitAnimationView()
                Essentials41ExplicitAnimationTapGestureView()
                Essentials41AnimationStateAndBindingView()
                Essentials41AnimationAutomaticallyStartView()
                Essentials41TransitionView()
                Essentials41AsymmetricalTransitionView()
            }
        }
        .safeAreaPadding()
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyNoScaleView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("りんごちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("みかんちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseFalseView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: false),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseTrueView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: true),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41ExplicitAnimationView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{ withAnimation(.linear (duration:1.4)){
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
            }
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("ブドウ先輩")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41ExplicitAnimationTapGestureView: View {
    @State private var redCircle = false
    
    var body: some View {
        Circle()
            .fill(redCircle ? .red: .orange)
            .frame(width: 200, height: 150)
            .onTapGesture {
                withAnimation{
                    redCircle.toggle()
                }
            }
    }
}

struct Essentials41AnimationStateAndBindingView: View {
    @State private var visibility = false
    
    var body: some View {
        VStack{
            Toggle(isOn: $visibility.animation(.linear(duration: 4))){
                Text("桃からりんごへ")
            }
            .padding()
            
            if visibility{
                Image("りんごちゃん")
            } else {
                Image("ピーチ姫笑")
            }
        }
    }
}

struct Essentials41AnimationAutomaticallyStartView: View {
    @State private var rotate: Double = 0
    @State private var isSpinning: Bool = true
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth: 2)
                .foregroundColor(Color.blue)
                .frame(width: 360, height: 360)
            
            Image(systemName: "apple.logo")
                .font(.largeTitle)
                .offset(y: -180)
                .rotationEffect(.degrees(isSpinning ? 0 : 360))
                .animation(Animation.linear(duration: 10.5)
                    .repeatForever(autoreverses: false),value: rotate)
        }
        .onAppear() {
            self.isSpinning.toggle()
        }
    }
}

struct Essentials41TransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.fadeAndMove)
                //エクステンションを使うのでコメントアウト
                //.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
            }
        }
    }
}

struct Essentials41AsymmetricalTransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.asymmetric(insertion: .scale, removal: .move(edge: .bottom)))
            }
        }
    }
}

extension AnyTransition{
    static var fadeAndMove: AnyTransition{
        AnyTransition.opacity.combined(with: .move(edge: .top))
    }
}

#Preview {
    Essentials41ContentsView()
}

Apple公式

さてと次回は、

今回でTapGestureが出てきたところで、

第42章 SwiftUI でジェスチャー認識機能を使用する

をやってく〜〜〜

記事公開後、

いつもどおり、

でやった操作を〜〜〜

てな
てな
感じで
今回も完了🕺

サンプルコード

◆Essentials41.swift

import SwiftUI
import WebKit

//タイトル
let essentialsChapter41NavigationTitle = "第41章"
let essentialsChapter41Title = "第41章 SwiftUI アニメーションとトランジション"
let essentialsChapter41SubTitle = "第41章 SwiftUI アニメーションとトランジション"

//コード
let codeEssentials41 = """
struct Essentials41ContentsView: View {
    var body: some View {
        ScrollView{
            VStack{
                Essentials41AnimationStandardView()
                Essentials41AnimationVarietyView()
                Essentials41AnimationVarietyNoScaleView()
                Essentials41AnimationRepeatingView()
                Essentials41AnimationRepeatingAutoreverseFalseView()
                Essentials41AnimationRepeatingAutoreverseTrueView()
                Essentials41ExplicitAnimationView()
                Essentials41ExplicitAnimationTapGestureView()
                Essentials41AnimationStateAndBindingView()
                Essentials41AnimationAutomaticallyStartView()
                Essentials41TransitionView()
                Essentials41AsymmetricalTransitionView()
            }
        }
        .safeAreaPadding()
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \\(tapCount) 回目")
                Text("サイズ \\(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \\(tapCount) 回目")
                Text("サイズ \\(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyNoScaleView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("りんごちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \\(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("みかんちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \\(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseFalseView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: false),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \\(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseTrueView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: true),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \\(tapCount) 回目")
        }
    }
}

struct Essentials41ExplicitAnimationView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \\(turnShapeCount) 周目")
            Spacer()
            Button(action:{ withAnimation(.linear (duration:1.4)){
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
            }
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("ブドウ先輩")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
            }
            Spacer()
            HStack{
                Text("タップ \\(tapCount) 回目")
                Text("サイズ \\(scaleSize)")
            }
        }
    }
}

struct Essentials41ExplicitAnimationTapGestureView: View {
    @State private var redCircle = false
    
    var body: some View {
        Circle()
            .fill(redCircle ? .red: .orange)
            .frame(width: 200, height: 150)
            .onTapGesture {
                withAnimation{
                    redCircle.toggle()
                }
            }
    }
}

struct Essentials41AnimationStateAndBindingView: View {
    @State private var visibility = false
    
    var body: some View {
        VStack{
            Toggle(isOn: $visibility.animation(.linear(duration: 4))){
                Text("桃からりんごへ")
            }
            .padding()
            
            if visibility{
                Image("りんごちゃん")
            } else {
                Image("ピーチ姫笑")
            }
        }
    }
}

struct Essentials41AnimationAutomaticallyStartView: View {
    @State private var rotate: Double = 0
    @State private var isSpinning: Bool = true
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth: 2)
                .foregroundColor(Color.blue)
                .frame(width: 360, height: 360)
            
            Image(systemName: "apple.logo")
                .font(.largeTitle)
                .offset(y: -180)
                .rotationEffect(.degrees(isSpinning ? 0 : 360))
                .animation(Animation.linear(duration: 10.5)
                    .repeatForever(autoreverses: false),value: rotate)
        }
        .onAppear() {
            self.isSpinning.toggle()
        }
    }
}

struct Essentials41TransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.fadeAndMove)
                //エクステンションを使うのでコメントアウト
                //.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
            }
        }
    }
}

struct Essentials41AsymmetricalTransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.asymmetric(insertion: .scale, removal: .move(edge: .bottom)))
            }
        }
    }
}

extension AnyTransition{
    static var fadeAndMove: AnyTransition{
        AnyTransition.opacity.combined(with: .move(edge: .top))
    }
}

#Preview {
    Essentials41ContentsView()
}
"""

//ポイント
let pointEssentials41 = """
◆linear – アニメーションは指定された期間、一定の速度で実行され、上記のコード例で宣言されたオプション。

◆easyOut – アニメーションは最初は速く始まり、シーケンスの終わりに近づくにつれて遅くなる。

◆easyIn – アニメーション シーケンスはゆっくりと始まり、終わりに近づくにつれて速度が上がる。

◆easyInOut – アニメーションはゆっくりと始まり、加速し、その後再び遅くなる。
"""

//URL
let urlEssentials41 = "https://note.com/m_kakudo/n/nfff7745f09db"

//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentialsCh41: Identifiable {
    var id: Int
    var title: String
    var view: ViewEnumiOSApp17DevelopmentEssentialsCh41
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentialsCh41{
    case Sec1
}
//各項目に表示するリスト項目
let dataiOSApp17DevelopmentEssentialsCh41: [ListiOSApp17DevelopmentEssentialsCh41] = [
    ListiOSApp17DevelopmentEssentialsCh41(id: 1, title: essentialsChapter41SubTitle, view: .Sec1),
]
struct iOSApp17DevelopmentEssentialsCh41: View {
    var body: some View {
        VStack {
            Divider()
            List (dataiOSApp17DevelopmentEssentialsCh41) { data in
                self.containedViewiOSApp17DevelopmentEssentialsCh41(dataiOSApp17DevelopmentEssentialsCh41: data)
            }
            .edgesIgnoringSafeArea([.bottom])
        }
        .navigationTitle(essentialsChapter41NavigationTitle)
        .navigationBarTitleDisplayMode(.inline)
    }
    //タップ後に遷移先へ遷移させる関数
    func containedViewiOSApp17DevelopmentEssentialsCh41(dataiOSApp17DevelopmentEssentialsCh41: ListiOSApp17DevelopmentEssentialsCh41) -> AnyView {
        switch dataiOSApp17DevelopmentEssentialsCh41.view {
        case .Sec1:
            return AnyView(NavigationLink (destination: Essentials41()) {
                Text(dataiOSApp17DevelopmentEssentialsCh41.title)
            })
        }
    }
}
#Preview {
    iOSApp17DevelopmentEssentialsCh41()
}

struct Essentials41: View {
    var body: some View {
        VStack{
            TabView {
                Essentials41ContentsView()
                    .tabItem {
                        Image(systemName: contentsImageTab)
                        Text(contentsTextTab)
                    }
                Essentials41Code()
                    .tabItem {
                        Image(systemName: codeImageTab)
                        Text(codeTextTab)
                    }
                Essentials41Points()
                    .tabItem {
                        Image(systemName: pointImageTab)
                        Text(pointTextTab)
                    }
                Essentials41WEB()
                    .tabItem {
                        Image(systemName: webImageTab)
                        Text(webTextTab)
                    }
            }
        }
    }
}
#Preview {
    Essentials41()
}

struct Essentials41Code: View {
    var body: some View {
        ScrollView{
            Text(codeEssentials41)
        }
    }
}
#Preview {
    Essentials41Code()
}
struct Essentials41Points: View {
    var body: some View {
        ScrollView{
            Text(pointEssentials41)
        }
    }
}
#Preview {
    Essentials41Points()
}
struct Essentials41WebView: UIViewRepresentable {
    let searchURL: URL
    func makeUIView(context: Context) -> WKWebView {
        let view = WKWebView()
        let request = URLRequest(url: searchURL)
        view.load(request)
        return view
    }
    func updateUIView(_ uiView: WKWebView, context: Context) {
        
    }
}
struct Essentials41WEB: View {
    private var url:URL = URL(string: urlEssentials41)!
    var body: some View {Essentials41WebView(searchURL: url)
    }
}
#Preview {
    Essentials41WEB()
}

struct Essentials41ContentsView: View {
    var body: some View {
        ScrollView{
            VStack{
                Essentials41AnimationStandardView()
                Essentials41AnimationVarietyView()
                Essentials41AnimationVarietyNoScaleView()
                Essentials41AnimationRepeatingView()
                Essentials41AnimationRepeatingAutoreverseFalseView()
                Essentials41AnimationRepeatingAutoreverseTrueView()
                Essentials41ExplicitAnimationView()
                Essentials41ExplicitAnimationTapGestureView()
                Essentials41AnimationStateAndBindingView()
                Essentials41AnimationAutomaticallyStartView()
                Essentials41TransitionView()
                Essentials41AsymmetricalTransitionView()
            }
        }
        .safeAreaPadding()
    }
}

struct Essentials41AnimationStandardView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.linear(duration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("りんごちゃん")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41AnimationVarietyNoScaleView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("りんごちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(.spring(response: 1, dampingFraction: 0.3,blendDuration: 0.1),value: rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("みかんちゃん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseFalseView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: false),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41AnimationRepeatingAutoreverseTrueView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                }
            }){
                Image("バナナさん")
                    .rotationEffect(.degrees(rotate))
                    .animation(Animation.linear(duration: 0.1).repeatCount(10,autoreverses: true),value:rotate)
                    .padding()
            }
            Spacer()
            Text("タップ \(tapCount) 回目")
        }
    }
}

struct Essentials41ExplicitAnimationView: View {
    @State private var rotate: Double = 0
    @State private var tapCount: Int = 0
    @State private var turnShapeCount: Int = 0
    @State private var scaleSize: CGFloat = 1
    
    var body: some View {
        VStack{
            Text("回転 \(turnShapeCount) 周目")
            Spacer()
            Button(action:{ withAnimation(.linear (duration:1.4)){
                self.rotate = (self.rotate < 360 ? self.rotate + 45 : 0)
                tapCount = tapCount + 1
            }
                self.scaleSize = (self.scaleSize < 3.1 ? self.scaleSize + 0.3 : 0)
                if (tapCount % 9 == 0){
                    turnShapeCount = turnShapeCount + 1
                    scaleSize = 1
                }
            }){
                Image("ブドウ先輩")
                    .scaleEffect(scaleSize)
                    .rotationEffect(.degrees(rotate))
            }
            Spacer()
            HStack{
                Text("タップ \(tapCount) 回目")
                Text("サイズ \(scaleSize)")
            }
        }
    }
}

struct Essentials41ExplicitAnimationTapGestureView: View {
    @State private var redCircle = false
    
    var body: some View {
        Circle()
            .fill(redCircle ? .red: .orange)
            .frame(width: 200, height: 150)
            .onTapGesture {
                withAnimation{
                    redCircle.toggle()
                }
            }
    }
}

struct Essentials41AnimationStateAndBindingView: View {
    @State private var visibility = false
    
    var body: some View {
        VStack{
            Toggle(isOn: $visibility.animation(.linear(duration: 4))){
                Text("桃からりんごへ")
            }
            .padding()
            
            if visibility{
                Image("りんごちゃん")
            } else {
                Image("ピーチ姫笑")
            }
        }
    }
}

struct Essentials41AnimationAutomaticallyStartView: View {
    @State private var rotate: Double = 0
    @State private var isSpinning: Bool = true
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth: 2)
                .foregroundColor(Color.blue)
                .frame(width: 360, height: 360)
            
            Image(systemName: "apple.logo")
                .font(.largeTitle)
                .offset(y: -180)
                .rotationEffect(.degrees(isSpinning ? 0 : 360))
                .animation(Animation.linear(duration: 10.5)
                    .repeatForever(autoreverses: false),value: rotate)
        }
        .onAppear() {
            self.isSpinning.toggle()
        }
    }
}

struct Essentials41TransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.fadeAndMove)
                //エクステンションを使うのでコメントアウト
                //.transition(AnyTransition.opacity.combined(with: .move(edge: .top)))
            }
        }
    }
}

struct Essentials41AsymmetricalTransitionView: View {
    
    @State private var isButtonVisible: Bool = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $isButtonVisible.animation(.linear(duration: 1.5))){
                Text("果物ちゃんたち現れり🕺")
            }
            .padding()
            
            if isButtonVisible {
                Button(action:{}){
                    Text("🍎🍊🍌🍇🍑")
                }
                .font(.largeTitle)
                .transition(.asymmetric(insertion: .scale, removal: .move(edge: .bottom)))
            }
        }
    }
}

extension AnyTransition{
    static var fadeAndMove: AnyTransition{
        AnyTransition.opacity.combined(with: .move(edge: .top))
    }
}

#Preview {
    Essentials41ContentsView()
}

◆EssentialsMenu.swift

//フレームワーク
import SwiftUI
import WebKit

//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentials: Identifiable {
    var id: Int
    var title: String
    var view: ViewEnumiOSApp17DevelopmentEssentials
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentials {
    case Ch1
    //じっくり13で追加
    case Ch2
    //じっくり14で追加
    case Ch3
    //じっくり15で追加
    case Ch4
    //じっくり16で追加
    case Ch5
    //じっくり17で追加
    case Ch6
    //じっくり18で追加
    case Ch7
    //じっくり19で追加
    case Ch8
    //じっくり20、21で追加
    case Ch9
    //じっくり22、23で追加
    case Ch10
    //じっくり24で追加
    case Ch11
    //じっくり25で追加
    case Ch12
    //じっくり26で追加
    case Ch13
    //じっくり27,28で追加
    case Ch14
    //じっくり29で追加
    case Ch15
    //じっくり31で追加
    case Ch16
    //じっくり32で追加
    case Ch17
    //じっくり33で追加
    case Ch18
    //じっくり34で追加
    case Ch19
    //じっくり35で追加
    case Ch20
    //じっくり36で追加
    case Ch21
    //じっくり37で追加
    case Ch22
    //じっくり40で追加
    case Ch23
    //じっくり41で追加
    case Ch24
    //じっくり43で追加
    case Ch25
    //じっくり44で追加
    case Ch26
    //じっくり45で追加
    case Ch27
    //じっくり46で追加
    case Ch28
    //じっくり47で追加
    case Ch29
    //じっくり48で追加
    case Ch30
    //じっくり49で追加
    case Ch31
    //じっくり50で追加
    case Ch32
    //じっくり51で追加
    case Ch33
    //じっくり52で追加
    case Ch34
    //じっくり53で追加
    case Ch35
    //じっくり54で追加
    case Ch36
    //じっくり55で追加
    case Ch37
    //じっくり56で追加
    case Ch38
    //じっくり57で追加
    case Ch39
    //じっくり58で追加
    case Ch40
    //じっくり59で追加
    case Ch41
}
//各項目に表示する文字列
let dataiOSApp17DevelopmentEssentials: [ListiOSApp17DevelopmentEssentials] = [
    ListiOSApp17DevelopmentEssentials(id: 1, title: essentialsChapter1Title, view: .Ch1),
    //じっくり13で追加
    ListiOSApp17DevelopmentEssentials(id: 2, title: essentialsChapter2Title, view: .Ch2),
    //じっくり13で追加
    ListiOSApp17DevelopmentEssentials(id: 3, title: essentialsChapter3Title, view: .Ch3),
    //じっくり15で追加
    ListiOSApp17DevelopmentEssentials(id: 4, title: essentialsChapter4Title, view: .Ch4),
    //じっくり16で追加
    ListiOSApp17DevelopmentEssentials(id: 5, title: essentialsChapter5Title, view: .Ch5),
    //じっくり17で追加
    ListiOSApp17DevelopmentEssentials(id: 6, title: essentialsChapter6Title, view: .Ch6),
    //じっくり18で追加
    ListiOSApp17DevelopmentEssentials(id: 7, title: essentialsChapter7Title, view: .Ch7),
    //じっくり19で追加
    ListiOSApp17DevelopmentEssentials(id: 8, title: essentialsChapter8Title, view: .Ch8),
    //じっくり20、21で追加
    ListiOSApp17DevelopmentEssentials(id: 9, title: essentialsChapter9Title, view: .Ch9),
    //じっくり22、23で追加
    ListiOSApp17DevelopmentEssentials(id: 10, title: essentialsChapter10Title, view: .Ch10),
    //じっくり24で追加
    ListiOSApp17DevelopmentEssentials(id: 11, title: essentialsChapter11Title, view: .Ch11),
    //じっくり25で追加
    ListiOSApp17DevelopmentEssentials(id: 12, title: essentialsChapter12Title, view: .Ch12),
    //じっくり26で追加
    ListiOSApp17DevelopmentEssentials(id: 13, title: essentialsChapter13Title, view: .Ch13),
    //じっくり27,28で追加
    ListiOSApp17DevelopmentEssentials(id: 14, title: essentialsChapter14Title, view: .Ch14),
    //じっくり29で追加
    ListiOSApp17DevelopmentEssentials(id: 15, title: essentialsChapter15Title, view: .Ch15),
    //じっくり31で追加
    ListiOSApp17DevelopmentEssentials(id: 16, title: essentialsChapter16Title, view: .Ch16),
    //じっくり32で追加
    ListiOSApp17DevelopmentEssentials(id: 17, title: essentialsChapter17Title, view: .Ch17),
    //じっくり33で追加
    ListiOSApp17DevelopmentEssentials(id: 18, title: essentialsChapter18Title, view: .Ch18),
    //じっくり34で追加
    ListiOSApp17DevelopmentEssentials(id: 19, title: essentialsChapter19Title, view: .Ch19),
    //じっくり35で追加
    ListiOSApp17DevelopmentEssentials(id: 20, title: essentialsChapter20Title, view: .Ch20),
    //じっくり36で追加
    ListiOSApp17DevelopmentEssentials(id: 21, title: essentialsChapter21Title, view: .Ch21),
    //じっくり37で追加
    ListiOSApp17DevelopmentEssentials(id: 22, title: essentialsChapter22Title, view: .Ch22),
    //じっくり40で追加
    ListiOSApp17DevelopmentEssentials(id: 23, title: essentialsChapter23Title, view: .Ch23),
    //じっくり41で追加
    ListiOSApp17DevelopmentEssentials(id: 24, title: essentialsChapter24Title, view: .Ch24),
    //じっくり43で追加
    ListiOSApp17DevelopmentEssentials(id: 25, title: essentialsChapter25Title, view: .Ch25),
    //じっくり44で追加
    ListiOSApp17DevelopmentEssentials(id: 26, title: essentialsChapter26Title, view: .Ch26),
    //じっくり45で追加
    ListiOSApp17DevelopmentEssentials(id: 27, title: essentialsChapter27Title, view: .Ch27),
    //じっくり46で追加
    ListiOSApp17DevelopmentEssentials(id: 28, title: essentialsChapter28Title, view: .Ch28),
    //じっくり47で追加
    ListiOSApp17DevelopmentEssentials(id: 29, title: essentialsChapter29Title, view: .Ch29),
    //じっくり48で追加
    ListiOSApp17DevelopmentEssentials(id: 30, title: essentialsChapter30Title, view: .Ch30),
    //じっくり49で追加
    ListiOSApp17DevelopmentEssentials(id: 31, title: essentialsChapter31Title, view: .Ch31),
    //じっくり50で追加
    ListiOSApp17DevelopmentEssentials(id: 32, title: essentialsChapter32Title, view: .Ch32),
    //じっくり51で追加
    ListiOSApp17DevelopmentEssentials(id: 33, title: essentialsChapter33Title, view: .Ch33),
    //じっくり52で追加
    ListiOSApp17DevelopmentEssentials(id: 34, title: essentialsChapter34Title, view: .Ch34),
    //じっくり53で追加
    ListiOSApp17DevelopmentEssentials(id: 35, title: essentialsChapter35Title, view: .Ch35),
    //じっくり54で追加
    ListiOSApp17DevelopmentEssentials(id: 36, title: essentialsChapter36Title, view: .Ch36),
    //じっくり55で追加
    ListiOSApp17DevelopmentEssentials(id: 37, title: essentialsChapter37Title, view: .Ch37),
    //じっくり56で追加
    ListiOSApp17DevelopmentEssentials(id: 38, title: essentialsChapter38Title, view: .Ch38),
    //じっくり57で追加
    ListiOSApp17DevelopmentEssentials(id: 39, title: essentialsChapter39Title, view: .Ch39),
    //じっくり58で追加
    ListiOSApp17DevelopmentEssentials(id: 40, title: essentialsChapter40Title, view: .Ch40),
    //じっくり59で追加
    ListiOSApp17DevelopmentEssentials(id: 41, title: essentialsChapter41Title, view: .Ch41),
]

struct iOSApp17DevelopmentEssentials: View {
    var body: some View {
        VStack {
            Divider()
            List (dataiOSApp17DevelopmentEssentials) { data in
                self.containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: data)
            }
            .edgesIgnoringSafeArea([.bottom])
        }
        .navigationTitle("iOS開発の章目次")
        .navigationBarTitleDisplayMode(.inline)
    }
    //タップ後に遷移先へ遷移させる関数
    func containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: ListiOSApp17DevelopmentEssentials) -> AnyView {
        switch dataiOSApp17DevelopmentEssentials.view {
        case .Ch1:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh1()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり13で追加
        case .Ch2:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh2()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり13で追加
        case .Ch3:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh3()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり15で追加
        case .Ch4:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh4()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり16で追加
        case .Ch5:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh5()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり17で追加
        case .Ch6:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh6()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり18で追加
        case .Ch7:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh7()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり19で追加
        case .Ch8:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh8()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり20、21で追加
        case .Ch9:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh9()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり22、23で追加
        case .Ch10:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh10()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり24で追加
        case .Ch11:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh11()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり25で追加
        case .Ch12:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh12()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり26で追加
        case .Ch13:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh13()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり27,28で追加
        case .Ch14:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh14()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり29で追加
        case .Ch15:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh15()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり31で追加
        case .Ch16:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh16()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり32で追加
        case .Ch17:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh17()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり33で追加
        case .Ch18:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh18()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり34で追加
        case .Ch19:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh19()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり35で追加
        case .Ch20:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh20()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり36で追加
        case .Ch21:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh21()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり37で追加
        case .Ch22:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh22()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり40で追加
        case .Ch23:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh23()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり41で追加
        case .Ch24:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh24()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり43で追加
        case .Ch25:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh25()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり44で追加
        case .Ch26:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh26()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり45で追加
        case .Ch27:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh27()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり46で追加
        case .Ch28:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh28()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり47で追加
        case .Ch29:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh29()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり48で追加
        case .Ch30:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh30()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり49で追加
        case .Ch31:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh31()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり50で追加
        case .Ch32:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh32()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり51で追加
        case .Ch33:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh33()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり52で追加
        case .Ch34:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh34()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり53で追加
        case .Ch35:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh35()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり54で追加
        case .Ch36:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh36()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり55で追加
        case .Ch37:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh37()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり56で追加
        case .Ch38:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh38()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり57で追加
        case .Ch39:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh39()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり58で追加
        case .Ch40:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh40()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり59で追加
        case .Ch41:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh41()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
        }
    }
}

#Preview {
    iOSApp17DevelopmentEssentials()
}

以上。

実際の動きが知りたければ、当たり前の話だけど、

(今回のコードをコピペでもいいから)
自分でちゃんとコードを組んで、プレビューで確認してみてね👀💦

元々、マガジンの最初に、

しおり機能なんかもないし、動画ではなく記事でやる

って予め断って始めてるから、

流石に、動画でやってほしいは少し甘え過ぎだと思う。。。
きちんと動くコードを無料で公開してもらってるんだから。
ま、そんなトチ狂ったクライアントが業務ではよく居るけど、
あくまでも趣味なので〜〜〜


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