Pair of DatePickers SwiftUI - swift

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

Related

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 DatePicker in Form shows different text randomly

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

SwiftUI example for autocompletion

I'm a SwiftUI beginner. I have an array of "values" provided by an API and what I want is to make autocompletion when we tap characters in a "textfield". Can you please provide me an example of code for SwiftUI which can do this stuff ?
What I mean by autocompletion is this :
I have my own values and not those provided by google such here;
thx
The code from this repository used: https://github.com/simonloewe/TextFieldInputPrediction
And modified so the predictions are returned as a list like this:
//
// ContentView.swift
// StackOverflow
//
// Created by Simon Löwe on 04.04.20.
// Copyright © 2020 Simon Löwe. All rights reserved.
//
import SwiftUI
struct ContentView: View {
#State var textFieldInput: String = ""
#State var predictableValues: Array<String> = ["First", "Second", "Third", "Fourth"]
#State var predictedValue: Array<String> = []
var body: some View {
VStack(alignment: .leading){
Text("Predictable Values: ").bold()
HStack{
ForEach(self.predictableValues, id: \.self){ value in
Text(value)
}
}
PredictingTextField(predictableValues: self.$predictableValues, predictedValues: self.$predictedValue, textFieldInput: self.$textFieldInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
// This is the only modification from the example in the repository
List() {
ForEach(self.predictedValue, id: \.self){ value in
Text(value)
}
}
}.padding()
}
}
/// TextField capable of making predictions based on provided predictable values
struct PredictingTextField: View {
/// All possible predictable values. Can be only one.
#Binding var predictableValues: Array<String>
/// This returns the values that are being predicted based on the predictable values
#Binding var predictedValues: Array<String>
/// Current input of the user in the TextField. This is Binded as perhaps there is the urge to alter this during live time. E.g. when a predicted value was selected and the input should be cleared
#Binding var textFieldInput: String
/// The time interval between predictions based on current input. Default is 0.1 second. I would not recommend setting this to low as it can be CPU heavy.
#State var predictionInterval: Double?
/// Placeholder in empty TextField
#State var textFieldTitle: String?
#State private var isBeingEdited: Bool = false
init(predictableValues: Binding<Array<String>>, predictedValues: Binding<Array<String>>, textFieldInput: Binding<String>, textFieldTitle: String? = "", predictionInterval: Double? = 0.1){
self._predictableValues = predictableValues
self._predictedValues = predictedValues
self._textFieldInput = textFieldInput
self.textFieldTitle = textFieldTitle
self.predictionInterval = predictionInterval
}
var body: some View {
TextField(self.textFieldTitle ?? "", text: self.$textFieldInput, onEditingChanged: { editing in self.realTimePrediction(status: editing)}, onCommit: { self.makePrediction()})
}
/// Schedules prediction based on interval and only a if input is being made
private func realTimePrediction(status: Bool) {
self.isBeingEdited = status
if status == true {
Timer.scheduledTimer(withTimeInterval: self.predictionInterval ?? 1, repeats: true) { timer in
self.makePrediction()
if self.isBeingEdited == false {
timer.invalidate()
}
}
}
}
/// Capitalizes the first letter of a String
private func capitalizeFirstLetter(smallString: String) -> String {
return smallString.prefix(1).capitalized + smallString.dropFirst()
}
/// Makes prediciton based on current input
private func makePrediction() {
self.predictedValues = []
if !self.textFieldInput.isEmpty{
for value in self.predictableValues {
if self.textFieldInput.split(separator: " ").count > 1 {
self.makeMultiPrediction(value: value)
}else {
if value.contains(self.textFieldInput) || value.contains(self.capitalizeFirstLetter(smallString: self.textFieldInput)){
if !self.predictedValues.contains(String(value)) {
self.predictedValues.append(String(value))
}
}
}
}
}
}
/// Makes predictions if the input String is splittable
private func makeMultiPrediction(value: String) {
for subString in self.textFieldInput.split(separator: " ") {
if value.contains(String(subString)) || value.contains(self.capitalizeFirstLetter(smallString: String(subString))){
if !self.predictedValues.contains(value) {
self.predictedValues.append(value)
}
}
}
}
}
Provides the following outcome:
Tested on Version 11.5 and iOS 13.5

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

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.