100 Days of SwiftUI(DAY 89(Project17 part 4))

スワイプしながらビューに色を付ける
Coloring views as we swipe

//
//  CardView.swift
//  Flashzilla
//
//  Created by Naoki Abe on 2020/09/09.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import SwiftUI

struct CardView: View {
    @State private var isShowingAnswer = false
    @State private var offset = CGSize.zero
    @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
    
    let card: Card
    var removal: (() -> Void)? = nil
    
    var body: some View {
        ZStack {
            /*
            RoundedRectangle(cornerRadius: 25, style: .continuous)
                //.fill(Color.white)
                .fill(
                    Color.white
                        .opacity(1 - Double(abs(offset.width / 50)))
                )
                .background(
                    RoundedRectangle(cornerRadius: 25, style: .continuous)
                        .fill(offset.width > 0 ? Color.green : Color.red)
                )
                .shadow(radius: 10)
        */
                RoundedRectangle(cornerRadius: 25, style: .continuous)
                    .fill(
                        differentiateWithoutColor
                            ? Color.white
                            : Color.white
                                .opacity(1 - Double(abs(offset.width / 50)))

                    )
                    .background(
                        differentiateWithoutColor
                            ? nil
                            : RoundedRectangle(cornerRadius: 25, style: .continuous)
                                .fill(offset.width > 0 ? Color.green : Color.red)
                    )
                    .shadow(radius: 10)
            VStack {
                Text(card.prompt)
                    .font(.largeTitle)
                    .foregroundColor(.black)

                if isShowingAnswer {
                    Text(card.answer)
                        .font(.title)
                        .foregroundColor(.gray)
                }
            }
            .padding(20)
            .multilineTextAlignment(.center)
        }
        .frame(width: 450, height: 250)
        .rotationEffect(.degrees(Double(offset.width / 5)))
        .offset(x: offset.width * 5, y: 0)
        .opacity(2 - Double(abs(offset.width / 50)))
        .gesture(
            DragGesture()
                .onChanged { gesture in
                    self.offset = gesture.translation
                }

                .onEnded { _ in
                    if abs(self.offset.width) > 100 {
                        // remove the card
                        self.removal?()
                    } else {
                        self.offset = .zero
                    }
                }
        )
        .onTapGesture {
            self.isShowingAnswer.toggle()
        }
    }
}

struct CardView_Previews: PreviewProvider {
    static var previews: some View {
        CardView(card: Card.example)
    }
}

//
//  ContentView.swift
//  Flashzilla
//
//  Created by Naoki Abe on 2020/09/03.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import SwiftUI
//-------------
struct ContentView: View {
    @State private var cards = [Card](repeating: Card.example, count: 10)
    @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
    
    func removeCard(at index: Int) {
        cards.remove(at: index)
    }

    
    var body: some View {
        ZStack {
            Image("background")
            .resizable()
            .scaledToFill()
            .edgesIgnoringSafeArea(.all)
            VStack {
                ZStack {
                    ForEach(0..<cards.count, id: \.self) { index in
                        CardView(card: self.cards[index]) {
                           withAnimation {
                               self.removeCard(at: index)
                           }
                        }
                        .stacked(at: index, in: self.cards.count)
                    }
                }
                
            }
           
            //if differentiateWithoutColor {
                VStack {
                    Spacer()

                    HStack {
                        Image(systemName: "xmark.circle")
                            .padding()
                            .background(Color.black.opacity(0.7))
                            .clipShape(Circle())
                        //Spacer()
                        Image(systemName: "checkmark.circle")
                            .padding()
                            .background(Color.black.opacity(0.7))
                            .clipShape(Circle())
                    }
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                }
            //}
          
        }
        
    }
    
}
//-------------
extension View {
    func stacked(at position: Int, in total: Int) -> some View {
        //let offset = CGFloat(total - position)
        //配列内の場所ごとにビューを10ポイント押し下げます(0、次に10、20、30など)
        // return self.offset(CGSize(width: 0, height: position * 10))
        let offset = CGFloat(total - position)
        return self.offset(CGSize(width: 0, height: offset * 10))
    }
}
//-------------
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


タイマーでカウントダウン
Counting down with a Timer
2つの新しいプロパティを作成します。1秒にtimeRemaining1回起動するタイマー自体と、タイマーが起動するたびに1を減算するプロパティです。これにより、現在のアプリの実行に残っている秒数を示すことができます。

//
//  ContentView.swift
//  Flashzilla
//
//  Created by Naoki Abe on 2020/09/03.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import SwiftUI
//-------------
struct ContentView: View {
    @State private var cards = [Card](repeating: Card.example, count: 10)
    @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
    @State private var timeRemaining = 100
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var isActive = true
    
    func removeCard(at index: Int) {
        cards.remove(at: index)
    }

    
    var body: some View {
        ZStack {
            Image("background")
            .resizable()
            .scaledToFill()
            .edgesIgnoringSafeArea(.all)
            VStack {
                //timeRemaining1
                Text("Time: \(timeRemaining)")
                .font(.largeTitle)
                .foregroundColor(.white)
                .padding(.horizontal, 20)
                .padding(.vertical, 5)
                .background(
                    Capsule()
                        .fill(Color.black)
                        .opacity(0.75)
                )
                ZStack {
                    ForEach(0..<cards.count, id: \.self) { index in
                        CardView(card: self.cards[index]) {
                           withAnimation {
                               self.removeCard(at: index)
                           }
                        }
                        .stacked(at: index, in: self.cards.count)
                    }
                }
                
            }
           
            //if differentiateWithoutColor {
                VStack {
                    Spacer()

                    HStack {
                        Image(systemName: "xmark.circle")
                            .padding()
                            .background(Color.black.opacity(0.7))
                            .clipShape(Circle())
                        //Spacer()
                        Image(systemName: "checkmark.circle")
                            .padding()
                            .background(Color.black.opacity(0.7))
                            .clipShape(Circle())
                    }
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                }
            //}
          
        }
        .onReceive(timer) { time in
            guard self.isActive else { return }
            if self.timeRemaining > 0 {
                self.timeRemaining -= 1
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
            self.isActive = false
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
            self.isActive = true
        }
        
    }
    
}
//-------------
extension View {
    func stacked(at position: Int, in total: Int) -> some View {
        //let offset = CGFloat(total - position)
        //配列内の場所ごとにビューを10ポイント押し下げます(0、次に10、20、30など)
        // return self.offset(CGSize(width: 0, height: position * 10))
        let offset = CGFloat(total - position)
        return self.offset(CGSize(width: 0, height: offset * 10))
    }
}

アプリケーションをallowsHitTesting()で終了する。
Ending the app with allowsHitTesting()

SwiftUIを使用すると、allowsHitTesting()falseに設定することでビューの対話性を無効にできます。そのため、このプロジェクトでは、の値をチェックすることで、タイムアウトが発生したときにカードのスワイプを無効にできますtimeRemaining。


//
//  ContentView.swift
//  Flashzilla


import SwiftUI
//-------------
struct ContentView: View {
    // For Card
    @State private var cards = [Card](repeating: Card.example, count: 10)
    
    @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
    @State private var timeRemaining = 100
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var isActive = true
    
    func removeCard(at index: Int) {
        cards.remove(at: index)
        //タイマー停止
        if cards.isEmpty {
            isActive = false
        }
    }
    //再試行できるようにアプリをリセット
    func resetCards() {
        cards = [Card](repeating: Card.example, count: 10)
        timeRemaining = 100
        isActive = true
    }
    
    var body: some View {
        ZStack {
            Image("background")
            .resizable()
            .scaledToFill()
            .edgesIgnoringSafeArea(.all)
            VStack {
                //timeRemaining1
                Text("Time: \(timeRemaining)")
                .font(.largeTitle)
                .foregroundColor(.white)
                .padding(.horizontal, 20)
                .padding(.vertical, 5)
                .background(
                    Capsule()
                        .fill(Color.black)
                        .opacity(0.75)
                )
                ZStack {
                    ForEach(0..<cards.count, id: \.self) { index in
                        CardView(card: self.cards[index]) {
                           withAnimation {
                               self.removeCard(at: index)
                           }
                        }
                        .stacked(at: index, in: self.cards.count)
                    }
                }
                //タイムアウトが発生したときにカードのスワイプ無効
                .allowsHitTesting(timeRemaining > 0)
                
                if cards.isEmpty {
                    Button("Start Again", action: resetCards)
                        .padding()
                        .background(Color.white)
                        .foregroundColor(.black)
                        .clipShape(Capsule())
                }
            }
           
            //if differentiateWithoutColor {
                VStack {
                    Spacer()

                    HStack {
                        Image(systemName: "xmark.circle")
                            .padding()
                            .background(Color.black.opacity(0.7))
                            .clipShape(Circle())
                        //Spacer()
                        Image(systemName: "checkmark.circle")
                            .padding()
                            .background(Color.black.opacity(0.7))
                            .clipShape(Circle())
                    }
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                }
            //}
          
        }
        .onReceive(timer) { time in
            guard self.isActive else { return }
            if self.timeRemaining > 0 {
                self.timeRemaining -= 1
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
            self.isActive = false
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
            if self.cards.isEmpty == false {
                self.isActive = true
            }
        }
        
    }
    
}
//-------------
extension View {
    func stacked(at position: Int, in total: Int) -> some View {
        //let offset = CGFloat(total - position)
        //配列内の場所ごとにビューを10ポイント押し下げます(0、次に10、20、30など)
        // return self.offset(CGSize(width: 0, height: position * 10))
        let offset = CGFloat(total - position)
        return self.offset(CGSize(width: 0, height: offset * 10))
    }
}
//-------------
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}