How can I automatically update TextField in swiftUI - timer

I'm new in swiftUI developing, and I have a little problem. I made a function using Timer to create a count up timer. I want to display the time updating in a TextField, but it doesn't work (with a Text it works). Here is the code:
import SwiftUI
struct ContentView: View {
#State var min: Int = 0
#State var sec: Int = 0
#State var time = ""
#State var timer: Timer? = nil
var body: some View {
NavigationView{
VStack{
TextField("Tempo (min o mm:ss)", text: $time)
HStack{
Button(action: {
self.start()
self.time = String(self.min)+":"+String(self.sec)
}){
Text("Start")
}
Button(action: {
self.stop()
}){
Text("Stop")
}
}
}//VStack
}//NavigationView
}//body
func start(){
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){
temp in
self.sec = self.sec + 1
if(self.sec == 59){
self.sec = 0
self.min = self.min + 1
}
}
}
func stop(){
timer?.invalidate()
timer = nil
}
}//contetView

You don't see the change because you're not updating the time String in your timer loop.
Here's the fix:
func updateTimeString() {
self.time = String(format: "%02d:%02d", self.min, self.sec)
}
func start(){
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { temp in
self.sec = self.sec + 1
if self.sec == 59 {
self.sec = 0
self.min = self.min + 1
}
updateTimeString()
}
}
Notes:
I moved the code to set the time string to a separate function. You can then call this from your Button as well.
I used String(format:) to add formatting to your string to show 2 digits for the minutes and seconds.

Related

I'm trying to use two timers in the same SwiftUI view

one or the other works but not both
if the timers have the same interval it works
struct ContentView: View {
#State var isPlaying: Bool = true
#State var timeRemaining: Int = 1800
#State var count = 1
#State var count2 = 10
var body: some View {
let timer2 = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
let timer = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
VStack {
Text("\(count) \(count2)")
.padding()
}
.onReceive(timer2) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count2 += 1
}
}
.onReceive(timer) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count += 1
}
}
}
}
can anyone suggest any changes to my code?
Move the timers out of the body and define them as properties of the View:
struct ContentView: View {
#State var isPlaying: Bool = true
#State var timeRemaining: Int = 1800
#State var count = 1
#State var count2 = 10
let timer2 = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
let timer = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
var body: some View {
VStack {
Text("\(count) \(count2)")
.padding()
}
.onReceive(timer2) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count2 += 1
}
}
.onReceive(timer) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count += 1
}
}
}
}
This gives me some very unusual results
struct ContentView: View {
#State var isPlaying: Bool = true
#State var timeRemaining: Int = 1800
#State var count = 1
#State var count2 = 10
#State var rate = 0.5
let timer = Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
var body: some View {
let timer2 = Timer.publish(every: rate, on: .main, in: .default).autoconnect()
VStack {
Text("\(count) \(count2)")
.padding()
}
.onReceive(timer) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count += 1
}
}
.onReceive(timer2) { _ in
if self.timeRemaining > 0 && isPlaying == true {
self.count2 += 1
}
}
}
}
despite the intervals being the same, the counters increase at different rates, even randomly

Cannot check array range in swift

I have different arrays, they store a different number of elements, at the click of a button the index of the array is increased by 1 and the element is added to another array, I did an array range check, but it does not work. what could be the problem
xCode console: Fatal error: Index out of range: file
class ViewController: UIViewController {
var currentIndex = 0
var arrayIndex = -1
var arrayWords: [Words]? {
didSet {
// wordTextLabel.text = arrayWords?[0].arrayWords?[currentIndex].name
// wordImage.image = arrayWords?[0].arrayWords?[currentIndex].image
// wordArray.append((arrayWords?[0].arrayWords?[currentIndex])!)
}
}
var testArray = ["Hello","Energy","Disk","Duck","Wafles"]
var wordArray: [String] = [] //[ArrayWords] = []
lazy var wordTextLabel: UILabel = {
let label = UILabel()
label.text = testArray[currentIndex]//arrayWords?[0].arrayWords?[currentIndex].name
label.textColor = .white
label.font = UIFont(name: "OpenSans-ExtraBold", size: 20)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.isUserInteractionEnabled = true
return label
}()
lazy var wordImage: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFit
image.tintColor = .white
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
lazy var button1: UIButton = {
let button = UIButton()
button.backgroundColor = .white
button.setTitle("Good", for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
button.layer.cornerRadius = 40
button.clipsToBounds = true
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
lazy var button2: UIButton = {
let button = UIButton()
button.backgroundColor = .white
button.setTitle("No", for: .normal)
button.setTitleColor(.black, for: .normal)
button.layer.cornerRadius = 40
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
func setupView() {
view.setGradient(colorOne: Color.purpleColor, colorTwo: Color.pinkColor,
startPoint: CGPoint(x: 1.0, y: 0.1), endPoint: CGPoint(x: 0.1, y: 1.0))
view.addSubview(wordTextLabel)
//wordTextLabel.addSubview(wordImage)
//view.addSubview(wordImage)
view.addSubview(button1)
view.addSubview(button2)
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-120-[v0]-120-|", metrics: nil, views: ["v0": wordTextLabel]))
button1.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -40).isActive = true
button1.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30).isActive = true
button1.heightAnchor.constraint(equalToConstant: 80).isActive = true
button1.widthAnchor.constraint(equalToConstant: 80).isActive = true
button2.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -40).isActive = true
button2.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -30).isActive = true
button2.heightAnchor.constraint(equalToConstant: 80).isActive = true
button2.widthAnchor.constraint(equalToConstant: 80).isActive = true
// wordArray.append((arrayWords?[0].arrayWords?[currentIndex])!)
}
#objc func buttonAction(sender: Any!) {
print("Button tapped")
action()
}
func action() {
if currentIndex < testArray.count/*arrayWords?[0].arrayWords?.count*/ { //!= 2 // !=
currentIndex += 1
arrayIndex += 1
wordTextLabel.text = testArray[currentIndex]//arrayWords?[0].arrayWords?[currentIndex].name
wordArray.append(testArray[arrayIndex])//((arrayWords?[0].arrayWords?[arrayIndex])!)
} else if arrayIndex != 3 {
arrayIndex += 1
wordArray.append(testArray[arrayIndex])//((arrayWords?[0].arrayWords?[arrayIndex])!)
}
}
}
Change action() as below and it will work fine. There is no need to maintain two index variables and without the second variable you don't need the else if... either
func action() {
if currentIndex < testArray.count {
wordTextLabel.text = testArray[currentIndex]
wordArray.append(testArray[currentIndex])
}
currentIndex += 1
}

Is it possible to make an if statement if a list item is equal to a certain number in swift?

I've been working on a truth or dare app, in which you spin a wheel and you randomly get a truth or dare, and I want to have a bunch of random truth questions and dares saved in a database.
I have it set up so when you press the spin button it randomly chooses a truth or dare out of an array.
i've tried it a little and couldn't seem to get it to work. so my question is would it be possible to do something like,
if self.number[0] == 0 || 2 || 3{ (this is where it would get the random truth)}
else{(this is where it would get the random dare)}
I don't know if it will help, but this is the rest of my code.
import SwiftUI
import CoreHaptics
class Colors{
static var drinkGradient = [LinearGradient(gradient: Gradient(colors: [Color("drinkcard"), Color("drinkcard2"), Color("drinkcard")]), startPoint: .top, endPoint: .bottom)]
static var truthGradient = [LinearGradient(gradient: Gradient(colors: [Color("truthcard"), Color("truthcard"), Color("truthcard")]), startPoint: .top, endPoint: .bottom)]
static var dareGradient = [LinearGradient(gradient: Gradient(colors: [Color("darecard"), Color("darecard"), Color("darecard")]), startPoint: .top, endPoint: .bottom)]
}
struct ContentView: View {
#State private var timer:Timer!
#State private var text = [String("Truth"), String("Dare"), String("Truth"), String("Dare"), String("Truth"), String("Dare"), String("Drink!!")]
#State private var foregrounds = [Color.white, Color.white, Color.white, Color.white, Color.white, Color.white, Color.black]
#State private var number = [0]
#State private var timeKeeper: Float = 0
#State private var angle: Double = 0
let generator = UINotificationFeedbackGenerator()
#State var backgrounds = [Colors.truthGradient[0], Colors.dareGradient[0], Colors.truthGradient[0], Colors.dareGradient[0], Colors.truthGradient[0], Colors.dareGradient[0], Colors.drinkGradient[0]]
var body: some View {
ZStack{
Rectangle()
.foregroundColor(Color("background"))
.edgesIgnoringSafeArea(.all)
VStack{
HStack {
Text("Truth, Dare or Drink")
.shadow(radius: 10)
.padding(.top, 20)
.foregroundColor(.white)
.font(.system(.largeTitle, design: .rounded) )
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 100)
.background(Color("banner"))
.edgesIgnoringSafeArea(.top)
.shadow(radius: 15)
}
Text("This is where the Truth or dare will go.")
.frame(width: 350, height: 200)
.foregroundColor(.white)
.font(.system(.headline, design: .rounded))
Spacer()
Text(text[number[0]])
.font(.system(.largeTitle, design: .rounded))
.foregroundColor(foregrounds[number[0]])
.frame(width: 250, height: 250)
.background(backgrounds[number[0]])
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(radius: 10)
.rotationEffect(.degrees(angle))
.animation(.easeIn)
Spacer()
Button(action: {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true, block: {_ in
self.timeKeeper += 0.10
if self.timeKeeper < 3{
if self.number[0] == self.text.count - 1{
self.number[0] = 0
self.angle += 360
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
else{
self.number[0] += 1
self.angle += 360
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
}
}
else{
self.number[0] = Int.random(in:
0...self.text.count - 1)
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
impactHeavy.impactOccurred()
self.angle += 360
self.timeKeeper = 0
self.timer.invalidate()
}
})
}) {
Text("Spin")
.font(.system(.title, design: .rounded))
.foregroundColor(.white)
.frame(width: 250, height: 50)
.background(Color("button"))
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(radius: 15)
}
Spacer()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
if (self.number[0] == 0 || self.number[0] == 2 || self.number[0] == 3) {
print("truth")
} else {
print("dare")
}

ObservableObject and Condition

I have a code for a timer that starts automatically and should stop again with an if statement. Unfortunately, that doesn't work and I don't get an error message. what am I doing wrong here?
import SwiftUI
import Combine
import Foundation
class WaitingTimerClass: ObservableObject {
#Published var waitingTimerCount: Int = 0
var waitingTimer = Timer()
func start() {
self.waitingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in self.waitingTimerCount += 1 } }
func stop() { waitingTimer.invalidate() }
func reset() { waitingTimerCount = 0; waitingTimer.invalidate() }
}
struct ContentView: View {
#ObservedObject var observed = WaitingTimerClass()
var body: some View {
Text("\(self.observed.waitingTimerCount)")
.onAppear {
self.observed.start()
if self.observed.waitingTimerCount == 3 {
self.observed.stop()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
``
The code in onAppear block works only once, when the View appears (or several times, when view disappear and appear again). In your case waitingTimerCount will always be 0. If you need to observe some value from the view use onReceive. The code should looks like this:
struct TimerWaiter: View {
#ObservedObject var observed = WaitingTimerClass()
var body: some View {
Text("\(self.observed.waitingTimerCount)")
.onAppear {
self.observed.start()
}
.onReceive(observed.$waitingTimerCount) { count in
guard count == 3 else { return }
self.observed.stop()
}
}
}
update for first author's comment: I can't figure out, why reseting timer is not working as expected and I have not much time today. Maybe tomorrow it will be easier to understand. Here is not quite good, but worked solution:
class WaitingTimerClass: ObservableObject {
#Published var waitingTimerCount: Int = 0
var didInvalidate = PassthroughSubject<Void, Never>()
var waitingTimer = Timer()
func start() {
self.waitingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in self.waitingTimerCount += 1 } }
func stop() { waitingTimer.invalidate() }
func reset() {
waitingTimer.invalidate()
waitingTimerCount = 0
didInvalidate.send()
}
}
struct TimerWaiter: View {
#ObservedObject var observed = WaitingTimerClass()
#State private var count = 0
var body: some View {
Text("\(count)")
.onAppear {
self.observed.start()
}
.onReceive(observed.$waitingTimerCount) { count in
self.count = count
guard count == 3 else { return }
self.observed.reset()
}
.onReceive(observed.didInvalidate) { _ in
self.count = 0
}
}
}
P.S. I will try to dig deeper to understand, why the last changing of waitingTimerCount does not affect View
.onAppear {
self.observed.start()
if self.observed.waitingTimerCount == 3 {
self.observed.stop()
}
}
How this code works?
it is running when the View Appears (most likely only once)
start the timer
checked if waitinigTimer == 3 // it is not 3 at this moment
return
So, it simply never stop, as you already know.
If you like to stop it if waitingTimer == 3, you have to do it in your model.
#Published var waitingTimerCount: Int = 0 {
willSet {
if newValue == 3 { stop() }
}
}
Thank you very much. the solution to the reset problem can be found here:
ObservableObject bug or not

SwiftUI: How to cancel timer in SwiftUI view?

I am using a timer in a SwiftUI view as in code below. It works as expected BUT under some conditions I want to cancel/stop that timer. There seems to be no ".cancel" property or method on the timer var. How do I cancel this timer? Any ideas/tips?
import SwiftUI
struct ContentView: View {
#State private var selection = 2
#State private var rotation: Double = GBstrtest
let timer = Timer.publish (every: 0.8, on: .current, in: .common).autoconnect()
var body: some View {
TabView(selection: $selection){
Text("Settings")
.font(.title)
.tabItem {
VStack {
Image(systemName: "gear")
.font(Font.system(.title ))
Text("Settings")
}
}
.tag(0)
VStack {
Divider().padding(2)
ZStack {
Image("image1")
.resizable()
.aspectRatio(contentMode: .fit)
Image("image2")
.resizable()
.aspectRatio(contentMode:.fit)
.rotationEffect(.degrees(rotation))
.animation(.easeInOut(duration: 0.3) )
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
}
Spacer()
}
.tabItem {
VStack {
Image(systemName: "speedometer")
.font(Font.system(.title ))
Text("Read Meter")
}
}
.tag(1)
}
.onReceive(timer) {
_ in self.rotation = Double.random(in: 0 ... 200)
// How do I cancel timer HERE!?
}
}
}
Inside your conditional statement, use the following code:
self.timer.upstream.connect().cancel()
Full cycle goes like this:
struct MySwiftUIView : View {
...
#State var timer = Timer.publish (every: 1, on: .current, in: .common).autoconnect()
#State var timeRemaining = -1
var body: some View {
ZStack {
// Underlying shapes etc as needed
Text("\(timeRemaining)").
.opacity(timeRemaining > 0 ? 1 : 0)
.onReceive(timer) { _ in
if self.timeRemaining < 0 {
// We don't need it when we start off
self.timer.upstream.connect().cancel()
return
}
if self.timeRemaining > 0 {
self.timeRemaining -= 1
} else {
self.timer.upstream.connect().cancel()
// Do the action on end of timer. Text would have been hidden by now
}
}
.onTapGesture { // Or any other event handler that should start countdown
self.timeRemaining = Int(delayValue)
self.timer = Timer.publish (every: 1, on: .current, in:
.common).autoconnect()
}
}
Voila! A reusable timer, use it as many times as you'd like!

Resources