How to localize relative Text style in SwiftUI? - ios

I am using relative Text format for displaying remaining time for the widget. However I am not able to localize the result. It always shows in English. I changed the language and regions settings in the device but I can not see any changes. Do you have any suggestions in order to fix this issue?
Text(date, style: .relative)
Result: 1hr 1min

You should localize the date itself using the calendar:
struct ContentView: View {
let calendar: Calendar = {
var calendar = Calendar.current
calendar.locale = .init(identifier: "fa")
return calendar
}()
var body: some View {
Text(Date.now, style: .relative)
.environment(\.calendar, calendar)
}
}

Related

How to change the backgroungColor cell in tableView with timeInterval using Swift

I'm trying to change the cell backgroungColor after 3.5 month. I have a textField where i put the date and after 3.5 month of that date I want to change the color of the cell in red.
I tried this where date1 is the date from textField and date2 is this from (isToday) where i have put 106 day = 3.5 month
let isToday= Date.now.addingTimeInterval(106)
func isSameDay(date1: Date, date2: Date) -> Bool {
let diff = Calendar.current.dateComponents([.day], from: date1, to: date2)
if diff.day == 0 {
return true
}
else {
return false
}
}
Inside cellForRowAt i have used like this
var documentSendDate = "05.08.2022"// this is example to be more understandable
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM/dd/yyyy"
let date = dateFormatter.date(from: documentSendDate)
if date != nil {
let isDayToday = isSameDay(date1: date!, date2: isToday) // here I call the function above
if isDayToday == true {
if customer.isToday == true {
cell.backgroundColor = .red
}
}
}
But I have this checkBox and when I check it or uncheck it change the color of the random cells. Can someone help me with this please?
Here is how i wanted to look.
UITableView is a recycle-list view, so it will reuse the cell UI instance to display data for the corresponding indexPath.
First, modify your code to add a new way of dateFormatter declaration.
// Use lazy var to reduce initialization cost
// Because initializing a new DateFormatter is not cheap, it can consume CPU time like initializing a new NumberFormatter
// lazy var will be only initialized once on the first call/use
lazy var dateFormatter = DateFormatter()
func viewDidLoad() {
super.viewDidLoad()
dateFormatter.dateFormat = "MMM/dd/yyyy"
}
It is a reusable UI, so UI won't hold the data or state. The cellForRowAt will be called multiple times when you scroll tableView or when tableView needs to re-layout,... to display the corresponding data-state for each indexPath.
That is why you must not initialize or do some big calculations/long waiting here. It will freeze/delay your UI (ref: DispatchQueue.main or MainQueue).
So inside your cellForRowAt function, you need to add logic for all cases if you use switch/if-else.
var documentSendDate = "05.08.2022"// this is example to be more understandable
// Here I combine all checks into one if-else
// Order of check is left-to-right.
// It is condition1 AND condition2 AND condition3 (swift syntax)
if let date = dateFormatter.date(from: documentSendDate),
let isDayToday = isSameDay(date1: date!, date2: isToday),
customer.isToday == true {
cell.backgroundColor = .red
} else {
cell.backgroundColor = .clear // or your desired color
}

SwiftUI - how to shuffle an array one time a day?

What is the approach to shuffle an array of strings one time a day?
And not every time the app is relaunches.
struct View: View {
#ObservedObject var quotes = Quotes()
var body: some View {
List {
ForEach(quotes.shuffled()) { quote in
Text(quote.quotes)
}
}
}
}
When I try the shuffled() method every time the view is updated the quotes are shuffled again, also when relaunching the app, I want to shuffle the array only one time a day.
You need to store current date in memory like user defaults and check every time for new date like I have in code below. isNewDay() function checks if date is new and saves current date in user defaults. Condition isNewDay() ? quotes.shuffled() : quotes shuffles quotes only if date is new
struct View :View{
#ObservedObject var quotes = Quotes()
var body :some View{
List{
ForEach(isNewDay() ? quotes.shuffled() : quotes){ quote in
Text(quote.quotes)
}
}
}
func isNewDay()-> Bool{
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
let currentDateString = dateFormatter.string(from: currentDate)
if let lastSaved = UserDefaults.standard.string(forKey: "lastDate"){// last saved date
if lastSaved == currentDateString{
return true
}else{
UserDefaults.standard.setValue(currentDateString, forKey: "lastDate")
return false
}
}else{
UserDefaults.standard.setValue(currentDateString, forKey: "lastDate")
return false
}
}
}

How to change view on midnight in WidgetKit, SwiftUI?

I have a code:
struct ContentView: View {
let entry: LessonWidgetEntry
private static let url: URL = URL(string: "widgetUrl")!
var body: some View {
VStack {
switch entry.state {
case .none:
ProgramNotStartedView()
case .currentLesson(let lesson):
LessonView(lesson: lesson, imageName: entry.program?.imageName)
case .lessonCompleted(let lesson):
LessonCompletedView(lesson: lesson)
case .programCompleted:
ProgramCompletedView()
}
}.widgetURL(ContentView.url)
}
}
At midnight LessonCompletedView should change to LessonView, but I am not sure how to do that.
Any ideas on how to change views on midnight from the widget?
Assuming you have an Entry (in your app you have entry.state... but for this example I used a simplified version):
struct SimpleEntry: TimelineEntry {
let date: Date
let lesson: Lesson
}
Setup your TimelineProvider to refresh timeline after the next midnight:
struct SimpleProvider: TimelineProvider {
...
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> Void) {
let currentDate = Date()
let midnight = Calendar.current.startOfDay(for: currentDate)
let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!
let entries = [
SimpleEntry(date: currentDate, lesson: Lesson()) // pass the lesson here
]
let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
completion(timeline)
}
}
In the TimelineProvider you may pass any lesson you want (depending on the day or the previous lesson - it's up to you). You may also pass a variable to an Entry indicating whether the lesson is completed.
By setting the .after(nextMidnight) policy you indicate when do you want your Timeline (and therefore you Widget View) to be reloaded.

Group separators in TextField

I pass Int in to TextField together with NumberFormatter
private var numberFormatter: NumberFormatter {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale(identifier: "pl")
return numberFormatter
}
TextField(placeholder, value: self.$amount, formatter: self.numberFormatter)
And I expect, that TextField will include group separators, and it is, but only in initial state, later, when I change value: add and remove new digits, it works as with simple string just ignores places where separators should be.
If I use proxy
var amountProxy: Binding<String> {
Binding<String>(
get: { self.numberFormatter.string(from: NSNumber(value: self.amount)) ?? "" },
set: {
let cleanValue = $0.replacingOccurrences(of: self.numberFormatter.groupingSeparator, with: "")
if let value = self.numberFormatter.number(from: cleanValue) {
self.amount = value.intValue
}
}
)
}
TextField(placeholder, text: amountProxy)
It formats it right, but when adds a new separator, displays cursor from last position to lastPosition + numberOfSeparators
Did you try using onEditingChanged completion for TextField? I would suggest that when User starts editing, you can remove the formatting and let user edit the phone number. When user finishes the editing, you can show the formatted number again.
Something like this:
TextField(placeholder, text: $amount, onEditingChanged: { (editing) in
if editing {
amountBinding = amount
} else {
amountBinding = amount.formatted()
}
})
I haven't tested the code but it is something you can modify to fit your needs. Also, you can probably have a separate binding and model variable

WatchOS2 Event Complication, how to step to next event at endTime of prior event? (getTimelineEntriesForComplication)

As title says, at end of a watch Event complication, ModularLarge, as Apple's Calendar complication does, say Event is 7-7:10pm, then next event is 8pm. So at Time Travel, or even on watch face itself live, when Event end Time occurs, I do not step/display the next event(I have event in the array note). Think I am just missing something. Note: I am a bit new at this. Here is the code I have.
func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
// Call the handler with the timeline entries after to the given date
var timeLineEntryArray = [CLKComplicationTimelineEntry]()
if allEvents.count > 0 {
for (index, title) in allEvents.enumerate() {
let item = allEvents[index]
let startDate = item.startDate
let endDate = item.endDate
let title = item.title
//let entry = createTimeLineEntry2(timeString, body1Text: timeLineText[index], body2Text: timeUntilArray[index], date: nextDate)
let entry = createTimeLineEntry2(timeString, body1Text: title, body2Text: timeUntil, startDate: startDate)
timeLineEntryArray.append(entry)
} //end for loop...
} //end If allEvents.count > 0 we have events
handler(timeLineEntryArray)
}
so at watch time, and TimeTravel of 7:11 should display the 8pm Event, and then I desire to delta time to shot time until that event. so "-49 minutes" with this code that works:
func createTimeLineEntry2(headerText: String, body1Text: String, body2Text: String, startDate: NSDate) -> CLKComplicationTimelineEntry {
template.body2TextProvider = CLKRelativeDateTextProvider(date: startDate,
style: .Offset,
units: NSCalendarUnit.Hour.union(.Minute))
template.body2TextProvider!.tintColor = UIColor.yellowColor()
template.tintColor = UIColor.yellowColor()
let entry = CLKComplicationTimelineEntry(date: startDate, complicationTemplate: template)
return(entry)
}
Lastly, can we set the tint(color) of the body2TextProvider?
thank you so much. Mike
here is a link to picture, this event ended at 9:10 am, so watch shodld show the 12 pm event now! http://pics.derr.ws/watch.png

Resources