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

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)
}
}

Related

How to make a 5 stars review widget with an incremental of 0.5 in SwiftUI

I have the widget fully working with the 5 stars, the data binding and the interface as follows:
import SwiftUI
struct mainView: View {
#State var rating: Double = 0.0
var body: some View {
VStack {
RatingView(rating: $rating)
Text("\(String(format: "%.1f", rating))")
.font(.system(size: 30))
}.background(.mint)
}
}
struct RatingView: View {
#Binding var rating: Double
var offColor = Color.white
var onColor = Color("AccentColor")
var starHalf = Image(systemName: "star.leadinghalf.filled")
var body: some View {
HStack {
ForEach(1..<6) { number in
Image(systemName: "star.fill")
.foregroundColor(number > Int(self.rating) ? self.offColor : self.onColor)
.onTapGesture {
self.rating = Double(number)
selectionChanged()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
mainView()
}
}
Works perfect, but have no idea how can I implement to have a 0.5 increment, and to change the icon for the half star. there is nothing like .onHelfLeftTapGesture() I can use?
You can't do it reasonably with a tap gesture, unless you want to go a half star at a time. But using a different mechanism will make it flow better. Simply use a Slider with an .opacity(0.1) so it works, but is transparent, in an .overlay() that is keyed to the stars:
struct RatingsView: View {
let ratingsArray: [Double]
let color: Color
#Binding var rating: Double
init(rating: Binding<Double>, maxRating: Int = 5, starColor: Color = .yellow) {
_rating = rating
ratingsArray = Array(stride(from: 0.0, through: Double(max(1, maxRating)), by: 0.5))
color = starColor
}
var body: some View {
VStack {
HStack(spacing: 0) {
ForEach(ratingsArray, id: \.self) { ratingElement in
if ratingElement > 0 {
if Int(exactly: ratingElement) != nil && ratingElement <= rating {
Image(systemName: "star.fill")
.foregroundColor(color)
} else if Int(exactly: ratingElement) == nil && ratingElement == rating {
Image(systemName: "star.leadinghalf.fill")
.foregroundColor(color)
} else if Int(exactly: ratingElement) != nil && rating + 0.5 != ratingElement {
Image(systemName: "star")
.foregroundColor(color)
}
}
}
}
.overlay(
Slider(value: $rating, in: 0.0...ratingsArray.last!, step: 0.5)
.tint(.clear)
.opacity(0.1)
)
}
.onAppear {
rating = Int(exactly: rating) != nil ? rating : Double(Int(rating)) + 0.5
}
}
}
No, I didn't just come up with this, but had come up with this before Christmas to play with the idea. This is the code I came up with.

Swift UI | Textfield not reading entered value

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()
}
}

SwiftUI - avoid AnyPublisher the first time

I've created publishers to validate the user input, in this case just validate they have 32 chars length.
ConnectionVM
import UIKit
import Combine
class ConnectionVM: ObservableObject {
private var cancellableSet: Set<AnyCancellable> = []
//INPUT
#Published var uuid1: String = ""
#Published var uuid2: String = ""
//OUTPUT
#Published var uuid1Message = ""
#Published var uuid2Message = ""
init() {
isUUID1ValidPublisher
.receive(on: RunLoop.main)
.map { valid in
valid ? "" : "UUID1 must have 32 characters"
}
.assign(to: \.uuid1Message, on: self)
.store(in: &cancellableSet)
isUUID2ValidPublisher
.receive(on: RunLoop.main)
.map { valid in
valid ? "" : "UUID2 must have 32 characters"
}
.assign(to: \.uuid2Message, on: self)
.store(in: &cancellableSet)
}
private var isUUID1ValidPublisher: AnyPublisher<Bool, Never> {
$uuid1
.debounce(for: 0.8, scheduler: RunLoop.main)
.removeDuplicates()
.map { input in
return input.count == 32
}
.eraseToAnyPublisher()
}
private var isUUID2ValidPublisher: AnyPublisher<Bool, Never> {
$uuid2
.debounce(for: 0.8, scheduler: RunLoop.main)
.removeDuplicates()
.map { input in
return input.count == 32
}
.eraseToAnyPublisher()
}
private var isFormValidPublisher: AnyPublisher<Bool, Never> {
Publishers.CombineLatest(isUUID1ValidPublisher, isUUID2ValidPublisher)
.map { uuid1IsValid, uuid2IsValid in
return uuid1IsValid && uuid2IsValid
}
.eraseToAnyPublisher()
}
}
ConnectionView
import SwiftUI
let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)
struct ConnectionView: View {
#ObservedObject var keyboardResponder = KeyboardResponder()
#ObservedObject var viewModel = ConnectionVM()
// #State var uuid1: String = ""
// #State var uuid2: String = ""
#State var authenticationDidFail: Bool = false
var body: some View {
return VStack {
WelcomeText()
LogoImage()
UUIDTextField(uuid: $viewModel.uuid1)
if !viewModel.uuid1Message.isEmpty {
Text(viewModel.uuid1Message)
.offset(y: -10)
.foregroundColor(.red)
}
UUIDTextField(uuid: $viewModel.uuid2)
if !viewModel.uuid2Message.isEmpty {
Text(viewModel.uuid2Message)
.offset(y: -10)
.foregroundColor(.red)
}
Button(action: {
print("Button tapped")
}) {
LoginButtonContent()
}
}
.padding()
.offset(y: -keyboardResponder.currentHeight*0.5)
}
struct WelcomeText : View {
var body: some View {
return Text("Welcome!")
.font(.largeTitle)
.fontWeight(.semibold)
.padding(.bottom, 20)
}
}
struct LogoImage : View {
var body: some View {
return Image("logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 150, height: 150)
.clipped()
.cornerRadius(150)
.padding(.bottom, 75)
}
}
struct UUIDTextField : View {
#Binding var uuid: String
var body: some View {
return TextField("UUID", text: $uuid)
.padding()
.background(lightGreyColor)
.cornerRadius(5.0)
.padding(.bottom, 20)
}
}
struct LoginButtonContent : View {
var body: some View {
return Text("LOGIN")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.green)
.cornerRadius(15.0)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ConnectionView()
}
}
The problem is the first time when the screen is just opened, the error messages appear automatically.
You basically need another condition to guard whether to display the message.
That condition could be that the field has first changed, so you'd count the number of times each field has changed, and produce a Bool on whether it has changed more than once (i.e. beyond the initial assignment of "")
let hasUUID1Changed = $uuid1.scan(0, { a, _ in a + 1}).map { $0 > 1 }
let hasUUID2Changed = $uuid2.scan(0, { a, _ in a + 1}).map { $0 > 1 }
Publishers.Scan acts as the counter / accumulator in this case. The lifetime would be the lifetime of the view model.
Then you could use this publisher in combination with the isUUID1ValidPublisher to determine whether to display the message:
isUUID1ValidPublisher
.receive(on: RunLoop.main)
.combineLatest(hasUUID1Changed) // here
.map { (valid, changed) in
valid && !changed ? "" : "UUID1 must have 32 characters"
}
.sink { [weak self] self?.uuid1Message = $0 }
.store(in: &cancellableSet)

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