Swift UI | Textfield not reading entered value - ios

I have a textfield which is supposed to log the units of a food product someone has eaten, which is then used to calculate the total number of calories, protein, etc. that the user consumed. But when the value is entered on the textfield, the units variable isn't updated. How can I fix this?
This is my code:
#State var selectFood = 0
#State var units = 0
#State var quantity = 1.0
#State var caloriesInput = 0.0
#State var proteinInput = 0.0
#State var carbsInput = 0.0
#State var fatsInput = 0.0
var body: some View {
VStack {
Group {
Picker(selection: $selectFood, label: Text("What did you eat?")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white))
{
ForEach(database.productList.indices, id: \.self) { i in
Text(database.productList[i].name)
}
}
.pickerStyle(MenuPickerStyle())
Spacer(minLength: 25)
Text("How much did you have?")
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.white)
.frame(alignment: .leading)
//Textfield not working.
TextField("Units", value: $units, formatter: NumberFormatter())
.padding(10)
.background(Color("Settings"))
.cornerRadius(10)
.foregroundColor(Color("Background"))
.keyboardType(.numberPad)
Button (action: {
self.quantity = ((database.productList[selectFood].weight) * Double(self.units)) / 100
caloriesInput = database.productList[selectFood].calories * quantity
proteinInput = database.productList[selectFood].protein * quantity
carbsInput = database.productList[selectFood].carbs * quantity
fatsInput = database.productList[selectFood].fats * quantity
UIApplication.shared.hideKeyboard()
}) {
ZStack {
Rectangle()
.frame(width: 90, height: 40, alignment: .center)
.background(Color(.black))
.opacity(0.20)
.cornerRadius(15)
;
Text("Enter")
.foregroundColor(.white)
.fontWeight(.bold)
}
}
}
}
}

This is an issue with NumberFormatter that has been going on for a while. If you remove the formatter it updates correctly.
This is a workaround. Sadly it requires 2 variables.
import SwiftUI
struct TFConnection: View {
#State var unitsD: Double = 0
#State var unitsS = ""
var body: some View {
VStack{
//value does not get extracted properly
TextField("units", text: Binding<String>(
get: { unitsS },
set: {
if let value = NumberFormatter().number(from: $0) {
print("valid value")
self.unitsD = value.doubleValue
}else{
unitsS = $0
//Remove the invalid character it is not flawless the user can move to in-between the string
unitsS.removeLast()
print(unitsS)
}
}))
Button("enter"){
print("enter action")
print(unitsD.description)
}
}
}
}
struct TFConnection_Previews: PreviewProvider {
static var previews: some View {
TFConnection()
}
}

Related

How to show random array's items only once in SwiftUI?

I'm trying to recreate a popular game: Heads up, basically the user has to try to guess the name putting the phone on his head, with friends' suggestions...if he raises the head he skips the word, if he lowers the head it means he guessed the name and he earns a point. He has limited time. I need that every time the user raises/lowers his head, the array's name changes, and each name must appear only once. Any suggestions?
This is my code:
import SwiftUI
import CoreMotion
struct ContentView: View {
let motionManager = CMMotionManager()
let queue = OperationQueue()
#State private var roll = Double.zero
#State private var people = ["John", "Marcus", "Steve", "Eric", "Philip"].shuffled()
#State private var randomPerson = Int.random(in: 0...4)
let timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect()
#State private var timeRemaining = 10
#State private var score = 0
var body: some View {
NavigationView {
ZStack {
//Show a red background and "SKIP" if the user raises head
if roll < 1 {
Color.red
.ignoresSafeArea()
Text("SKIP")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
} else if roll > 2.1 {
//Show a green background and "CORRECT" if user lowers head
Color.green
.ignoresSafeArea()
Text("CORRECT")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.onAppear {
score += 1
}
} else {
//Otherwise show a cyan back with array's name
Color.cyan
.ignoresSafeArea()
Text(people[randomPerson])
.font(.largeTitle)
.bold()
.foregroundColor(.white)
}
Text("\(timeRemaining)")
.font(.system(size: 39))
.padding(.bottom, 200)
.onReceive(timer) { _ in
if timeRemaining > 0 {
timeRemaining -= 1
}
}
Text("Score: \(score)")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.padding(.top, 200)
}
.onAppear {
//Detect device motion
self.motionManager.startDeviceMotionUpdates(to: self.queue) { (data: CMDeviceMotion?, error: Error?) in
guard let data = data else {
print("Error: \(error!)")
return
}
let attitude: CMAttitude = data.attitude
DispatchQueue.main.async {
self.roll = attitude.roll
}
}
}
}
.navigationViewStyle(.stack)
}
}
You can do like this:
a state variable for current selected person
#State private var currerntPerson : String = ""
a function to get random person
getRandomPerson()
change TextView show selected person name:
Text(currerntPerson)
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.onAppear {
getRandomPerson()
}
====
All code here:
let motionManager = CMMotionManager()
let queue = OperationQueue()
#State private var roll = Double.zero
#State private var people = ["John", "Marcus", "Steve", "Eric", "Philip"].shuffled()
#State private var randomPerson = Int.random(in: 0...4)
let timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect()
#State private var timeRemaining = 10
#State private var score = 0
#State private var currerntPerson : String = ""
var body: some View {
NavigationView {
ZStack {
//Show a red background and "SKIP" if the user raises head
if roll < 1 {
Color.red
.ignoresSafeArea()
Text("SKIP")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
} else if roll > 2.1 {
//Show a green background and "CORRECT" if user lowers head
Color.green
.ignoresSafeArea()
Text("CORRECT")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.onAppear {
score += 1
}
} else {
//Otherwise show a cyan back with array's name
Color.cyan
.ignoresSafeArea()
Text(currerntPerson)
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.onAppear {
getRandomPerson()
}
}
Text("\(timeRemaining)")
.font(.system(size: 39))
.padding(.bottom, 200)
.onReceive(timer) { _ in
if timeRemaining > 0 {
timeRemaining -= 1
}
}
Text("Score: \(score)")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.padding(.top, 200)
}
.onAppear {
//Detect device motion
self.motionManager.startDeviceMotionUpdates(to: self.queue) { (data: CMDeviceMotion?, error: Error?) in
guard let data = data else {
print("Error: \(error!)")
return
}
let attitude: CMAttitude = data.attitude
DispatchQueue.main.async {
self.roll = attitude.roll
}
}
}
}
.navigationViewStyle(.stack)
}
func getRandomPerson() {
if people.count > 0 {
let index = Int.random(in: 0..<people.count)
currerntPerson = people[index]
people.remove(at: index)
}
}

value being constant in swiftUI

I am sorry im not sure that the title makes sense but if u read, im sure u will understand my problem. I have declared a variable #State var timetext: Int32 in the file CreatingWorkout, with a textfield TextField("5000, 100 etc", value: $timetext, formatter: NumberFormatter()) When i go to the createWorkoutView file and try to present it with the sheet, it wants me to give a value to timetext. However, when i provide a value with the textfield it stays constantly value given when calling with the sheet. I will attach a video here for you to see.
CreatingWorkout.swift :
struct CreatingWorkout: View {
#State var workoutTitle: String
#State var desc: String
#State var timetext: Int32
#State private var iconColor = Color.black
#State var displayWorkout: String = ""
#Environment(\.dismiss) var dismiss
#Environment(\.managedObjectContext) private var viewContext
private func saveWorkout() {
do {
let workout = Workout(context: viewContext)
workout.title = workoutTitle
workout.time = timetext
workout.icon = displayWorkout
workout.descriptionn = desc
try viewContext.save()
} catch {
print(error.localizedDescription)
}
}
CreateWorkout.swift :
import SwiftUI
struct CreateWorkoutView: View {
#State private var showingCreateWorkout = false
#State var timetext: Int32 = 0
var body: some View {
NavigationView {
VStack {
VStack(alignment: .center, spacing: 20) {
Text("Fitzy")
.font(.largeTitle)
.fontWeight(.bold)
Text("Create your first Workout")
.font(.title3)
.foregroundColor(.gray)
Button {
showingCreateWorkout.toggle()
} label: {
Image(systemName: "plus")
.font(.largeTitle)
.foregroundColor(.white)
.padding()
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(Color("AccentColor"))
.frame(width: 50, height: 50)
)
}.sheet(isPresented: $showingCreateWorkout) {
CreatingWorkout(workoutTitle: "", desc: "", timetext: timetext)
}
}
}.navigationTitle("Create Workout")
}
}
}
struct CreateWorkoutView_Previews: PreviewProvider {
static var previews: some View {
CreateWorkoutView()
}
}
https://drive.google.com/file/d/1qQDQmap5bMz9LxzibV98epHW5urqUtZ7/view?usp=sharing
as mentioned in the comments, you need a #State property to pass the value
that you type in your TextField to the sheet with CreatingWorkout. Try something like this:
struct CreatingWorkout: View {
#State var workoutTitle: String
#State var desc: String
#State var timetext: Int32
// ....
var body: some View {
Text("\(timetext)")
}
}
struct CreateWorkout: View {
#State var showingCreateWorkout = false
#State var timetext: Int32 = 0 // <-- here
var body: some View {
VStack {
TextField("type a number", value: $timetext, format: .number).border(.red) // <-- here
Button {
showingCreateWorkout.toggle()
} label: {
Image(systemName: "plus").font(.largeTitle).foregroundColor(.red).padding()
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(Color("AccentColor"))
.frame(width: 50, height: 50)
)
}.sheet(isPresented: $showingCreateWorkout) {
CreatingWorkout(workoutTitle: "", desc: "", timetext: timetext) // <-- here
}
}
}
}

SwiftUI - conditional onChange

I have 2 TextFields:
__ x2 __
I want to perform simple calculations: string1 x 2 = string2. I am using .onChange modifier, so if you type first number it is multiplied by 2 and result is printed in second TextField. You can go the other way around and type string2 and it will be divided by 2 and result will be printed in the first TextField.
Now because both TextFields have .onChange, it gets triggered few times (3 in this case). After changing string1, string2 gets updated. And as it changed, .onChange of string2 is triggered and later the same with .onChange of string1.
Please run this example code and check what gets printed in console:
import SwiftUI
struct ContentView: View {
#State private var string1: String = ""
#State private var int1: Int = 0
#State private var string2: String = ""
#State private var int2: Int = 0
let multiplier: Int = 2
var body: some View {
VStack {
HStack {
TextField("0", text: $string1)
.keyboardType(.decimalPad)
.onChange(of: string1, perform: { value in
string1 = value
int1 = Int(string1) ?? 0
int2 = int1 * multiplier
string2 = "\(int2)"
print("int1: \(int1)")
})
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
.background(Color(UIColor.systemGray5))
HStack {
Spacer()
Text("x2")
}
HStack {
TextField("0", text: $string2)
.keyboardType(.decimalPad)
.onChange(of: string2, perform: { value in
string2 = value
int2 = Int(string2) ?? 0
int1 = int2 / multiplier
string1 = ("\(int1)")
print("int2: \(int2)")
})
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
.background(Color(UIColor.systemGray5))
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Question:
How to make .onChange conditional so it runs only once? To be precise, I want to execute .onChange on first input ONLY when I edit first input. And execute .onChange on second input ONLY when I edit second input.
Probably it will be easy with .onFocus in iOS 15. But how to do it in iOS 14?
I've figured it out. I needed two variables, one per TextField: isFocused1, isFocused2. Each of them changes to true with onEditingChanged. And each onChange has if condition that checks if isFocused for this TextField is true.
Now onChange is triggered only if each TextField is being edited. I have added changing background colors to visualize focus changes.
Working code:
import SwiftUI
struct ContentView: View {
#State private var string1: String = ""
#State private var int1: Int = 0
#State private var string2: String = ""
#State private var int2: Int = 0
let multiplier: Int = 2
#State private var isFocused1 = false
#State private var isFocused2 = false
var body: some View {
VStack {
HStack {
TextField("0", text: $string1, onEditingChanged: { (changed) in
isFocused1 = changed
})
.keyboardType(.decimalPad)
.onChange(of: string1, perform: { value in
if isFocused1 {
int1 = Int(string1) ?? 0
int2 = int1 * multiplier
string2 = "\(int2)"
print("int1: \(int1)")
}
})
.background(isFocused1 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
HStack {
Spacer()
Text("x2")
}
HStack {
TextField("0", text: $string2, onEditingChanged: { (changed) in
isFocused2 = changed
})
.keyboardType(.decimalPad)
.onChange(of: string2, perform: { value in
if isFocused2 {
int2 = Int(string2) ?? 0
int1 = int2 / multiplier
string1 = ("\(int1)")
print("int2: \(int2)")
}
})
.background(isFocused2 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
After feedback from #JoakimDanielson I've made a version with enum instead of 2 separate variables:
import SwiftUI
struct ContentView: View {
enum Focus {
case input1
case input2
}
#State private var isFocused: Focus?
#State private var string1: String = ""
#State private var int1: Int = 0
#State private var string2: String = ""
#State private var int2: Int = 0
let multiplier: Int = 2
var body: some View {
VStack {
HStack {
TextField("0", text: $string1, onEditingChanged: { (changed) in
if changed {
isFocused = Focus.input1
}
})
.keyboardType(.decimalPad)
.onChange(of: string1, perform: { value in
if isFocused == .input1 {
int1 = Int(string1) ?? 0
int2 = int1 * multiplier
string2 = "\(int2)"
print("int1: \(int1)")
}
})
.background(isFocused == .input1 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
HStack {
Spacer()
Text("x2")
}
HStack {
TextField("0", text: $string2, onEditingChanged: { (changed) in
if changed {
isFocused = Focus.input2
}
})
.keyboardType(.decimalPad)
.onChange(of: string2, perform: { value in
if isFocused == .input2 {
int2 = Int(string2) ?? 0
int1 = int2 / multiplier
string1 = ("\(int1)")
print("int2: \(int2)")
}
})
.background(isFocused == .input2 ? Color.yellow : Color.gray)
}
.multilineTextAlignment(.trailing)
.font(.largeTitle)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI => AttributeGraph: cycle detected through attribute after updating one model in list

Context:
I've got a list of custom views. The array is stored a #ObservableObject as #Published.
My custom view has a function which detects when the View is touched (I did it because it's triggered only after an animation). This event activates, through my #ObservableObject, an event that shows a View which is in ZStack with my list. There I could update my passed object through a TextField, and when I come back I have everything updated.
However, when I try to re-show one of every element in my list, my debug shows this error:
AttributeGraph: cycle detected through attribute.
Instead, if I show the detail without updating my model's data, I have not any leak.
Any suggestion?
EDIT:
here's the code:
struct ProcedureList: View {
#ObservedObject var procedureManager = ProcedureManager()
#State private var showModal = false
var isEmpty:Bool {
return procedureManager.procedures.isEmpty
}
init() {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().standardAppearance = appearance
}
var body: some View {
NavigationView {
GeometryReader { geometry in
ZStack {
VStack{
if !self.isEmpty {
List {
ForEach(self.procedureManager.procedures.indices, id: \.self) { index in
ProcedureCell(procedure: self.$procedureManager.procedures[index]){ procedure, position, size in
self.procedureManager.selectedProcedure = procedure
self.procedureManager.cardSize = size
self.procedureManager.cardPosition = position
self.procedureManager.size = size
self.procedureManager.position = position
self.procedureManager.isPressed = true
withAnimation(Animation.default.delay(0.1)) {
self.procedureManager.size.width = geometry.frame(in: .local).width
self.procedureManager.size.height = geometry.frame(in: .local).size.height
self.procedureManager.position.x = geometry.frame(in: .global).origin.x
self.procedureManager.position.y = geometry.frame(in: .global).origin.y
}
print(
"""
pressed procedure: \(procedure.title)
at position: \(position)
and with size: \(size)
"""
)
}
// .tag(self.procedureManager.procedures[index])
.tag(index)
}
.onDelete(perform: self.onDelete)
}
.environment(\.defaultMinListRowHeight, 120)
.animation(.easeInOut)
}else {
VStack{
Text("Non hai ancora creato una procedura!")
.font(.largeTitle)
.multilineTextAlignment(.center)
.padding(.bottom, 30)
Button(action: {
self.showModal.toggle()
}){
Text("Creane una nuova!")
}
.sheet(isPresented: self.$showModal) {
NewProcedure(showModal: self.$showModal) { procedure in
self.procedureManager.newProcedure = procedure
self.procedureManager.createProcedure()
}
}
}.padding(20)
}
}
Rectangle()
.edgesIgnoringSafeArea(.all)
.zIndex(self.procedureManager.isPressed ? 0 : -1)
.opacity(self.procedureManager.isPressed ? 0.7 : 0)
.animation(Animation.easeInOut(duration: 0.5))
ProcedureDetail(action: { procedure in
self.procedureManager.update(procedure: procedure)
}, procedure: self.$procedureManager.selectedProcedure, isShowingDetail: self.$procedureManager.isPressed)
.frame(width: self.procedureManager.correctSize.width, height: self.procedureManager.correctSize.height)
.position(x: self.procedureManager.correctPosition.x, y: self.procedureManager.correctPosition.y - (geometry.frame(in: .global).origin.y))
.offset(x: self.procedureManager.correctSize.width / 2, y: self.procedureManager.correctSize.height / 2)
.animation(.easeInOut)
.opacity(self.procedureManager.correctOpacity)
.animation(Animation.easeInOut.delay(self.procedureManager.isPressed ? 0 : 0.2))
}
.onAppear {
UITableView.appearance().separatorStyle = .none
}
.onDisappear() {
UITableView.appearance().separatorStyle = .singleLine
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(trailing:
!self.isEmpty && !self.procedureManager.isPressed ?
Button(action: {
self.showModal.toggle()
}){
Image(systemName: "plus.circle.fill")
.font(Font.system(size: 40))
.foregroundColor(Color.red)
}
.sheet(isPresented: self.$showModal) {
NewProcedure(showModal: self.$showModal) { procedure in
self.procedureManager.newProcedure = procedure
self.procedureManager.createProcedure()
}
} : nil
)
}
}
}
private func onDelete(offsets: IndexSet) {
self.procedureManager.procedures.remove(atOffsets: offsets)
}
}
struct ProcedureCell: View {
#Binding var procedure: Procedure
#State var position:CGPoint = .zero
#State var size:CGSize = .zero
var action:(_ procedure:Procedure, _ position: CGPoint, _ size:CGSize)->Void
var body: some View {
return
GeometryReader { geometry in
Button(action: {
let position = geometry.frame(in: .global).origin
let size = geometry.frame(in: .global).size
self.action(self.procedure, position, size)
}){
HStack {
VStack(alignment: .leading) {
Text(self.procedure.title)
.font(.largeTitle)
Text(self.procedure.subtitle)
.font(.title)
}
.padding(10)
Spacer()
}
}
.buttonStyle(MyButtonStyle())
.padding([.top, .bottom])
.edgesIgnoringSafeArea(.all)
}
}
}
struct MyButtonStyle:ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.background(
Rectangle()
.fill(configuration.isPressed ? Color.red : Color.orange)
.cornerRadius(20)
.shadow(radius: configuration.isPressed ? 5 : 0)
)
.scaleEffect(configuration.isPressed ? 1.1 : 1)
.animation(.easeInOut)
}
}
struct Procedure: Identifiable {
var title: String
var subtitle: String
var id: String
static var empty:Procedure {
return Procedure(title: "", subtitle: "")
}
init (title:String, subtitle:String) {
self.id = UUID().uuidString
self.title = title
self.subtitle = subtitle
}
}
class ProcedureManager: ObservableObject {
#Published var procedures: [Procedure]
#Published var newProcedure = Procedure.empty
#Published var selectedProcedure = Procedure.empty
#Published var cardSize:CGSize = .zero
#Published var cardPosition:CGPoint = .zero
#Published var size:CGSize = .zero
#Published var position:CGPoint = .zero
#Published var isPressed:Bool = false
var correctSize:CGSize {
if isPressed {
return size
}
else{
return cardSize
}
}
var correctPosition:CGPoint {
if isPressed {
return position
}
else{
return cardPosition
}
}
var correctOpacity: Double {
return isPressed ? 1 : 0
}
func update(procedure:Procedure) {
if let index = procedures.compactMap({$0.id}).firstIndex(of: procedure.id) {
procedures[index].title = procedure.title
procedures[index].subtitle = procedure.subtitle
objectWillChange.send()
}
}
func createProcedure(){
procedures.append(newProcedure)
newProcedure = .empty
}
func createProcedure(with title:String, andSubtitle subtitle:String) {
let procedure = Procedure(title: title, subtitle: subtitle)
procedures.append(procedure)
}
init(){
procedures = [
Procedure(title: "test1", subtitle: "subtitletest1"),
Procedure(title: "test2", subtitle: "subtitletest2"),
Procedure(title: "test3", subtitle: "subtitletest3"),
Procedure(title: "test4", subtitle: "subtitletest4"),
Procedure(title: "test5", subtitle: "subtitletest5"),
]
}
}

How to make a timer in SwiftUI keep firing when changing tab with tabview

I have a timer that fires every half second and that leads to the calling of a function that outputs a set of strings that are used to display a countdown to a specific date. It works when I create a new event and then switch over to the tab that contains the information for the countdown, but when I switch back to the add event tab and then back it stops counting down.
The timer is made using this:
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
It runs later using this
ForEach(eventNames.indices, id: \.self) { index in
VStack{
Text("Your event " + "\(self.eventNames[index])" + " is in " + "\(self.string[index])")
.onReceive(self.timer) { input in
self.differenceDate(numbers: index)
}
}
}
And finally, it calls this function
func differenceDate(numbers: Int) {
self.formatter.unitsStyle = .full
self.formatter.allowedUnits = [.day, .hour, .minute, .second]
//self.formatter.maximumUnitCount = 2
self.now = Date();
if self.now > self.eventDates[numbers] {
self.eventNames[numbers] = "";
}
else {
self.string[numbers] = self.formatter.string(from: self.now, to: self.eventDates[numbers]) ?? ""
}
}
This is the full code
import SwiftUI
struct ContentView: View {
#State private var selection = 0
#State private var eventDates = [Date]()
#State private var eventNames = [String]()
#State private var currentName = "";
#State private var counter = 0;
#State private var placeholderText = "Event Name";
#State private var selectedDate = Date();
var numbers = 0;
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
#State var now = Date();
#State var string = [String]();
var formatter = DateComponentsFormatter();
func differenceDate(numbers: Int) {
self.formatter.unitsStyle = .full
self.formatter.allowedUnits = [.day, .hour, .minute, .second]
//self.formatter.maximumUnitCount = 2
self.now = Date();
if self.now > self.eventDates[numbers] {
self.eventNames[numbers] = "";
}
else {
self.string[numbers] = self.formatter.string(from: self.now, to: self.eventDates[numbers]) ?? ""
}
}
var body: some View {
TabView(selection: $selection){
//Page 1
VStack{
Text("Add New Event")
.underline()
.font(.title)
.padding(15)
// .onReceive(self.timer) { input in
// self.differenceDate(numbers: index)
// //}
// }
// .minimumScaleFactor(0.1)
TextField("\(placeholderText)", text: $currentName)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 1)
.padding(5)
)
Text("When is your event?")
DatePicker("Please enter a date", selection: $selectedDate, displayedComponents: .date)
.labelsHidden()
.scaledToFill()
Button(action: {
if self.currentName != "" {
self.eventNames.append(self.currentName)
self.eventDates.append(self.selectedDate)
self.string.append("")
self.currentName = "";
}
})
{
Text("Add Event")
.font(.headline)
.foregroundColor(.black)
}
.padding(25)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 3)
.padding(5)
)
}
//Tab 1
.tabItem {
VStack {
Image(systemName: "calendar")
Text("Add Event")
}
}
.tag(1)
//Page 2
VStack{
Text("Your Events").underline()
.font(.title)
.padding(15)
ForEach(eventNames.indices, id: \.self) { index in
VStack{
Text("Your event " + "\(self.eventNames[index])" + " is in " + "\(self.string[index])")
.onReceive(self.timer) { input in
self.differenceDate(numbers: index)
}
}
}
}
//Tab 2
.font(.title)
.tabItem {
VStack {
Image(systemName: "flame.fill")
Text("Countdowns")
}
}
.tag(0)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I was wondering if there was a workaround or how to keep the timer firing while the tab changes or pause it when the tab changes and then start it again when the tab is swapped back over.
It needs to attach the .onReceive to the TabView and it will be received on all tabs, like
TabView {
...
// << all tab items here
...
}
.onReceive(self.timer) { _ in
self.differenceDate()
}
and iterate indexes inside of handler
func differenceDate() {
for numbers in eventNames.indices {
// current body here
}
}

Resources