SwiftUI: DatePicker/Date() formating - swift

I have a .sheet element, that acts as a form when a user is adding to a calendar. Everything works as expected, but as soon as they submit, and upload the Date, Start Time, and end time into firebase where it's held, I noticed a problem. The date and time were formated weird.
My question is, how can I change the var scheduledate to a mm/dd/yy format when stored, and how can I change the var starttime and var endtime to a h:mm format when stored.
Here's my code:
struct SheetView: View {
var userEmail = Auth.auth().currentUser?.email
#Environment(\.dismiss) var dismiss
#State private var scheduledate = Date() //Store this as mm/dd/yy
#State private var startTime = Date() //Store this as h:mm
#State private var endTime = Date() //Store this as h:mm
#State private var scheduleairplane = ""
#State private var scheduleinstructor = ""
var body: some View {
VStack {
HStack {
Text("New Flight")
.font(.title)
.fontWeight(.bold)
Spacer()
Button("Cancel") {
dismiss()
}
}
.padding()
VStack {
DatePicker("Date", selection: $scheduledate, displayedComponents: .date)
.datePickerStyle(GraphicalDatePickerStyle())
.frame(maxHeight: 400)
DatePicker("Start Time", selection: $startTime, displayedComponents: .hourAndMinute)
.datePickerStyle(GraphicalDatePickerStyle())
.padding(.leading)
DatePicker("End Time", selection: $endTime, displayedComponents: .hourAndMinute)
.datePickerStyle(GraphicalDatePickerStyle())
.padding(.leading)
}
VStack {
TextField("Airplane...", text: self.$scheduleairplane)
.padding([.leading, .trailing])
.offset(x: 10)
.frame(height: 30)
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color.white, lineWidth: 1)
.padding([.leading, .trailing])
)
TextField("Instructor...", text: self.$scheduleinstructor)
.padding([.leading, .trailing])
.offset(x: 10)
.frame(height: 30)
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color.white, lineWidth: 1)
.padding([.leading, .trailing])
)
}
Spacer(minLength: 0)
Button(action: {
let doc = db.collection("Scheduled").document("\(userEmail ?? "Error")")
// Atomically add a new region to the "regions" array field.
doc.updateData([
"Airplanes": FieldValue.arrayUnion(["\(scheduleairplane)"])
])
doc.updateData([
"Date": FieldValue.arrayUnion(["\(scheduledate)"])
])
doc.updateData([
"Time": FieldValue.arrayUnion(["\(ScheduledTime)"])
])
doc.updateData([
"Instructor": FieldValue.arrayUnion(["\(scheduleinstructor)"])
])
}) {
Text("Submit")
}
.foregroundColor(.white)
.frame(width: 120, height: 45)
.background(Color("Blue"))
.cornerRadius(30)
.padding(.bottom)
}
}
}

Thanks for all the help but I found an answer
here that works very well and is easy.
I used 3 different functions that I ran when I click the button. Then I send them off to firebase with the new values.
func updateDate() {
let selecteddate = scheduleddate //Here it gets the date and saves as another variable
let formatter1 = DateFormatter()
formatter1.dateStyle = .short
//I then made a saved string called newscheduledate to use this new variable anywhere in my project.
newscheduledate = formatter1.string(from: selecteddate) //Here is when it sets the conversion to a string which I used when I sent to firebase.
}
I then used this piece of code below twice, for the start time, and the end time.
func updateTime() {
let selectedtime = startTime //Here it gets the date
let formatter2 = DateFormatter()
formatter2.timeStyle = .medium
//I have a saved string called newstarttime to use this new variable anywhere in my project.
newstarttime = formatter2.string(from: selectedtime)
}

class GeneralExtension{
static func DateToStr(date:Date,format:String = "yyyy/MM/dd HH:mm:ss")->String{
let timeFormatter = DateFormatter()
timeFormatter.locale = Locale.current
timeFormatter.timeZone = TimeZone.current
timeFormatter.dateFormat = format
let nowTimestr = timeFormatter.string(from: date)
return nowTimestr
}
static func StrToDate(dateStr:String,format:String = "yyyy/MM/dd HH:mm:ss")->Date{
var cbDate:Date = Date()
if(!dateStr.isEmpty){
let dateFormatter=DateFormatter()
dateFormatter.dateFormat=format
let tmpDate = dateFormatter.date(from: dateStr)
if let tmpDate = tmpDate{
cbDate = tmpDate
}
}
return cbDate
}
}
example
GeneralExtension.DateToStr(date: dateSetting,format: "MM")
or
extension String{
func StrToDate(format:String = "yyyy/MM/dd HH:mm:ss")->Date{
var cbDate:Date = Date()
if(!self.isEmpty){
let dateFormatter=DateFormatter()
dateFormatter.dateFormat=format
let tmpDate = dateFormatter.date(from: self)
if let tmpDate = tmpDate{
cbDate = tmpDate
}
}
return cbDate
}
}
extension Date{
func DateToStr(format:String = "yyyy/MM/dd HH:mm:ss")->String{
let timeFormatter = DateFormatter()
timeFormatter.locale = Locale.current
timeFormatter.timeZone = TimeZone.current
timeFormatter.dateFormat = format
let nowTimestr = timeFormatter.string(from: self)
return nowTimestr
}
}
example
let date = Date()
let dateStr = date.DateToStr("MM")
let convertDate = dateStr.StrToDate()

Related

Streak builder app using Swift and SwiftUI

I am looking to make a Streak builder app using Swift and SwiftUI. However, I am finding it difficult to create the logic for the counter using the Date(). Any suggestions will be highly appreciated.
Mainly I wanna replicate the Streak thing from the Duolingo app.
// I have this extension to follow up on the Date from the time user clicks it.
extension Date {
// for tomorow's Date
static var tomorrow: Date { return Date().dayAfter }
static var today: Date {return Date()}
var dayAfter: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: Date())!
}
}
// More or less this was supposed to be my view.
struct StreakTrial: View {
#State var counter = 0
#State var TapDate: Date = Date.today
var body: some View {
NavigationView {
VStack{
Button {
// if TapDate != Date.today {
// counter += 1
// let TapDate = Date.tomorrow
// }
// else if TapDate == Date.tomorrow {
// counter = counter
// }
} label: {
Image(systemName: "flame")
.resizable()
.frame(width: 40, height: 50)
.padding()
.scaledToFit()
.background(Color.gray)
.foregroundColor(Color.orange)
.cornerRadius(12)
Text("\(counter)").foregroundColor(.gray)
}
Text("\(Date())")
.padding()
Text("\(Date().dayAfter)")
.padding()
}
}
}
}
**SO I TRIED SOME TUTORIALS OF #NICK SARNO WHICH GOES LIKE SWIFTFUL THINKING ON YOUTUBE AND GOT IT DONE**
*This code is compatible with Xcode 14.0*
import SwiftUI
extension Date {
// for tomorow's Date
static var tomorrow: Date { return Date().dayAfter }
static var today: Date {return Date()}
var dayAfter: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: Date())!
// just add .minute after byAdding: , to create a streak minute counter and check the logic.
}
static func getTodayDate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E d MMM yyyy"
//to continue with the minute streak builder just add "E d MMM yyyy h:mm a" above, it will allow date formatting with minutes and follow the changes in dayAfter
return dateFormatter.string(from: Date.today)
}
static func getTomDate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E d MMM yyyy"
return dateFormatter.string(from: Date.tomorrow)
}
}
struct StreakApp: View {
#AppStorage("counter") var counter = 0
#AppStorage("tapDate") var TapDate: String?
#AppStorage("Tappable") var ButtonTapped = false
var body: some View {
NavigationView {
VStack{
VStack {
Text("\(counter)").foregroundColor(.gray)
Text("Restore your streak on ")
Text(TapDate ?? "No Date")
Image(systemName: "flame")
.resizable()
.frame(width: 40, height: 50)
.padding()
.scaledToFit()
.background(ButtonTapped ? Color.red : Color.gray)
.foregroundColor(ButtonTapped ? Color.orange : Color.black)
.cornerRadius(12)
}
Button {
if TapDate == nil {
//Check if user has already tapped
self.ButtonTapped = true
counter += 1
self.TapDate = ("\(Date.getTomDate())")
}
else if ("\(Date.getTodayDate())") == TapDate {
//Check for the consecutive Day of Streak
self.TapDate = ("\(Date.getTomDate())")
counter += 1
//Let's light the flame back again.
self.ButtonTapped = true
}
} label: {
RoundedRectangle(cornerRadius: 12, style: .continuous)
.foregroundColor(.black)
.frame(width: 120, height: 40)
.overlay {
Text("Add Streak")
.foregroundColor(.white)
}
}
.padding()
//This button is only for testing purpose.
Button {
self.TapDate = nil
self.ButtonTapped = false
self.counter = 0
} label: {
RoundedRectangle(cornerRadius: 12, style: .continuous)
.foregroundColor(.black)
.frame(width: 160, height: 40)
.overlay {
Text("Reset Streak")
.foregroundColor(.white)
}
}
}
//Ensuer the flame dies out if we run into any other day except today or tommorow.
.onAppear {
if ("\(Date.getTodayDate())") == TapDate ||
("\(Date.getTomDate())") == TapDate {
self.ButtonTapped = true
}
//Breaking the Streak
else {
self.TapDate = nil
self.ButtonTapped = false
self.counter = 0
}
}
}
}
}
struct StreakApp_Previews: PreviewProvider {
static var previews: some View {
StreakApp()
}
}

How to save a value correctly to userdefaults?

I have an issue and I hope that I will find the answer here to my questions. I made a Circular Timer, but everytime I'm closing the view , is going back from the start.
I tried to save the value where the time is calculated "onTick", is working for 2-3 seconds, and after again is going back from the start and is countdown from there.
I'll put a picture/video bellow with the behaviour, maybe somone can help me fix it .
Thanks !
This is WaitingOrderView.
struct WaitingOrderView: View {
#State private var timer: AnyCancellable?
#EnvironmentObject var syncViewModel : SyncViewModel
var body: some View {
ZStack {
if syncViewModel._order.id == 0 && syncViewModel._order.status == 0 {
CartView()
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.accepted.rawValue
})?.id
{
OrderConfirmedView()
}
}
.onAppear() {
startFetchStatus()
}
}
func startFetchStatus() {
timer = Timer.publish(every: 20, on: .main, in: .common)
.autoconnect()
.sink { _ in
syncViewModel.fetchOrder(id: syncViewModel._order.id)
}
}
}
The function startFetchStatus() gets the data from the backend every 20 seconds, and it looks like this, for example: Fetch response
This is the CircularTimer View :
let timer = Timer
.publish(every: 5, on: .main, in: .common)
.autoconnect()
#available(iOS 15, *)
struct CircularTimer: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var orderDate : Date
#State var orderDeliveryDate : Date
#State var onTick: Double = 1
#State var savedOnTick : Double = 1
let date = Date()
var body: some View {
VStack(spacing : 0){
Image("clock_button")
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.gray.opacity(22/100), lineWidth: 5)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: onTick)
.stroke(
style: StrokeStyle(
lineWidth: 5,
lineCap: .round ,
lineJoin:.round
)
)
.foregroundColor(
( completed() ? Color.orange : Color.orange)
).animation(
.easeInOut(duration: 0.2)
)
)
.rotationEffect(Angle(degrees: 270.0))
Image("indicator_ellipse")
.resizable()
.frame(width: 230, height: 230)
}
.onAppear {
getData()
print(savedOnTick)
}
.onDisappear {
saveData()
}
}
.onReceive(timer) { time in
progress(time: Int(time.timeIntervalSince1970))
}
}
func completed() -> Bool {
return onTick == 1
}
func progress(time: Int) {
let minutesOrderDeliveryDate = Int(orderDeliveryDate.timeIntervalSince1970)
let minutesOrderDate = Int(orderDate.timeIntervalSince1970)
let minutesCurrentDate = time
let totalMinutes = minutesOrderDeliveryDate - minutesOrderDate
let remainingMinutes = minutesOrderDeliveryDate - minutesCurrentDate
onTick = CGFloat(remainingMinutes) / CGFloat(totalMinutes)
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter.date(from: date) ?? Date()
}
func saveData() {
UserDefaults.standard.set(onTick, forKey: "onTick")
}
func getData() {
onTick = UserDefaults.standard.double(forKey: "onTick")
}
}
This is OrderConfirmedView :
struct OrderConfirmedVieww: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var nowDate: Date = Date()
var body: some View {
VStack {
Spacer()
Text(Texts.orderConfirmedText1)
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.colorGrayDark)
.multilineTextAlignment(.center)
.lineLimit(3)
.padding(40)
CircularTimer(orderDate: dateFormatTime(date: syncViewModel._order.date ?? ""), orderDeliveryDate: dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""))
.padding()
// Spacer()
Text(Texts.orderOraLivrareText)
.font(.headline)
.fontWeight(.thin)
.padding(.bottom)
Text(dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""), style: .time)
.font(.title2)
.fontWeight(.bold)
Spacer()
Spacer()
Button {
} label: {
Text(Texts.orderButtonText)
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.onboardingColor)
.cornerRadius(20)
}
.padding()
}
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}

How to save a Timer value when the view is closed?

I've made a similiar question yesterday, but I have fixed the problem. Now I have another issue and I made a new question to ask.
I've made a Circular Timer which countdown : CircularTimer
But if I'm closing the view, and open it again, it starts again from scratch. How can I save the progress of the timer or something like that, so if I'm closing the view, when I'm coming back to be where it was last time ?
Thanks !
This is WaitingOrderView.
struct WaitingOrderView: View {
#State private var timer: AnyCancellable?
#EnvironmentObject var syncViewModel : SyncViewModel
var body: some View {
ZStack {
if syncViewModel._order.id == 0 && syncViewModel._order.status == 0 {
CartView()
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.accepted.rawValue
})?.id
{
OrderConfirmedView()
}
}
.onAppear() {
startFetchStatus()
}
}
func startFetchStatus() {
timer = Timer.publish(every: 20, on: .main, in: .common)
.autoconnect()
.sink { _ in
syncViewModel.fetchOrder(id: syncViewModel._order.id)
}
}
}
The function startFetchStatus() gets the data from the backend every 20 seconds, and it looks like this, for example: Fetch response
This is the CircularTimer View :
let timer = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
#available(iOS 15, *)
struct CircularTimer: View {
var orderDate : Date
var orderDeliveryDate : Date
#State var onTick: CGFloat = 1
let date = Date()
var body: some View {
VStack(spacing : 0){
Image("clock_button")
ZStack{
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().stroke(Color.gray.opacity(22/100), lineWidth: 5)
)
Circle()
.fill(Color.clear)
.frame(width: 250, height: 250)
.overlay(
Circle().trim(from:0, to: onTick)
.stroke(
style: StrokeStyle(
lineWidth: 5,
lineCap: .round ,
lineJoin:.round
)
)
.foregroundColor(
( completed() ? Color.orange: Color.orange)
).animation(
.easeInOut(duration: 0.2)
)
)
.rotationEffect(Angle(degrees: 270.0))
Image("indicator_ellipse")
.resizable()
.frame(width: 230, height: 230)
}
}.onReceive(timer) { time in
progress(time: Int(time.timeIntervalSince1970))
}
}
func completed() -> Bool {
return onTick == 1
}
func progress(time: Int) {
let minutesOrderDeliveryDate = Int(orderDeliveryDate.timeIntervalSince1970)
let minutesOrderDate = Int(orderDate.timeIntervalSince1970)
let minutesCurrentDate = time
let totalMinutes = minutesOrderDeliveryDate - minutesOrderDate
let remainingMinutes = minutesOrderDeliveryDate - minutesCurrentDate
onTick = CGFloat(remainingMinutes) / CGFloat(totalMinutes)
print(onTick)
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}
This is OrderConfirmedView :
struct OrderConfirmedVieww: View {
#EnvironmentObject var syncViewModel : SyncViewModel
#State var nowDate: Date = Date()
var body: some View {
VStack {
Spacer()
Text(Texts.orderConfirmedText1)
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.colorGrayDark)
.multilineTextAlignment(.center)
.lineLimit(3)
.padding(40)
CircularTimer(orderDate: dateFormatTime(date: syncViewModel._order.date ?? ""), orderDeliveryDate: dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""))
.padding()
// Spacer()
Text(Texts.orderOraLivrareText)
.font(.headline)
.fontWeight(.thin)
.padding(.bottom)
Text(dateFormatTime(date: syncViewModel._order.deliveryDate ?? ""), style: .time)
.font(.title2)
.fontWeight(.bold)
Spacer()
Spacer()
Button {
} label: {
Text(Texts.orderButtonText)
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.onboardingColor)
.cornerRadius(20)
}
.padding()
}
}
func dateFormatTime(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = .current
return dateFormatter.date(from: date) ?? Date.now
}
}

swiftUI - Date Comparison

I need to compare 2 dates and if it is the same date, do something... (dd/MM to dd/MM only). I generate today's date, then set my own date and when compared, the same dates do not match. (When displaying each date (as Text), the result is the same. E.g. 10-04 and 10-04.)
struct eddEntryView : View { // ContentView
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM"
return formatter
}()
#State private var today = Date()
let specificDate: Date = {
var components = DateComponents()
components.day = 14
components.month = 04
let newDate = Calendar.current.date(from: components) ?? Date()
return newDate
}()
var entry: Provider.Entry
var body: some View {
HStack{
VStack(alignment: .leading){
if Calendar.current.isDateInToday(specificDate) // today is 14-04, specific date is set for 14-04, but it doesn't work
{
Text("THE SAME: \(specificDate, formatter: dateFormatter)")
.font(.title)
.foregroundColor(Color(.label))
.bold()
Spacer()
} else {
Text("Not the same day... \(specificDate, formatter: dateFormatter)")
.font(.title)
.foregroundColor(Color(.label))
.bold()
Spacer()
}
}
}
There is a function isDateInToday(Date) on Calendar. You could use it like this:
if Calendar.current.isDateInToday(dueDate) {
//...
}
There is also a function that compares whether to date fall on the same day:
Calendar.current.isDate(today, inSameDayAs: dueDate)
Solution:
let today = Date() // Actual date
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM"
return formatter.string(from: date)
}
and then:
if ("\(formatDate(date: today))") .elementsEqual("14-04") // specific date {
Text("the same: \(formatDate(date: today))")
// ... do something for the same day
Spacer()
} else {
// ...
}

SwiftUI resize a rectangle to fit the content

using SwiftUI on my project I'm try to display some data in a list.
I would like to insert on the left side of my cell a rectangle(), but I can't find the way to fix the height.
(i don't want to manual input the height otherwise it doesn't look good on different device)
I'm try using geometry render but not working.
I highlight on the attached picture my idea...
here my code:
import SwiftUI
struct MatchViewUser: View {
var match : MatchModel
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}
var timeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "HH:mm a"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
return formatter
}
var body: some View {
GeometryReader { geometry in
HStack {
Rectangle().frame(width: 30,height: geometry.size.height ,alignment: .leading)
VStack(alignment:.leading){
HStack{
Text("\(self.match.dateMatch, formatter: self.dateFormatter)")
Spacer()
Text("Time:\(self.match.dateMatch, formatter: self.timeFormatter)")
}.font(.headline).foregroundColor(.blue)
Divider().padding(.horizontal)
HStack{
VStack(alignment:.leading){
Text("Match Name:").bold()
Text("\(self.match.matchName)").font(.body)
}
Spacer()
VStack(alignment:.leading){
Text("Pitch Name").bold()
Text("\(self.match.pitchName)").font(.body)
}
Spacer()
VStack(alignment:.trailing){
Text("Player").bold()
Text("\(self.match.maxPlayer)").font(.body)
}
}.font(.headline)
}
}
}
}
}
Here is a demo of possible approach (simplified your code w/o dependent model). Prepared & tested with Xcode 11.4 / iOS 13.4
struct DemoView: View {
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}
let dateMatch = Date()
var timeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "HH:mm a"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
return formatter
}
var body: some View {
HStack {
VStack(alignment:.leading){
HStack{
Text("\(self.dateMatch, formatter: self.dateFormatter)")
Spacer()
Text("Time:\(self.dateMatch, formatter: self.timeFormatter)")
}.font(.headline).foregroundColor(.blue)
Divider().padding(.horizontal)
HStack{
VStack(alignment:.leading){
Text("Match Name:").bold()
Text("Demo1").font(.body)
}
Spacer()
VStack(alignment:.leading){
Text("Pitch Name").bold()
Text("Demo2").font(.body)
}
Spacer()
VStack(alignment:.trailing){
Text("Player").bold()
Text("10").font(.body)
}
}.font(.headline)
}.padding(.leading, 38)
}.overlay(
Rectangle().frame(width: 30)
, alignment: .leading)
}
}