SwiftUI DatePicker in Form shows different text randomly - datepicker

I am trying to use DatePicker in my app. After I select a date, the displayed text changes randomly. it changes on random dates too.
Sometimes it shows date like "Jan 26, 2021" and sometimes it shows "1/28/2021".
Can someone please help me find a solution here ?
I want to show something like "Jan 26, 2021" if that date is selected.
Here is how it works right now
Here is my code
import SwiftUI
struct WeirdDate: View {
#State var selectedDate : Date = Date()
var body: some View {
VStack {
Form {
Text("Testing out Date")
DatePicker(
"Date: ",
selection: $selectedDate,
displayedComponents: [.date]
)
}
}
}
}
struct WeirdDate_Previews: PreviewProvider {
static var previews: some View {
WeirdDate()
}
}

Related

Refresh picker selection when published variable is reset

I have a published variable that keeps track in this case, an amount of time. When a user saves their values, the variable will be reset (in this case back to an empty string. My recipeClass is updated correctly (as I can see my variable updating to the users selection, then back to "" when submitted. I'll include the published variable below here for reference:
#Published var recipePrepTime: String = ""
My Picker in the view is just not updating back to the initial value, in this case "5 minutes" How can I accomplish this? Any feedback would be great. Please find my code below:
struct RecipeEditorView: View {
#ObservedObject var recipeClass: Recipe
//#State var recipeTitle = ""
#State private var recipeTime = "Cook Time"
#State private var pickerTime: String = ""
//calls macro pickers
var cookingTime = ["5 Mins", "10 Mins","15 Mins","20 Mins","25 Mins","30 Mins ","45 Mins ","1 Hour","2 Hours", "A Long Time", "A Very Long Time"]
var body: some View {
HStack(spacing: 0){
ZStack{
Image(systemName:("clock"))
.padding(.leading, 150)
.foregroundColor(Color("completeGreen"))
//picker I would like updated
Picker(selection: $recipeClass.recipePrepTime, label: Text("")) {
ForEach(cookingTime, id: \.self) {
Text($0)
}
}
.onChange(of: pickerTime, perform: { _ in
recipeClass.recipePrepTime = pickerTime
})
.accentColor(.gray)
}
.multilineTextAlignment(.center)
}
}
}
}
Noting I didn't include my recipeClass as the values are being correctly updated on the backend.

SwiftUI's List displays the same row twice in ForEach [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 9 months ago.
Improve this question
can anyone please help me to solve this? Please paste the same code in your Xcode and run it. Please follow the below steps:
Select Date as 1 June 2022, Time 1 as 07:00 and Time2 as 15:00. Enter load1 as 7 and load2 as 8. Click on Add load. It will show up the date, time1 and time2 with total amount.
Similarly keeping the time same(07:00-15:00) and changing the dates to 3rd of June and 4th of June and with different values of load1 and load2, click on Add load.
Now click on the Edit button and delete the data of 4th June and 1st June and then Enter a new data using 2nd June. The date and total amount of 3rd June automatically changes to 2nd of June. So I now have both data as 2nd June. Also the Total comes up with a different value. Why is this happening? any idea where am I doing wrong here?
struct Task : Identifiable {
var id = String()
var toDoItem = String()
var amount : Float = 0 //<-- Here
}
class TaskStore : ObservableObject {
#Published var tasks = [Task]()
}
struct Calculation: View {
#State var load1 = Float()
#State var load2 = Float()
#State var gp : Float = 0
#State var rate: Float = 0
#ObservedObject var taskStore = TaskStore()
#State private var birthDate = Date()
#State private var time1 = Date()
#State private var time2 = Date()
func addNewToDo() {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy"
let dayoftime = formatter.string(from: birthDate)
let formatter2 = DateFormatter()
formatter2.dateFormat = "HH:mm"
let timeof1 = formatter2.string(from: time1)
let timeof2 = formatter2.string(from: time2)
taskStore.tasks.append(Task(id: String(taskStore.tasks.count + 1), toDoItem: "\(dayoftime) \(timeof1)-\(timeof2) total = \(rate.description) ", amount: rate))
//gp += rate
}
var body: some View {
NavigationView {
VStack {
HStack {
VStack(spacing: 1) {
Section(header: Text("Date") .foregroundColor(.black)
){
DatePicker(selection: $birthDate, displayedComponents: [.date]){Text(" Time")
}
}
VStack(spacing: 1) {
Section(header: Text("Time1") .foregroundColor(.black)){
DatePicker(selection: $time1, displayedComponents: [.hourAndMinute]){Text(" Time 1")
}
}
VStack(spacing: 1) {
Section(header: Text("Time2") .foregroundColor(.black)
){ DatePicker(selection: $time2, displayedComponents: [.hourAndMinute]){Text(" Time 1")
}
}
}
List {
Section(header:Text("load 2"))
{
TextField("Enter value of load 1", value: $load1, format: .number)
TextField("Enter value of load 1", value: $load2, format: .number)
}
HStack {
Button(String(format: "Add Load"), action: {
print(Rocky(mypay: rate))
gp += rate
})
Button(action: {
addNewToDo()
Rocky(mypay: rate)
},
label: {
Text(" ")
})
}
ForEach(self.taskStore.tasks) { task in
Text(task.toDoItem)
}
.onMove(perform : self.move)
.onDelete(perform : self.delete) //For each
}
.navigationBarTitle("SHIFTS")
.navigationBarItems(trailing: EditButton()) //List
Text("Total = $\(gp) ")
}.onAppear()
}
}
}
}
}
func Rocky(mypay: Float)
{
rate = load1 + load2
print("Sus \(gp)")
}
func move(from source : IndexSet, to destination : Int)
{
taskStore.tasks.move(fromOffsets: source, toOffset: destination)
}
func delete(at offsets : IndexSet) {
if let index = offsets.first { //<-- Here
let task = taskStore.tasks[index]
gp -= task.amount
}
taskStore.tasks.remove(atOffsets: offsets)
}
}
The issue is that ForEach needs your items to be uniquely identifiable.
You're using an autogenerated id for your tasks that is basically calculated as a current count of tasks in the storage:
taskStore.tasks.append(Task(id: String(taskStore.tasks.count + 1), ......`
So here's what's happening:
You add first three tasks, they get the IDs of "1", "2" and "3".
You remove the tasks with the IDs "1" and "3". The only task that 's remaining in the array is the one with the ID of "2".
You're adding another task to the list. When creating a task, the count of the tasks in the array is 1, so the new task gets the ID of "2". So now you have two different tasks in the array with the same IDs.
When SwiftUI renders you view, the ForEach method distinguishes your tasks by ID. It sees that it should render two rows of the list and both of them have the ID of "2". So it finds the first item with the ID equal to "2" and renders it twice because it assumes that all items in the array have unique IDs (which the should have really).
To solve this, use a UUID as an identifier for a task.
You can also create an initializer that doesn't take an ID as an argument because the task will create a unique identifier for itself:
struct Task: Identifiable {
var id: UUID
var toDoItem: String
var amount: Float
init(toDoItem: String, amount: Float) {
self.id = UUID()
self.toDoItem = toDoItem
self.amount = amount
}
}
When creating a new task, use this new initializer without assigning items an explicit identifier:
taskStore.tasks.append(
Task(
toDoItem: "\(dayoftime) \(timeof1)-\(timeof2) total = \(rate.description) ",
amount: rate
)
)
That's it, now all tasks will have unique identifiers and will render correctly.

SwiftUI TextField Date Binding

Hmmm. I'm not doing very well with these bindings.
struct RecordForm: View
{
#State var record: Record
var body: some View
{
TextField("date", text: Binding($record.recordDate!)))
}
}
I want to convert this Date to a String. In regular Swift I would just call my extension
record.recordDate.mmmyyy()
but I cannot find the right syntax or even the right place to do the conversion.
If I try to put the code in the body or the struct I just get a pile of errors.
Is there any easy to read documentation on this subject?
The answer by nine stones worked nicely, although I had to tweak the code slightly to get it to work for me with an NSManagedObject:
struct RecordDate: View
{
#State var record: Record //NSManagedObject
var body: some View {
let bind = Binding<String>(
get: {self.$record.recordDate.wrappedValue!.dateString()},
set: {self.$record.recordDate.wrappedValue = dateFromString($0)}
)
return HStack {
Text("Date:")
TextField("date", text: bind)
}
}
}
//dateString is a date extension that returns a date as a string
//dateFromString is a function that returns a string from a date
Documentation is hard to find and Apple's really ucks.
Try to create a custom binding.
extension Date {
func mmyyy() -> String { "blah" }
static func yyymm(val: String) -> Date { Date() }
}
struct RecordForm: View {
struct Record {
var recordDate: Date
}
#State var record = Record(recordDate: Date())
var body: some View {
let bind = Binding(
get: { self.record.recordDate.mmyyy() },
set: { self.record.recordDate = Date.yyymm(val: $0)}
)
return VStack {
TextField("date", text: bind)
}
}
}

Pair of DatePickers SwiftUI

I have a pair of DatePickers in my SwiftUI application which denote the start and end of some time period.
The intended behaviour is that the end date is never before the start date. Thus, the value of endDateTime should change when startDateTime is changed by the DatePicker. I'm unsure of how exactly to go about this, as I do not believe that DatePicker provides something like onValueChanged.
Code
struct ContentView: View {
#State var startDateTime: Date = Date()
#State var endDateTime: Date = Date()
var body: some View {
Form {
DatePicker(selection: $startDateTime, in: Date.distantPast...Date.distantFuture) {
Text("Start")
}
DatePicker(selection: $endDateTime, in: startDateTime...Date.distantFuture) {
Text("End")
}
}
}
}
Here is possible approach (tested & works with Xcode 11.2 / iOS 13.2)
Form {
DatePicker(selection: Binding<Date>(
get: { self.startDateTime },
set: { self.startDateTime = $0
if self.endDateTime < $0 {
self.endDateTime = $0
}
}), in: Date.distantPast...Date.distantFuture) {
Text("Start")
}
DatePicker(selection: $endDateTime, in: startDateTime...Date.distantFuture) {
Text("End")
}
}

Why does picker binding not update when using SwiftUI?

I have just begun learning Swift (and even newer at Swift UI!) so apologies if this is a newbie error.
I am trying to write a very simple programme where a user chooses someone's name from a picker and then sees text below that displays a greeting for that person.
But, the bound var chosenPerson does not update when a new value is picked using the picker. This means that instead of showing a greeting like "Hello Harry", "Hello no-one" is shown even when I've picked a person.
struct ContentView: View {
var people = ["Harry", "Hermione", "Ron"]
#State var chosenPerson: String? = nil
var body: some View {
NavigationView {
Form {
Section {
Picker("Choose your favourite", selection: $chosenPerson) {
ForEach ((0..<people.count), id: \.self) { person in
Text(self.people[person])
}
}
}
Section{
Text("Hello \(chosenPerson ?? "no-one")")
}
}
}
}
}
(I have included one or two pieces of the original formatting in case this is making a difference)
I've had a look at this question, it seemed like it might be a similar problem but adding .tag(person) to Text(self.people[person])did not solve my issue.
How can I get the greeting to show the picked person's name?
Bind to the index, not to the string. Using the picker, you are not doing anything that would ever change the string! What changes when a picker changes is the selected index.
struct ContentView: View {
var people = ["Harry", "Hermione", "Ron"]
#State var chosenPerson = 0
var body: some View {
NavigationView {
Form {
Section {
Picker("Choose your favourite", selection: $chosenPerson) {
ForEach(0..<people.count) { person in
Text(self.people[person])
}
}
}
Section {
Text("Hello \(people[chosenPerson])")
}
}
}
}
}
The accepted answer is right if you are using simple arrays, but It was not working for me because I was using an array of custom model structs with and id defined as string, and in this situation the selection must be of the same type as this id.
Example:
struct CustomModel: Codable, Identifiable, Hashable{
var id: String // <- ID of type string
var name: String
var imageUrl: String
And then, when you are going to use the picker:
struct UsingView: View {
#State private var chosenCustomModel: String = "" //<- String as ID
#State private var models: [CustomModel] = []
var body: some View {
VStack{
Picker("Picker", selection: $chosenCustomModel){
ForEach(models){ model in
Text(model.name)
.foregroundColor(.blue)
}
}
}
Hope it helps somebody.