Why does SwiftUI format dates as "2020 M10 27" in navigationTitle? - swiftui

I'm using Xcode 12.1 and the iOS 14.1 simulator.
With the simplest possible code using Text.init(_:style:), the date format seems to differ between the nav bar title and the content view:
struct ContentView: View {
var body: some View {
NavigationView {
Text(Date(), style: .date)
.navigationTitle(
Text(Date(), style: .date)
)
}
}
}
I'd expect the title and content to look like "October 27, 2020" but the title looks like "2020 M10 27". I've never seen this format before.
Using a custom Formatter with Text.init(_:formatter:) gives the same result:
struct ContentView: View {
var body: some View {
let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
NavigationView {
Text(Date(), formatter: formatter) // "October 27, 2020"
.navigationTitle(
Text(Date(), formatter: formatter) // "2020 M10 27"
)
}
}
}
Finally, pre-formatting the string with Formatter.string(from:) gives the expected result:
import SwiftUI
struct ContentView: View {
var body: some View {
let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
NavigationView {
Text(formatter.string(from: Date())) // "October 27, 2020"
.navigationTitle(
Text(formatter.string(from: Date())) // "October 27, 2020"
)
}
}
}
What's going on here? Do I really need this workaround in order to display a date in the navigation title?

Yes, unfortunately it looks like a bug.
As a workaround you can create an extension:
extension Text {
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
init(date: Date) {
self.init(Text.dateFormatter.string(from: date))
}
}
so you have the same amount of code in navigationTitle:
.navigationTitle(
Text(date: Date())
)

Related

how to convert muliple int number to string in qr code generator with swiftui

I have made a qr code generator with date and I want to add another picker for hours, and I have tried to conver Int to String, there isn't any problem, when I tried to convert multiple Int to string and it doesn't work and also i want to change the Int format to something like this 12 02:11, first is integer space and time. how could I do that?
My Code:
struct GenerateQRCode: View {
#Binding var time: Date
#Binding var hours: Int
let hour = ["3","6","9","12"]
let filter = CIFilter.qrCodeGenerator()
let cont = CIContext()
var dateFormatter: DateFormatter {
let df = DateFormatter()
df.dateFormat = "HH:mm"
return df
}
var body: some View {
NavigationView{
Image(uiImage: imageGenerate(times:time, hours: hours))
.interpolation(.none)
.resizable()
.frame(width: 150, height: 150, alignment: .center)
}.navigationBarBackButtonHidden(true)
}
func imageGenerate(hours: Int, times: Date)-> UIImage { //<--here how to add integer parameter?
let str = dateFormatter.string(from: start)
let ts = String(hours)
let com = ts + str
let data = com.data(using: .utf8)
filter.setValue(data, forKey: "inputMessage") //
if let qr = filter.outputImage {
if let qrImage = cont.createCGImage(qr, from: qr.extent){
return UIImage(cgImage: qrImage)
}
}
return UIImage(systemName: "xmark") ?? UIImage()
}
}
Preview:
import Foundation
import SwiftUI
import CoreImage.CIFilterBuiltins
struct DatePicker: View {
#State var Time = Date()
#State var sHours = Int()
#State var navigated = false
let hour = ["3", "6", "9", "12"]
var body: some View {
NavigationView{
VStack{
Section{
Text("Please Select Time")
DatePicker("", selection: $startTime, displayedComponents: [.hourAndMinute])
.datePickerStyle(.wheel)
}
Section{
Text("Please Select Minutes")
Picker(selection: $sMinutes, label: Text("Please Select Minutes"))
{
ForEach(0 ..< minutes.count) {
index in Text(self.minutes[index]).tag(index)
}
}
}
Section
{
NavigationLink(destination: GenerateQRCode(start: $Time, minutes: $sHours), isActive: self.$navigated)
{
Text("Complete")
}
}.padding(100)
}.navigationBarTitle("Visitor")
}
}
}
struct DatePicker_Previews: PreviewProvider {
static var previews: some View {
DatePicker()
}
}
Output: "012:30"
what i expected output, should be like this: "3 12:30" first should be hours and then time. i don't know why it only shows 0 if my picker turns to 3. How can i solve it out?

Value of type 'DateComponents' has no member 'DateComponentsFormatter'

I am trying to use the DateComponentFormatter to remove the details in the NSDateComponent (see screenshot) but it throws me 3 errors:
Cannot convert return expression of type '()' to return type 'DateComponents'
Reference to member 'year' cannot be resolved without a contextual type
Value of type 'DateComponents' has no member 'DateComponentsFormatter'
I tried setting a lazy var formatter = DateComponentsFormatter like in the tutorials, but it more or less throw the same error. It also doesn't work attaching the formatter to the remainder but it throws the mutatable error.
Big thanks in advance!
import SwiftUI
import Foundation
struct ContentView: View {
#State private var birthDay: Date = Calendar.current.date(byAdding: DateComponents(year: -30), to: Date()) ?? Date()
lazy var remainder = Calendar.current.date(byAdding: DateComponents(year: 73), to: birthDay)
lazy var remainderComp = Calendar.current.dateComponents([.year], from: now, to: remainder!)
let now = Date()
var body: some View {
VStack {
NavigationView {
VStack {
Text("Your birthday...?")
.font(.title)
DatePicker("", selection: $birthDay, in:...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
Text("""
The world's average life expectancy in 2022 is
73 years
""")
.multilineTextAlignment(.center)
Text("You still have: ")
Text("\(remainderCompValue()) years on earth")
Spacer()
}
}
}
}
func remainderCompValue() -> DateComponents {
var mutatableSelf = self
return mutatableSelf.remainderComp.DateComponentsFormatter().allowedUnits = [.year]
If you want to use a DateComponentsFormatter, there are a few issues:
The following line does not makes sense:
mutatableSelf.remainderComp.DateComponentsFormatter().allowedUnits = [.year]
You should create a DateComponentsFormatter property and set its allowedUnits (and presumably its unitStyle). E.g.:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.year]
formatter.unitsStyle = .full
return formatter
}()
The issue in the screen snapshot is that you are not using the DateComponentsFormatter that you should have created in the prior step. So, first, I would retire remainder, remainderComp, and now and just have a function to prepare the “number of years left” string:
func remainingYearsString(for date: Date) -> String? {
guard let expectancyEndOfLife = Calendar.current.date(byAdding: .year, value: 73, to: date) else {
return nil
}
return formatter.string(from: .now, to: expectancyEndOfLife)
}
Note that that the DateComponents object is now redundant and no longer needed.
Anyway, I would then have the View use that function:
var body: some View {
VStack {
NavigationView {
VStack {
Text("Your birthday...?")
.font(.title)
DatePicker("", selection: $birthDay, in:...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
.labelsHidden()
Text("""
The world's average life expectancy in 2022 is
73 years
"""
).multilineTextAlignment(.center)
Text("You still have: ")
Text("\(remainingYearsString(for: birthDay) ?? "unknown time") on earth")
Spacer()
}
}
}
}
Note that the date components formatter will include the “years” string, so you will notice that I have removed the redundant “years” in the Text(...) string.

Is it possible to dismiss the DatePicker's calendar when you tap a date?

I have a basic SwiftUI date picker that shows a calendar widget when tapped:
DatePicker(
"Date",
selection: $date,
in: ...Date(),
displayedComponents: [.date]
)
When you select a date (8th October in the example above), the calendar remains on screen and in order to collapse it, you need to tap outside of it.
Is it possible to automatically collapse it when a date is selected?
I ended up with a rather hacky solution that seems to do the job:
Add a #State variable that holds the calendar ID:
#State private var calendarId: Int = 0
Chain the DatePicker call with .id, .onChange and .onTapGesture actions:
DatePicker(
"Date", selection: $date, in: ...Date(), displayedComponents: [.date]
)
.id(calendarId)
.onChange(of: date, perform: { _ in
calendarId += 1
})
.onTapGesture {
calendarId += 1
}
#chris.kobrzak provided a good direction, and I ended up solving this with:
struct ContentView: View {
#State var calendarId: UUID = UUID()
#State var someday: Date = Date()
var body: some View {
VStack {
DatePicker("Day", selection: $someday, displayedComponents: [.date])
.labelsHidden()
.id(calendarId)
.onChange(of: whatday) { _ in
calendarId = UUID()
}
AnotherView(someday)
}
}
}
This is just an updated answer following #Chris Kobrzak as above.
I am using XCode 14.1 and iOS 15+ and 16+ (iPad and iPhone) and it seems to work without error today in Nov 2022.
I have seen some folk using the same .id() method complain that it doesn’t work.
I haven’t tested this but note that I am using the CompactDatePickerStyle(), maybe it doesn’t work the same on other styles.
The reason this hack works is the .id() is for the ‘view’ (DatePicker being a view). When you change the id of a view you basically reset it (in this case closing the DatePicker).
There is a good explanation about .id() here: https://swiftui-lab.com/swiftui-id/
Why this isn’t built into the control seems rather a joke but hey…
Note I have ripped the following out of a real App. I've edited it in a dumb text editor to post on here so there may be some silly syntax errors and odd remnants of the original code.
import SwiftUI
struct FooView: View {
#Published var dateOfBirth: Date = Date()
#State private var datePickerId: Int = 0
private var dateOfBirthRange: ClosedRange<Date> {
let dateFrom = Calendar.current.date(byAdding: .year, value: -160, to: Date())!
let dateTo: Date = Date()
return dateFrom...dateTo
}
var body: some View {
Form {
ZStack(alignment: .leading) {
Text("Date of Birth")
.offset(y: -36)
.foregroundColor(Color.accentColor)
.scaleEffect(0.9, anchor: .leading)
DatePicker(
"",
selection: $dateOfBirth,
in: dateOfBirthRange,
displayedComponents: .date
)
.datePickerStyle(CompactDatePickerStyle())
.labelsHidden()
.id(datePickerId)
.onChange(of: dateOfBirth) { _ in
datePickerId += 1
}
}
.padding(.top, 24)
.animation(.default, value: "")
}
}
}
I had a similar problem and put a .graphical DatePicker in my own popover. The only downside is on iPhone popovers currently show as sheets but that's ok.
struct DatePickerPopover: View {
#State var showingPicker = false
#State var oldDate = Date()
#Binding var date: Date
let doneAction: () -> ()
var body: some View {
Text(date, format:.dateTime.year())
.foregroundColor(.accentColor)
.onTapGesture {
showingPicker.toggle()
}
.popover(isPresented: $showingPicker, attachmentAnchor: .point(.center)) {
NavigationStack {
DatePicker(selection: $date
, displayedComponents: [.date]){
}
.datePickerStyle(.graphical)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
date = oldDate
showingPicker = false
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
doneAction()
showingPicker = false
}
}
}
}
}
.onAppear {
oldDate = date
}
}
}

Fetching and setting date from datepicker, but still getting old default value

I want to fetch and set date from DatePicker, but my date is not updating. SwiftUI is new to me and I am confused with what type of property wrapper to use. Please help in this and advice when and where to use #State, #Binding, #Published I read some articles but still concept is not clear to me.
Here I used MVVM and SwiftUI and my code as follows.
class MyViewModel:ObservableObject {
#Published var selectedDate : Date = Date()
#Published var selectedDateStr : String = Date().convertDateToString(date: Date())
}
struct DatePickerView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#ObservedObject var viewModel : MyViewModel
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
#State private var selectedDate = Date()
var body: some View {
VStack {
//Title
HStack{
Text("SELECT A DATE")
.foregroundColor(.white)
.font(.system(size: 20))
}
.frame(width:UIScreen.main.bounds.width,height: 60)
.background(Color.red)
//Date Picker
DatePicker(selection: $selectedDate, in: Date()-15...Date(), displayedComponents: .date) {
Text("")
}.padding(30)
Text("Date is \(selectedDate, formatter: dateFormatter)")
Spacer()
//Bottom buttons
Text("DONE")
.fontWeight(.semibold)
.frame(width:UIScreen.main.bounds.width/2,height: 60)
.onTapGesture {
self.viewModel.selectedDate = self.selectedDate
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
//calling:
DatePickerView(viewModel: self.viewModel)
Reply against your second question about wrapper properties used in SwiftUI i.e #State, #Binding, #Published.
The most common #Things used in SwiftUI are:
• #State - Binding<Value>
• #Binding - Binding<Value>
• #ObservedObject - Binding<Value> (*)
• #EnvironmentObject - Binding<Value> (*)
• #Published - Publisher<Value, Never>
(*) technically, we get an intermediary value of type Wrapper, which turns a Binding once we specify the keyPath to the actual value inside the object.
So, as you can see, the majority of the property wrappers in SwiftUI, namely responsible for the view’s state, are being “projected” as Binding, which is used for passing the state between the views.
The only wrapper that diverges from the common course is #Published, but:
1. It’s declared in Combine framework, not in SwiftUI
2. It serves a different purpose: making the value observable
3. It is never used for a view’s variable declaration, only inside ObservableObject
Consider this pretty common scenario in SwiftUI, where we declare an ObservableObject and use it with #ObservedObject attribute in a view:
class ViewModel: ObservableObject {
#Published var value: Int = 0
}
struct MyView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View { ... }
}
MyView can refer to $viewModel.value and viewModel.$value - both expressions are correct. Quite confusing, isn’t it?
These two expressions ultimately represent values of different types: Binding and Publisher, respectively.
Both have a practical use:
var body: some View {
OtherView(binding: $viewModel.value) // Binding
.onReceive(viewModel.$value) { value // Publisher
// do something that does not
// require the view update
}
}
Hope it may help you.
You can calculate the current date - 15 days using this:
let previousDate = Calendar.current.date(byAdding: .day, value: -15, to: Date())!
Then use the previousDate in DatePicker`s range:
DatePicker(selection: $selectedDate, in: previousDate...Date(), displayedComponents: .date) { ...
Summing up, your code can look like this:
struct DatePickerView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var viewModel: MyViewModel
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}
#State private var selectedDate = Date()
let previousDate = Calendar.current.date(byAdding: .day, value: -15, to: Date())!
var body: some View {
VStack {
//Title
HStack{
Text("SELECT A DATE")
.foregroundColor(.white)
.font(.system(size: 20))
}
.frame(width:UIScreen.main.bounds.width,height: 60)
.background(Color.red)
//Date Picker
DatePicker(selection: $selectedDate, in: previousDate...Date(), displayedComponents: .date) {
Text("")
}.padding(30)
Text("Date is \(selectedDate, formatter: dateFormatter)")
Spacer()
//Bottom buttons
Button(action: {
self.viewModel.selectedDate = self.selectedDate
self.presentationMode.wrappedValue.dismiss()
}) {
Text("DONE")
.fontWeight(.semibold)
}
}
}
}
Tested in Xcode 11.5, Swift 5.2.4.

SWIFTUI : get date only

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