Automatic slide switching (SwiftUI) - ios

I can not understand why the timer does not work, and the text does not scroll automatically.
I tried to do so:
ForEach(0..<numberText)
But I got such an error:
Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'some View' conform to 'TableRowContent'
Full code:
let numberText = ["text1","text2","text3","text4"]
struct TextNew: View {
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
#State private var index = 0
let textdata = TextData.getAllText()
var body: some View {
GeometryReader { proxy in
TabView(selection: $index) {
ForEach(numberText, id: \.self) { num in
Text("\(num)")
.font(.system(size: 10))
.foregroundColor(.white)
.tag(num)
.padding(.bottom, 50)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.frame(height: 80)
.onReceive(timer, perform: { _ in
withAnimation {
index = index < numberText.count ? index + 1 : 0
}
})
}
}
}
Thanks for any help, I'm new

Try this:
struct TextNew: View {
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
#State private var index = 1
#State private var selectedNum: String = ""
var body: some View {
GeometryReader { proxy in
TabView(selection: $selectedNum) {
ForEach(numberText, id: \.self) { num in
Text("\(num)")
.font(.system(size: 10))
.foregroundColor(.white)
.padding(.bottom, 50)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.onReceive(timer, perform: { _ in
withAnimation {
index = index < numberText.count ? index + 1 : 1
selectedNum = numberText[index - 1]
}
})
}
}
}
Explanation:
The reason for this not working is the type mismatch between your .tag and the $index in your TabView declaration. These have to match. By the way you do not need the .tag here as you are setting it to .self in your ForEach loop.

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

How to pass selected struct to another view?

I'm struggling to pass the right data to another View.
The idea is first to select the Task that has Status 0 and pass it to CardContentView to display under the Section of New tasks. If I print the values, its correct but it always displays the first data/array regardless of its Status. What could be done here?
struct Tasks: Identifiable {
let id = UUID()
let name: String
let status: Int
let image: String
}
extension Tasks {
static var testData: [Tasks] {
return [
Tasks(name: "Inprogress", status: 1, image:"a1"),
Tasks(name: "New", status: 0, image:"a2"),
]
}
}
ContentsView
struct ContentsView: View {
#State var items: [Tasks]
var size = 0
var body: some View {
NavigationView {
List {
let new = items.filter({$0.status == 0})
let size = new.count
if size > 0 {
Section(header:Text("\(size)" + " New")){
//let _ = print(new)
ForEach(new.indices, id: \.self) {itemIndex in
NavigationLink(destination: ChosenTask()) {
CardContentView(item: self.$items[itemIndex])
}
}
}
}
}
.navigationBarTitle("My tasks")
}
}
}
CardContentView
struct CardContentView: View {
#Binding var item: Tasks
var body: some View {
HStack {
VStack(alignment: .leading,spacing: 5) {
Label("Name: " + (item.name), systemImage: "person.crop.circle")
.font(.system(size: 12))
.labelStyle(.titleAndIcon)
}
.frame(maxWidth: .infinity, alignment: .leading)
Image(item.image)
.resizable()
.frame(width: 60, height: 70)
}
}
}
You are already passing the item to another view when you call CardContentView. You just have to do the same thing and pass the item to ChosenTask in your NavigationLink. When the user taps the item, SwiftUI will take care of creating and displaying the ChoseTask view for you.
You should also avoid using indices. There is no need. Your struct conforms to Identifiable so you can use it directly
var body: some View {
NavigationView {
List {
let new = items.filter({$0.status == 0})
if !new.isEmpty {
Section(header:Text("\(size)" + " New")){
//let _ = print(new)
ForEach(new) {item in
NavigationLink(destination: ChosenTask(item: item)) {
CardContentView(item: item)
}
}
}
}
}
.navigationBarTitle("My tasks")
}
}

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

A View with an ObservedObject and nothing will allow me to edit it

https://github.com/ryanpeach/RoutinesAppiOS
A Picker will not move, the Edit Button won't click, and a Text Field won't enter more than 1 character. Return on keyboard doesn't work. Most of these are tied to bindings to the ObservedObject and should be able to directly edit it. I believe there is a common cause with CoreData hanging on object updates. The done button on the final player view hangs, but the skip button does not! That means it only happens when TaskData is updated. If you delete an Alarm in certain situations the app crashes too.
Here's a video of the app behavior so far. In the view right before the end nothing will click. At the final view it hangs when you click the checkmark.
https://www.icloud.com/photos/#0HMiYqZ08ZYoFu5BEQXET4gRA
I am seeking some tips on how to debug this error. I can't put a breakpoint on the picker "when something changes" and I similarly cant do it on the Text Field. Why would a text field only take one character and then stop? Why would the edit button not work? This is the only view in the app where these sub-views don't work, the rest of the app works fine.
Some relevant background information:
I'm using coredata. There are 3 classes: AlarmData for the Routines Page, which has a one2many relationship to TaskData for the TaskList Page, which has a one2many relationship to SubTaskData for the TaskPlayerView and TaskEditor pages, the ones I'm having trouble with.
No further relationships.
I'm doing a fetchrequest at the root view and then using #ObservedObject the rest of the way down the view hierarchy. I'm using mostly isActive and tag:selection NavigationLinks.
The relevant file:
struct TaskEditorView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#ObservedObject var taskData: TaskData
#State var newSubTask: String = ""
var subTaskDataList: [SubTaskData] {
var out: [SubTaskData] = []
for sub_td in self.taskData.subTaskDataList {
out.append(sub_td)
}
return out
}
var body: some View {
VStack {
TitleTextField(text: self.$taskData.name)
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
TimePickerRelativeView(time: self.$taskData.duration)
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
HStack {
Spacer().frame(width: DEFAULT_LEFT_ALIGN_SPACE, height: DEFAULT_HEIGHT_SPACING)
ReturnTextField(
label: "New Subtask",
text: self.$newSubTask,
onCommit: self.addSubTask
)
Button(action: {
self.addSubTask()
}) {
Image(systemName: "plus")
.frame(width: DEFAULT_LEFT_ALIGN_SPACE, height: 30)
}
Spacer().frame(width: DEFAULT_HEIGHT_SPACING)
}
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
Text("Subtasks:")
Spacer().frame(height: DEFAULT_HEIGHT_SPACING)
List {
ForEach(self.subTaskDataList, id: \.id) { sub_td in
Text(sub_td.name)
}
.onDelete(perform: self.delete)
.onMove(perform: self.move)
}
}
.navigationBarItems(trailing: EditButton())
}
...
}
It also doesn't like to be edited here (see FLAG comment):
struct TaskPlayerView: View {
#Environment(\.managedObjectContext) var managedObjectContext
var taskDataList: [TaskData] {
return self.alarmData.taskDataList
}
var taskData: TaskData {
return self.taskDataList[self.taskIdx]
}
var subTaskDataList: [SubTaskData] {
var out: [SubTaskData] = []
for sub_td in self.taskData.subTaskDataList {
out.append(sub_td)
}
return out
}
#ObservedObject var alarmData: AlarmData
#State var taskIdx: Int = 0
// For the timer
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var isPlay: Bool = true
#State var done: Bool = false
#State var startTime: Date?
#State var lastTime: Date = Date()
#State var durationBeforePause: TimeInterval = 0
#State var durationSoFar: TimeInterval = 0
var body: some View {
VStack {
if !self.done {
...
HStack {
Spacer()
Button(action: {
withAnimation {
if self.subTaskDataList.count == 0 || self.subTaskDataList.allSatisfy({$0.done}) {
// FLAG: It fails here where setting task data
self.taskData.lastDuration_ = self.durationSoFar
self.taskData.done = true
self.taskData.lastEdited = Date()
self.next()
}
}
}) {
if self.subTaskDataList.count == 0 || self.subTaskDataList.allSatisfy({$0.done}) {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 100.0, height: 100.0)
} else {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 100.0, height: 100.0)
.foregroundColor(Color.gray)
}
}
Spacer()
}
...
}
...
}
...
}

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