I need help with dates. How do you initialize the DatePicker with a stored date? Say, for example, that the user entered an entry with a date. He then determines the date is wrong and would like to change the date. But currently the DatePicker in this code will always default to today's date instead of the stored date.
The state parameter startDate can't be initialized with a stored date above the body. It appears that I need to set startDate in the body.
import SwiftUI
import Combine
struct GetDate: View {
#ObservedObject var userData : UserData = UserData()
#State private var startDate = Date()
var body: some View {
//let startDate = userData.startDate
VStack (alignment: .leading) {
Form {
Text("startdate 1 = \(startDate)")
// enter start date
Section(header: Text("Enter Start Date")) {
HStack {
Image(systemName: "calendar.badge.clock")
.resizable()
.foregroundColor(Color(.systemRed))
.frame(width: 35, height: 35)
Spacer()
DatePicker("", selection: $startDate, in: ...Date(), displayedComponents: .date)
.datePickerStyle(CompactDatePickerStyle())
}
}
Button ( action: {
userData.saveDates(startDate: startDate)
}) {
HStack {
Spacer()
Text("Save Dates?")
Spacer()
}
}
}
.font(.body)
.navigationBarTitle("Get Date", displayMode: .inline)
}
}
}
class UserData: ObservableObject {
#Published var startDate: Date = Date() {
didSet {
UserDefaults.standard.set(startDate, forKey: "startDate") // save
}
}
init() {
// save / retrieve trip dates
if let sdate = UserDefaults.standard.object(forKey: "startDate") as? Date {
startDate = sdate
print("startDate 2 = \(startDate)")
} else {
UserDefaults.standard.set(Date(), forKey: "startDate")
}
}
func saveDates(startDate: Date) -> () {
self.startDate = startDate
UserDefaults.standard.set(self.startDate, forKey: "startDate")
print("startDate 3 = \(self.startDate)")
}
}
Use #AppStorage property wrapper:
struct GetDate: View {
#AppStorage("startDate") var startDate = Date()
#State private var selectedDate = Date()
var body: some View {
VStack (alignment: .leading) {
Form {
Text("startdate 1 = \(startDate)")
Section(header: Text("Enter Start Date")) {
HStack {
Image(systemName: "calendar.badge.clock")
.resizable()
.foregroundColor(Color(.systemRed))
.frame(width: 35, height: 35)
Spacer()
DatePicker("", selection: $selectedDate, in: ...Date(), displayedComponents: .date)
.datePickerStyle(CompactDatePickerStyle())
}
}
Button ( action: {
startDate = selectedDate // save
}) {
HStack {
Spacer()
Text("Save Dates?")
Spacer()
}
}
}
.font(.body)
.navigationBarTitle("Get Date", displayMode: .inline)
}
.onAppear {
selectedDate = startDate
}
}
}
// Support #AppStorage for `Date` type.
extension Date: RawRepresentable {
private static let formatter = ISO8601DateFormatter()
public var rawValue: String {
Date.formatter.string(from: self)
}
public init?(rawValue: String) {
self = Date.formatter.date(from: rawValue) ?? Date()
}
}
Just reset startDate to userData.startDate in .onAppear().
.onAppear {
startDate = userData.startDate
}
Related
I tried to do rename functionality when user click on rename bucket. BucketName is changed to the latest one but it's not reflecting on view neither in realm database. I debug it and find that its generate new bucker_id.
Here is my code:
**SwiftUI Code: **
#State private var selectedFolder = Set<UUID>()
.customDialog(isShowing: $renameDialogue, horizontalPadding: 30, dialogContent: {
RenameDialogueView(title: "Bucket", showCreateNewDialog: $renameDialogue, selectedFolder: $selectedFolder)
})
**Code of DialogueView **
import SwiftUI
struct RenameDialogueView: View {
var title: String
#State private var titleName = ""
#Binding var showCreateNewDialog: Bool
#Binding var selectedFolder: Set<UUID>
#EnvironmentObject var dataTypeViewModel: DataTypeViewModel
var body: some View {
VStack {
Text("Rename \(title)".uppercased())
.kerning(5)
.font(.custom("NiveauGroteskBold", size: 14))
.foregroundColor(Color("CornflowerColor"))
TextField("\(title) Name", text: $titleName)
.font(.custom("NiveauGroteskLight", size: 14))
.overlay(VStack{Divider().offset(x: 0, y: 15)})
.padding(.horizontal, 20)
.padding(.top, 30)
HStack {
Button {
showCreateNewDialog.toggle()
} label: {
Text("Cancel")
.padding(.horizontal, 30)
.padding(.vertical, 10)
.foregroundColor(.red)
.font(.custom("NiveauGroteskRegular", size: 14))
}
.frame(maxWidth: .infinity)
.background(.gray.opacity(0.15))
.cornerRadius(8)
Button {
selectedFolder.forEach { id in
bucketDao.updateBucketName(bucketId: id, newBucketName: titleName)
}
print(titleName)
showCreateNewDialog.toggle()
} label: {
Text("Done")
.padding(.horizontal, 30)
.padding(.vertical, 10)
.foregroundColor(Color("CornflowerColor"))
.font(.custom("NiveauGroteskRegular", size: 14))
}
.frame(maxWidth: .infinity)
.background(.gray.opacity(0.15))
.cornerRadius(8)
}
.padding(.top, 30)
}
.padding(30)
}
}
**Realm Model: **
class Bucket: Object {
#Persisted var bucket_name = ""
#Persisted (primaryKey: true)var bucket_id = UUID()
override static func primaryKey() -> String? {
return "bucket_id"
}
}
Function for rename bucketName
func updateBucketName(bucketId: UUID, newBucketName: String) throws {
let realm = try! Realm()
let bucketToUpdate = realm.objects(Bucket.self).filter("bucket_id == %#", bucketId).first
try! realm.write {
bucketToUpdate?.bucket_name = newBucketName
}
}
I tried to compare currentId of selected bucket but its showing some different Id.
As the title might not very clear, below is an example:
I have a View which is just DatePicker, name is "MIMIRxDatePicker"
struct MIMIRxDatePicker: View {
#Binding var dobStr: String
#Binding var screenShouldGrayOut: Bool
var body: some View {
VStack {
Text("Select Date of Birth: \(dateFormatter.string(from: self.dobStr))")
DatePicker(selection: self.dobStr , in: ...Date(), displayedComponents: .date) {
Text("")
}
Button(action: {
withAnimation {
self.screenShouldGrayOut.toggle()
}
}) {
Text("Choose").foregroundColor(.white).padding()
}.background(Color(Constants.ThemeColor)).cornerRadius(20)
}.cornerRadius(10).frame(width: 270).padding(20).background(Color(.white))
}
}
Heres is MIMIRxDatePicker s parent view: SignupContentView
struct SignupContentView: View {
#State var email: String = ""
#State var firstName: String = ""
#State var lastName: String = ""
#State var dobStr: String = ""
#State var screenShouldGrayOut: Bool = false
var body: some View {
ZStack {
Color(Constants.ThemeColor)
ScrollView(.vertical) {
Spacer().frame(height: 50)
VStack (alignment: .center, spacing: 2) {
Spacer()
Group {
Group {
HStack {
Text("Email:").modifier(AuthTextLabelModifier())
Spacer()
}.padding(2)
HStack {
Image(systemName: "envelope").foregroundColor(.white)
TextField("Email", text: $email).foregroundColor(.white)
}.modifier(AuthTextFieldContainerModifier())
}
Group {
HStack {
Text("First Name:").modifier(AuthTextLabelModifier())
Spacer()
}.padding(2)
HStack {
Image(systemName: "person").foregroundColor(.white)
TextField("First Name", text: $firstName).foregroundColor(.white)
}.modifier(AuthTextFieldContainerModifier())
}
Group {
HStack {
Text("Last Name:").modifier(AuthTextLabelModifier())
Spacer()
}.padding(2)
HStack {
Image(systemName: "person").foregroundColor(.white)
TextField("Last Name", text: $lastName).foregroundColor(.white)
}.modifier(AuthTextFieldContainerModifier())
Spacer()
HStack { GenderSelector() }
}
Group {
HStack {
Text("Date of Birth:").modifier(AuthTextLabelModifier())
Spacer()
}.padding(2)
HStack {
Image(systemName: "calendar").foregroundColor(.white)
TextField("", text: $dobStr).foregroundColor(.white).onTapGesture {
withAnimation {
self.screenShouldGrayOut.toggle()
}
}
}.modifier(AuthTextFieldContainerModifier())
}
}
}.padding()
}.background(screenShouldGrayOut ? Color(.black) : Color(.clear)).navigationBarTitle("Sign Up", displayMode: .inline)
if screenShouldGrayOut {
MIMIRxDatePicker(dobStr: $dobStr, screenShouldGrayOut: $screenShouldGrayOut).animation(.spring())
}
}.edgesIgnoringSafeArea(.all)
}
}
As you can see the #State var dobStr in "SignupContentView" is a String which is Date of birth's TextField needed, but in the MIMIRxDatePicker the DatePicker's selection param needs a type of Date, not a string, even if I declared a #Binding var dobStr: String in MIMIRxDatePicker.
I also tried:
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
and do:
DatePicker(selection: dateFormatter.date(from: self.dobStr) , in: ...Date()....
But it didn't work.
I know that assume if the DatePicker is a TextField then everything will work and good to go because TextField accept String only also, but that's not the case, I need the DatePicker.
So in SwiftUI how can I use #State and #Binding to deal with different types?
What you can do is to use Date for date manipulation and String for displaying the date.
Which means you can use Date variable in your Picker and String in the Text views.
struct MIMIRxDatePicker: View {
#State var dob: Date
...
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
var dobStr: String {
dateFormatter.string(from: self.dob)
}
var body: some View {
VStack {
// `String` for displaying...
Text("Select Date of Birth: \(self.dobStr)")
// and `Date` for date manipulation...
DatePicker(selection: self.$dob, in: ...Date(), displayedComponents: .date) {
Text("")
}
...
}
...
}
}
Then follow the same pattern in your SignupContentView. Generally try to use Date objects for date manipulation as they are less prone to errors and malformed data.
You need to first identify the types involved.
You are passing a Binding<String> to selection: which expects a Binding<Date>.
#Binding var dobStr: String is still Binding<String>
dateFormatter.date(from: self.dobStr) is Date, not Binding<Date>
What you need is to create a custom Binding<Date>, with its getter/setter interacting with Binding<String>
E.g.;
Binding(
get: { // return a Date from dobStr binding },
set: { // set dobStr binding from a Date}
)
Then use this Binding as argument to selection:
I'm trying to do something, and search, but I found nothing. I have a function DatePicker in a file. I call this function and send to it a binding var date. I want to keep only date and remove time from it. I have this function :
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}
But I use it like this : Text("(date, formatter: dateFormatter)")
How I can return the binding value with this formatter ?
thank you
edit my file :
struct WIDatePicker: View {
#Binding var date: Date
#State private var sheetDate = false
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}
var body: some View {
Button(action: {self.sheetDate.toggle()}) {
VStack {
HStack {
Text("\(date, formatter: dateFormatter)")
Print("Variables : \(dateFormatter.string(from: date))")
Spacer()
if (sheetDate == true) {
Divider()
DatePicker("", selection: $date, displayedComponents: .date)
.labelsHidden()
}
}
}
}
}
I guess you are asking how to use the formatter. Use it like this:
Text("date \(date, formatter: dateFormatter)")
maybe you could do something like this, to get the string date without hours and minutes.
struct WIDatePicker: View {
#State var date: Date
#State private var sheetDate = false
#State var myDateString = ""
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}
var body: some View {
let bindingDate = Binding<Date>(
get: { self.date },
set: {
self.myDateString = self.dateFormatter.string(from: $0)
self.date = $0
})
return Button(action: {
self.sheetDate.toggle()
}) {
VStack {
HStack {
Text("\(self.myDateString)")
Spacer()
if (sheetDate == true) {
Divider()
DatePicker("", selection: bindingDate, displayedComponents: .date).labelsHidden()
}
}
}
}.onAppear(perform: loadData)
}
func loadData() {
myDateString = dateFormatter.string(from: date)
}
}
Here's the situation, I have a Master / Detail view set up. When navigating from the "Events" view to the Events Details view. If a user taps the "Back" button, which I have designed using "Button(action: {self.presentationMode.wrappedValue.dismiss()})..", the view will temporarily change back to the Events list, but then jumps automatically back to the details view that a user was navigating from.
Here's the code on the Events List page
import SwiftUI
import Firebase
struct EventsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#State var data: [EventObject] = []
let db = Firestore.firestore()
var body: some View {
ZStack {
VStack {
List {
ForEach((self.data), id: \.self.eventID) { item in
NavigationLink(destination: EventDetail()) {
VStack {
HStack{
Text("\(item.eventDate)")
.font(.footnote)
.foregroundColor(Color("bodyText"))
Spacer()
}
HStack {
Text("\(item.eventTitle)")
.fontWeight(.bold)
.foregroundColor(Color("Charcoal"))
Spacer()
}.padding(.top, 8)
}.padding(.bottom, 16)
} // nav
}
Spacer()
}
.padding(.top, 60)
}
//Floating Navbar
ZStack {
VStack {
GeometryReader { gr in
HStack {
Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
Image(systemName: "chevron.left")
.foregroundColor(Color("Charcoal"))
.padding(.leading, 16)
HStack {
Text("Explore · Disney Events")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(Color("Charcoal"))
.padding()
Spacer()
}
}.frame(width: gr.size.width * 0.92, height: 48)
.background(Color("navBackground"))
.cornerRadius(8)
.shadow(color: Color("Shadow"), radius: 10, x: 2, y: 7)
}.padding(.leading, 16)
Spacer()
}
}
.padding(.top, 50)
.edgesIgnoringSafeArea(.top)
// Floating Nav Ends
}
}.onAppear(perform: self.queryEvents)
}
func queryEvents() {
self.data.removeAll()
self.db.collectionGroup("events").getDocuments() {(querySnapshot, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
for document in querySnapshot!.documents {
let id = document.documentID
let title = document.get("eventTitle") as! String
let shortDesc = document.get("eventShort") as! String
let description = document.get("eventDescription") as! String
let date = document.get("eventDate") as! Timestamp
let aDate = date.dateValue()
let formatter = DateFormatter()
formatter.dateFormat = "E, MMM d · h:mm a"
let formattedTimeZoneStr = formatter.string(from: aDate)
let address = document.get("eventAddress") as! String
let cost = document.get("eventCost") as! Double
let location = document.get("eventLocation") as! String
let webURL = document.get("eventURL") as! String
self.data.append(EventObject(id: id, title: title, shortDesc: shortDesc, description: description, date: formattedTimeZoneStr, address: address, cost: cost, location: location, webURL: webURL))
}
}
}
}
}
class EventObject: ObservableObject {
#Published var eventID: String
#Published var eventTitle: String
#Published var eventShort: String
#Published var eventDescription: String
#Published var eventDate: String
#Published var eventAddress: String
#Published var eventCost: Double
#Published var eventLocation: String
#Published var eventURL: String
init(id: String, title: String, shortDesc: String, description: String, date: String, address: String, cost: Double, location: String, webURL: String) {
eventID = id
eventTitle = title
eventShort = shortDesc
eventDescription = description
eventDate = date
eventAddress = address
eventCost = cost
eventLocation = location
eventURL = webURL
}
}
Event Details stripped down code below. I tried to take things away to search for the cause. It seems to be isolated to the Firebase call.
import SwiftUI
import Firebase
import MapKit
struct EventDetail: View {
#Environment(\.presentationMode) var presentationMode:
Binding<PresentationMode>
// var eventID: String
// var eventTitle: String
// var eventShort: String
// var eventDescription: String
// var eventDate: String
// var eventAddress: String
// var eventCost: Double
// var eventLocation: String
// var eventURL: String
var body: some View {
ZStack {
VStack {
GeometryReader { gr in
HStack {
Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
Image(systemName: "chevron.left")
.foregroundColor(Color("Charcoal"))
.padding(.leading, 16)
HStack {
Text("Events · Event Details")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(Color("Charcoal"))
.padding()
Spacer()
}
}.frame(width: gr.size.width * 0.92, height: 48)
.background(Color("navBackground"))
.cornerRadius(8)
.shadow(color: Color("Shadow"), radius: 10, x: 2, y: 7)
}.padding(.leading, 16)
Spacer()
}
}
.padding(.top, 50)
.edgesIgnoringSafeArea(.top)
}
}
}
Here's a video to illustrate what I'm talking about.
Dropbox Video Link
Here is a demo of possible approach based on simplified variant of your views. The idea is to use tag/selection based NavigationLink constructor and pass binding to selection to EventDetail to deactivate selection via binding and thus activate back navigation.
Note: I think that presentationMode was not designed for navigation scenario.
struct EventsView: View {
#State private var selectedItem: Int? = nil
var body: some View {
NavigationView {
List {
ForEach(0..<10, id: \.self) { item in
NavigationLink("Item \(item)", destination: EventDetail(selected: self.$selectedItem), tag: item, selection: self.$selectedItem)
}
}
}
}
}
struct EventDetail: View {
#Binding var selected: Int?
var body: some View {
VStack {
HStack {
Button(action: { self.selected = nil }) {
Image(systemName: "chevron.left")
HStack {
Text("Events · Event Details")
.padding()
Spacer()
}
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
Spacer()
}
}
}
I need to be able to add or minus 1 month from the current date.
So far I have this code:
import SwiftUI
struct DateView: View {
static let dateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
return formatter
}()
var date = Date()
var body: some View {
HStack {
Button(action: {
print("Button Pushed")
}) {
Image(systemName: "chevron.left")
.padding()
}
Spacer()
Text("\(date, formatter: Self.dateFormat)")
Spacer()
Button(action: {
print("Button Pushed")
}) {
Image(systemName: "chevron.right")
.padding()
}
}
.padding()
.background(Color.yellow)
}
}
struct DateView_Previews: PreviewProvider {
static var previews: some View {
DateView()
}
}
I would like to change the date displayed to be +1 month or -1 month depending on which chevron I will tap.
I am new to swift and swiftui and don't know what action I should use. I think it's related to DateComponents, but what should I do about it now? I am stuck. Please help me.
To better visualise what I have and want to do, here is an image of my current result:
Swift 5
Function to add or subtract month from current date.
func addOrSubtractMonth(month: Int) -> Date {
Calendar.current.date(byAdding: .month, value: month, to: Date())!
}
Now calling the function
// Subtracting
var monthSubtractedDate = addOrSubtractMonth(-7)
// Adding
var monthAddedDate = addOrSubtractMonth(7)
To Add date pass prospective value
To Subtract pass negative value
You can use the Calendar to add or subtract months/days/hours etc to your Date. Apple's documentation on the Calendar can be found here.
Below is a working example, showing how to increase/decrease the month by 1.
struct ContentView: View {
static let dateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
return formatter
}()
#State var date = Date()
var body: some View {
HStack {
Button(action: {
print("Button Pushed")
self.changeDateBy(-1)
}) {
Image(systemName: "chevron.left")
.padding()
}
Spacer()
Text("\(date, formatter: Self.dateFormat)")
Spacer()
Button(action: {
print("Button Pushed")
self.changeDateBy(1)
}) {
Image(systemName: "chevron.right")
.padding()
}
}
.padding()
.background(Color.yellow)
}
func changeDateBy(_ months: Int) {
if let date = Calendar.current.date(byAdding: .month, value: months, to: date) {
self.date = date
}
}
}