Im trying to make a an app that calculates the WPM. In the end of the game I would like to use a timer to stop the app after 60 seconds. I can't figure out how to stop it. I'm trying to stop it with conditional statement. But I don't know how to implement it with SwiftUI. If anyone had any other ideas that would be great.
import SwiftUI
struct ContentView: View {
#State var userInput = ""
#State var modalview = false
#State var getstarted = false
#EnvironmentObject var timerHolder : TimerHolder
var body: some View {
ZStack() {
modalView(modalview: $modalview, userInput: userInput)
}.sheet(isPresented: $modalview) {
modalView(modalview: self.$modalview)
}
}
}
struct modalView : View {
#ObservedObject var durationTimer = TimerHolder()
#Binding var modalview : Bool
#State var userInput: String = ""
var body: some View {
VStack{
Button(action: {
self.modalview = true
}) {
TextField("Get Started", text:$userInput)
.background(Color.gray)
.foregroundColor(.white)
// .frame(width: 300, height: 250).cornerRadius(20)
}
Text("\(userInput.count)")
if durationTimer == 60 {
.alert(isPresented: $showAlert) {
Alert(title: Text("Reminder"), message: Text("You wrote"), primaryButton: .default(Text("Yes"), action: { self.presentationMode.wrappedValue.dismiss() })
, secondaryButton: .cancel(Text("No")))
}; else {
}
}
}
}
class TimerHolder : ObservableObject {
var timer : Timer!
#Published var count = 0
func start() {
self.timer?.invalidate()
self.count = 0
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
_ in
self.count += 1
print(self.count)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
I changed some codes to give you a clue.
struct ContentView: View {
#State var userInput = ""
#State var modalview = false
#State var getstarted = false
#EnvironmentObject var timerHolder : TimerHolder
var body: some View {
ZStack() {
modalView(modalview: $modalview, userInput: userInput)
}.sheet(isPresented: $modalview) {
modalView(modalview: self.$modalview)
}
}
}
struct modalView : View {
#ObservedObject var durationTimer = TimerHolder()
#Binding var modalview : Bool
#State var userInput: String = ""
var body: some View {
VStack{
Button(action: {
self.modalview = true
}) {
TextField("Get Started", text:$userInput)
.background(Color.gray)
.foregroundColor(.white)
// .frame(width: 300, height: 250).cornerRadius(20)
}
Text("\(userInput.count)").alert(isPresented: self.$durationTimer.count) {
Alert(title: Text("Reminder"),
message: Text("You wrote"),
primaryButton: .default(Text("Yes"), action: { self.presentationMode.wrappedValue.dismiss()
print(123)
})
,
secondaryButton: .cancel(Text("No")))
}
}
}
}
class TimerHolder : ObservableObject {
var timer : Timer!
#Published var count = false
init(){
self.start()
}
func start() {
self.timer?.invalidate()
self.count = false
self.timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {
_ in
self.count = true
print(self.count)
}
}
}
Related
I am trying to imitate a lock screen of iOS in my own way with some basic code. However I do not understand how to properly hide an input textview. Now I am using an opacity modifier, but it does not seem to be the right solution. Could you please recommend me better options?
import SwiftUI
public struct PasscodeView: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var viewModel: ContentView.ViewModel
private let maxDigits: Int = 4
private let userPasscode = "1234"
#State var enteredPasscode: String = ""
#FocusState var keyboardFocused: Bool
#State private var showAlert = false
#State private var alertMessage = "Passcode is wrong, try again!"
public var body: some View {
VStack {
HStack {
ForEach(0 ..< maxDigits) {
($0 + 1) > enteredPasscode.count ?
Image(systemName: "circle") :
Image(systemName: "circle.fill")
}
}
.alert("Wrong Passcode", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}
TextField("Enter your passcode", text: $enteredPasscode)
.opacity(0)
.keyboardType(.decimalPad)
.focused($keyboardFocused)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
keyboardFocused = true
}
}
}
.padding()
.onChange(of: enteredPasscode) { _ in
guard enteredPasscode.count == maxDigits else { return }
passcodeValidation()
}
}
func passcodeValidation() {
if enteredPasscode == userPasscode {
viewModel.isUnlocked = true
dismiss()
} else {
enteredPasscode = ""
showAlert = true
}
}
}
I'm trying to figure how can change the value from different View. Here is the implementation of my main view:
import SwiftUI
import Combine
struct ContentView: View {
#State private var isPresented: Bool = false
#State private var textToProces = "" {
didSet{
print("text: oldValue=\(oldValue) newValue=\(textToProces)")
}
}
var body: some View {
ZStack{
VStack{
Button("Show Alert"){
self.isPresented = true
}.background(Color.blue)
}
ItemsAlertView(isShown: $isPresented, textToProcess: $textToProces)
}
}
}
On this view I'm trying to change the textToProces variable:
struct AnotherView: View {
#Binding var isShown: Bool
#Binding var textToProcess: String
var title: String = "Add Item"
let screenSize = UIScreen.main.bounds
var body: some View {
VStack {
Button(action: {
self.textToProcess = "New text"
self.isShown = false
}, label: {
Text("dissmis")
})
Text(self.textToProcess)
}
.background(Color.red)
.offset(y: isShown ? 0 : screenSize.height)
}
}
When I change the value on this line self.textToProcess = "New text" the textToProcess in the main view never gets the notification of the change. What I can I do to get the notification of the change in the main view any of you knows?
I'll really appreciate your help
You have to use the onChange modifier to track changes to textToProces.
import SwiftUI
import Combine
struct ContentView: View {
#State private var isPresented: Bool = false
#State private var textToProces = ""
var body: some View {
ZStack{
VStack{
Button("Show Alert"){
self.isPresented = true
}.background(Color.blue)
}
ItemsAlertView(isShown: $isPresented, textToProcess: $textToProces)
}
.onChange(of: textToProces) { value in
print("text: \(value)")
}
}
}
Follow-up question to iOS14 introducing errors with #State bindings
I was displaying a modal sheet from several options, depending on which button was pressed. However, now in iOS14 I get a fatal error caused by the selectedSpeaker/selectedMicrophone/selectedAmp being nil when the sheet displays. I am trying to change to .sheet(item:, content:) but I can't see how to implement the enum and then pass in the appropriate selected object. This is what I was doing previously:
enum ActiveSheet {
case speakerDetails, micDetails, ampDetails, settings
}
struct FavoritesView: View {
#State private var selectedSpeaker: Speaker?
#State private var selectedMicrophone: Microphone?
#State private var selectedAmp: Amplifier?
#State private var showingSheet = false
#State private var activeSheet: ActiveSheet = .settings
var body: some View {
List {
Button(action: {
self.activeSheet = .settings
self.showingSheet = true
}, label: { Text("Settings")})
Button(action: {
self.activeSheet = .micDetails
self.selectedMicrophone = microphones[0]
self.showingSheet = true
}, label: { Text("Mic 1")})
Button(action: {
self.activeSheet = .micDetails
self.selectedMicrophone = microphones[1]
self.showingSheet = true
}, label: { Text("Mic 2")})
Button(action: {
self.activeSheet = .speakerDetails
self.showingSheet = true
self.selectedSpeaker = speakers[0]
}, label: { Text("Speaker 1")})
Button(action: {
self.activeSheet = .speakerDetails
self.showingSheet = true
self.selectedSpeaker = speakers[1]
}, label: { Text("Speaker 2")})
//and so on for activeSheet = .ampDetails in the same way.
}
.sheet(isPresented: self.$showingSheet) {
if self.activeSheet == .speakerDetails {
SpeakerDetailView(speaker: self.selectedSpeaker!)
}
else if self.activeSheet == .micDetails {
MicDetailView(microphone: self.selectedMicrophone!)
}
else if self.activeSheet == .ampDetails {
AmpDetailView(amp: self.selectedAmp!)
} else if self.activeSheet == .settings {
SettingsView(showSheet: self.$showingSheet))
}
}
}
}
}
Here is another approach for your problem which uses sheet(item:content:)
struct ContentView: View {
#State private var selectedSpeaker: Speaker?
#State private var selectedMicrophone: Microphone?
#State private var selectedAmp: Amplifier?
#State private var showSettingsSheet = false
var body: some View {
List {
settingsSection
microphonesSection
// more sections
}
}
var settingsSection: some View {
Button(action: {
self.showSettingsSheet = true
}) {
Text("Settings")
}
.sheet(isPresented: self.$showSettingsSheet) {
SettingsView()
}
}
#ViewBuilder
var microphonesSection: some View {
Button(action: {
self.selectedMicrophone = microphones[0]
}) {
Text("Mic 1")
}
Button(action: {
self.selectedMicrophone = microphones[1]
}) {
Text("Mic 2")
}
.sheet(item: self.$selectedMicrophone) {
MicDetailView(microphone: $0)
}
}
}
This way you also don't need enum ActiveSheet.
You can always use #Environment(\.presentationMode) to dismiss a sheet, no need to pass the variable to the sheet (as in SettingsView(showSheet: self.$showingSheet)):
struct SettingsView: View {
#Environment(\.presentationMode) private var presentationMode
var body: some View {
Text("SettingsView")
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
}
I'm trying to show multiple sheets with SwiftUI.
Overlay.swift
import SwiftUI
struct OverlayWith<Content: View>: View {
let content: () -> Content
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack{
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.compact.down")
.resizable()
.frame(width: 40, height: 15)
.accentColor(Color.gray)
.padding()
}
content()
Spacer()
}
}
}
struct OverlayView_Button1: View {
var body: some View{
Text("Button 1 triggered this overlay")
}
}
struct OverlayView_Button2: View {
var body: some View{
Text("Button 2 triggered this overlay")
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var overlayVisible: Bool = false
// Button States
#State var view1_visible: Bool = false
#State var view2_visible: Bool = false
var body: some View {
VStack() {
Button(action: {
self.showSheet1()
}, label: {
Text("Button 1")
})
Button(action: {
self.showSheet2()
}, label: {
Text("Button 2")
})
}
.sheet(isPresented: $overlayVisible, content:{
if self.view1_visible {
OverlayWith(content: {
OverlayView_Button1()
})
} else if self.view2_visible {
OverlayWith(content: {
OverlayView_Button2()
})
}
})
}
func showSheet1(){
self.resetButtonStates()
self.view1_visible = true
self.overlayVisible = true
}
func showSheet2(){
self.resetButtonStates()
self.view2_visible = true
self.overlayVisible = true
}
func resetButtonStates(){
self.view1_visible = false
self.view2_visible = false
}
}
Compiling this code for iOS 13 works as expected. For iOS 14 on the other hand showSheetX() opens a sheet with an empty view. Dismissing the sheet and opening it again shows OverlayView_ButtonX. Debugging shows that viewX_visible is false when showSheetX() was called for the first time.
Is this a bug with iOS itself or am I missing something here?
Thank you in advance!
I think I found the solution for this issue:
ContentView.swift
import SwiftUI
var view1_visible: Bool = false // <- view1_visible is not a state anymore
var view2_visible: Bool = false // <- view2_visible is not a state anymore
struct ContentView: View {
#State var overlayVisible: Bool = false
var body: some View {
VStack() {
Button(action: {
self.showSheet1()
}, label: {
Text("Button 1")
})
Button(action: {
self.showSheet2()
}, label: {
Text("Button 2")
})
}
.sheet(isPresented: $overlayVisible, content:{
if view1_visible {
OverlayWith(content: {
OverlayView_Button1()
})
} else if view2_visible {
OverlayWith(content: {
OverlayView_Button2()
})
}
})
}
func showSheet1(){
self.resetButtonStates()
view1_visible = true
self.overlayVisible = true
}
func showSheet2(){
self.resetButtonStates()
view2_visible = true
self.overlayVisible = true
}
func resetButtonStates(){
view1_visible = false
view2_visible = false
}
}
Feel free to explain to me why this code works. I'm really confused :S
I want to start a timer of 60 seconds to test how many words a user can type within that minute. I started counting the characters within the TextField. But Now I need to decrement a timer so I can do the math and print out the answer to the user. I can't seem to figure out how to use the timer when it's not in the Content View struct though. Can I do that?
import SwiftUI
struct ContentView: View {
#State var userInput = ""
#State var modalview = false
#State var getstarted = false
#EnvironmentObject var timerHolder : TimerHolder
var body: some View {
ZStack() {
modalView(modalview: $modalview, userInput: userInput)
}.sheet(isPresented: $modalview) {
modalView(modalview: self.$modalview)
}
}
}
struct modalView : View {
#ObservedObject var durationTimer = TimerHolder()
#Binding var modalview : Bool
#State var userInput: String = ""
var body: some View {
VStack{
Button(action: {
self.modalview = true
}) {
TextField("Get Started", text:$userInput)
.background(Color.gray)
.foregroundColor(.white)
.frame(width: 300, height: 250)
.cornerRadius(20)
Text("\(userInput.count)")
Text("\(durationTimer.count) Seconds")
}
}
}
}
class TimerHolder : ObservableObject {
var timer : Timer!
#Published var count = 0
func start() {
self.timer?.invalidate()
self.count = 0
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
_ in
self.count += 1
print(self.count)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
The simples one, as you hold it as property, is to start in .onAppear... (supposing, of course, that you pass it in ContentView().environmentObject(TimerHolder()) on ContentView creation)
struct ContentView: View {
#State var userInput = ""
#State var modalview = false
#State var getstarted = false
#EnvironmentObject var timerHolder : TimerHolder
var body: some View {
ZStack() {
modalView(modalview: $modalview, userInput: userInput)
}.sheet(isPresented: $modalview) {
modalView(modalview: self.$modalview)
}
.onAppear {
self.timerHolder.start()
}
}
}