SwiftUI: DatePicker is it possible to display date/time in other timezone then current? - datepicker

In UIDatePicker we can do something like this
datePicker.timeZone = TimeZone(secondsFromGMT: 5*60*60)
in order to specify timezone of datepicker.
But how about doing this in DatePicker in SwiftUI, I cannot find any such param or modifier.

It is possible to do with SwiftUI .environment modifier as in below example
DatePicker("Due Date", selection: $selected,
displayedComponents: [.date, .hourAndMinute]
)
.environment(\.timeZone, TimeZone(secondsFromGMT: 5*60*60)!)

Related

How can I display two decimal digits for a Float number in a string?

I am studying basic data types, variables and constants, and made a simulation for an app that displays the price of a coffee beverage, using SwiftUI.
I found a way to arrive at the result I want (to display two decimal houses for the price 16.99), but I don't understand why it works. What is the correct way to display two decimal houses intentionally?
var favoriteCoffeePrice: Float = 16.99
When I use that variable in a string to display text, like this:
Text("R$\(favoriteCoffeePrice)")
the app displays 6 decimal houses (R$16.990000). However, if I use String in the interpolation as shown above, it displays two decimal houses (R$16.99), which is what I want.
Text("R$\(String(favoriteCoffeePrice))")
As a student, I want to know: why does the String definition force the two decimal houses?
And how could I have control over the amount of decimal houses I want to display?
To format a number for display as a currency, use Texts
init(_ input:format:)
initialiser with .currency as the format e.g:
Text(favoriteCoffeePrice, format: .currency(code: "BRL"))
You can add a .presentation modifier to change how it's displayed:
struct ContentView: View {
let favoriteCoffeePrice: Float = 16.99
var body: some View {
VStack {
Text(favoriteCoffeePrice, format: .currency(code: "BRL"))
Text(favoriteCoffeePrice, format: .currency(code: "BRL").presentation(.narrow))
Text(favoriteCoffeePrice, format: .currency(code: "BRL").presentation(.fullName))
Text(favoriteCoffeePrice, format: .currency(code: "BRL").presentation(.isoCode))
Text(favoriteCoffeePrice, format: .currency(code: "BRL").presentation(.standard))
}
}
}
In SwiftUI you can use a format specifier in your Text View to define the number decimal places you want. Your example would look like this;
Text("\(favoriteCoffeePrice, specifier: "%.2f")")
Here is a link to the documentation for appendInterpolation(_:specifier:).

SwiftUI return type Error: Cannot convert return expression of type 'Foundation.Date' to return type 'BarCharts.Date'

I am doing a bar chart in my SwiftUI app, however I encountered a problem...
struct ViewMonth: Identifiable {
let id = UUID()
let date: Date
let viewCount: Int
}
I defined several variable types in the above code.
struct Date {
static func from(year: Int, month: Int, day: Int) -> Date {
let components = DateComponents(year: year, month: month, day: day)
return Calendar.current.date(from: components)!
}
}
It seems like I can't convert the expression type 'Foundation.Date' to 'BarCharts.Date'. I don't understand the error message. Please help!
I expect the code will yield no errors.
The error complains about a terminology conflict of your type Date with existing Date in Foundation.
Basically there are three options:
Replace struct Date with extension Date.
Rename your struct Date as something which does not interfere with any type in the Foundation framework.
Declare all instances of your custom type as BarCharts.Date.
I recommend the first option.
There are two types: Foundation.Date is the date type that everyone uses. And I suppose you defined your struct Date inside a class Barcharts, so it is a Barcharts.Date.
The “from” function is declared to return Date - which is a shortcut for Barcharts.Date. Now the error message should make sense. It’s up to you to decide what you wanted it to return.

SwiftUI - Localization of a dynamic Text

I am struggling with the locilization of some of my TextFields.
Usually a "normal" localization of a Text() or TextField() works without any problem in my App if the text I want to translate is hardcoded like this:
Text("English Text")
I translate it in my Localizable.strings like this:
"English Text" = "German Text";
Now I want to translate TextFields which are more dynamic, but where I know each possible entry:
TextField("New note" + (refresh ? "" : " "),text: $newToDo, onCommit: {
self.addToDo()
self.refresh.toggle()
})
(The refresh is necessary because of a SwiftUI bug sometimes not showing the placeholder-text again.)
Another example would be:
func dayWord() -> String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone.current
dateFormatter.locale = Locale(identifier: "de_DE")
dateFormatter.dateFormat = "EEEE"
return dateFormatter.string(from: self)
}
var day: String {
return data.date.dateFromMilliseconds().dayWord()
}
Text(day.prefix(2))
The Text(day.prefix(2)) has only seven possible states, but I don't know what to write as a key in my Localizable.strings.
Use NSLocalizedString, like
TextField(NSLocalizedString("New note", comment: "") + (refresh ? "" : " "), ...
According to SwiftUI convention, the Text() label is automatically localized only when it has a string "literal". When the text is a variable, you need to use LocalizedStringKey(). Other localizable texts inside Button() or TextField() also require LocalizedStringKey(). So you need to change this to:
Text(LocalizedStringKey(String(day.prefix(2))))
This converts day.prefix(2) into a String, because it is actually a substring, and then calls LocalizedStringKey() to make it localizable. In your Localizable.strings file you could add all 7 possibilities.
"Mo" = "Mo";
"Di" = "Tu";
//etc.
but why would you? Instead, use:
dateFormatter.locale = Locale(identifier: Locale.preferredLanguages.first)
...
Text(day.prefix(2))
to determine the user's current language and display that. Apple returns the text in the proper language, so this text doesn't need to be localized any further.
TextField() does need localization using LocalizedStringKey():
TextField(LocalizedStringKey("New note") + (refresh ? "" : " "),text: $newToDo, onCommit: {
self.addToDo()
self.refresh.toggle()
})
As Asperi points out, for "New note" the same can be accomplished using NSLocalizedString(), which might be better depending on how you like to work. The benefits are: easily adding a comment for the translator, and automatic export into the xliff when you choose Editor -> Export for localization…
By contrast, SwiftUI's LocalizedStringKey() requires you to manually add strings to the Localizable.strings file. For your day.prefix(2) example, I think it would make more sense to get the user's preferred language and display the localized date directly.
SwiftUI Text will only localize literal strings, strings defined in double quotes. You have to either create localized key or retrieve localized string.
Examples
Simple localization with string literal key.
Text("Label")
If you construct the label, you can use LocalizedStringKey function to localize your computed label.
let key = "Label"
let localizedKey = LocalizedStringKey(key)
Text(localizedKey)
You can also get the localized string using NSLocalizedString.
let localizedString = NSLocalizedString("Label", comment: "")
Text(localizedString)
It's also possible to have localized strings accept arguments (#, lld, and lf) through String Interpolation. For example you could have the following localizations in your project Localizable.strings file:
"Name %#" = "Name %#";
"Number %lld" = "%lld is the number";
And you can use it like this:
Text("Label \(object)")
Text("Number \(number)")
Xcode Search
If you want to search for calls of Text that aren't done with literal strings in your projects. You can use Xcode Find Regular Expression feature. View > Navigators > Find or Cmd-4 keyboard shortcut. Change the search type just above the search field to Regular Expression.
Use the following regular expression: Text\([^\"]

Currency input in SwiftUI TextField

I am trying to implement the ability to change a currency value in an edit mode detailed view, but I am struggling to get formatted input for numeric values working.
struct JobDetailView_Edit: View {
#State var jobDetails: JobDetails
var currencyFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .currency
return f
}()
var body: some View {
Form {
Section(header: Text("General")) {
HStack {
Text("Job Name")
Spacer()
TextField($jobDetails.jobName)
.multilineTextAlignment(.trailing)
}
TextField($jobDetails.hourlyRateBasic, formatter: currencyFormatter)
}
... other unimportant code...
The data is passed in correctly, and the textfield displays the formatted value which is stored in $jobDetails.hourlyRateBasic, however, I cannot edit the field as I would be able to if it were a string. If I try to edit the field then press enter in the simulator, I get the following error message:
[SwiftUI] Failure setting binding's value. The supplied formatter does not produce values of type Double. This may be resolved by ensuring the binding and the output of the formatter are of the same type.
FYI $jobDetails.hourlyRateBasic is of type double.
I have created a component that wraps around a UITextfield.
You can check it out here https://github.com/youjinp/SwiftUIKit
Here's the demo
Ben Scheirman created a Tip Calculator that used a TextField with a currency formatter. The difference between his code and yours is that he stored the currency value in a Double?. NumberFormatter returns a NSNumber?.
You can see his code at https://github.com/nsscreencast/397-swiftui-tip-calculator

Using customized setter/getter and initializing the property for a swift date

I have a class called Trip which has an NSDate property called date. My desire is to make sure that the date property always has the time value set to the beginning of the day. I do this by:
Creating an NSCalendar constant called calendar so that I can use the startOfDayForDate method to reset the date value.
Creating a custom setter method for the date property that calls the startOfDayForDate method.
However, I want to initialize the date property to the start of today's date.
The code that I have so far is shown below.
class Trip {
private let calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var date: NSDate {
get {
return self.date
}
set {
self.date = calendar.startOfDayForDate(newValue)
}
}
}
This code a) doesn't initialize the date property and b) (I discovered) results in an infinite loop when the setter is called. Upon further research, I believe that customized getters/setters are strictly computed, right?
How do I accomplish what I mentioned earlier:
a.) making sure that sets to the date property reset to the start of the day and
b.) initializing the date property to the start of today's date?
I think you want to have a date that will always be the start of the date. Replace your current date variable with this:
private var privateDate = NSDate()
var date: NSDate {
get {
return privateDate
}
set {
privateDate = calendar.startOfDayForDate(newValue)
}
}
There may be a slightly better way to do this, but I'm guessing your application won't have tens of thousands of your Trip class, so you should be fine.