スワイプしながらビューに色を付ける
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() } }