Formatting a date to be fixed-width without zero-padding - ios

I have a UITableView, in which each row displays a date and time plus a message (screenshot below). I'm using a single attributed string to display the text in the row (which is necessary for the color difference).
Because I don't want to zero-pad the month, day, or hour, the date on the left-hand side of the colon is variable length, which causes the list of messages to look disorganized.
The result I want is for all the colons to line up vertically, so that the messages line up, despite the number of characters in the date being variable. What's the best way to accomplish this?
I've tried essentially space-padding (by detecting the length of the month, day, and hour), but the result still doesn't line up perfectly and can result in long (ugly looking) blocks of whitespace when all three (month, date, and hour) need to be padded. Perhaps there is a way to distribute this extra space amongst all the characters evenly?
Date formatting:
//choose format for the date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "M/d h:mma: "
dateFormatter.amSymbol = "am"
dateFormatter.pmSymbol = "pm"
Entering into the view:
//first put in the date
let classHistoryCellText = NSMutableAttributedString.init(string: formattedDate)
classHistoryCellText.addAttribute(NSForegroundColorAttributeName, value: UIColor.init(hex: "#00000", alpha: 0.3), range: NSMakeRange(0, lengthOfDate))
//append what the message is
classHistoryCellText.append(NSMutableAttributedString.init(string: classBeingViewed.classHistory[indexPath.row]["event"] as! String))
classHistoryCellText.addAttribute(NSForegroundColorAttributeName, value: UIColor.init(hex: "#00000"), range: NSMakeRange(lengthOfDate, classHistoryCellText.length - lengthOfDate))
//bold the entire thing and make it size fontSize
classHistoryCellText.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: fontSize), range: NSMakeRange(0, classHistoryCellText.length))
classHistoryCellToDisplay.textLabel?.attributedText = classHistoryCellText
Result:

Try changing your date formatter to:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd hh:mma: " // this line is changed
dateFormatter.amSymbol = "am"
dateFormatter.pmSymbol = "pm"
It will make months, days and hours to always have two digits which will make all your dates the same width.
This will work unless you have a specific requirement to avoid displaying leading zeros. If this is the case, the easiest solution is to use two labels - one for date and one for status. Make date label a fixed width.

Related

Controlling accessibility voice over readout of numbers in iOS

I'm trying to get an accessibilityValue with a decimal number on a custom UIView to readout as "twenty point one", for example, similar to how voice over reads out the duration and keyframe values on the video trimmer when editing a video in the Photos app.
The default setup reads out the value as "twenty dot one". If I set the accessibilityAttributedLabel instead using the accessibilitySpeechPunctuation key, it reads as "twenty period one".
view.accessibilityAttributedLabel = NSAttributedString(string: "20.1", attributes: [.accessibilitySpeechPunctuation: true])
Without resorting to manually building a numeric string to read out, anyone know how to get the number to read saying "point" instead of "dot" or "period"?
Got it! Formatting a number using a NumberFormatter with a style of .spellOut will generate a string with the fully spelled out value. Not what we want for a label's text, but exactly what we want for an accessibility label.
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
let label = UILabel()
label.text = formatter.string(from: 20.1)
label.accessibilityLabel = formatter.string(from: 20.1)
// prints out "twenty point one"
print(label.accessibilityLabel)

Time display as in system clock

I want to make the display of time as in the iOS system clock (time in capital letters, and part of the day small but capital).
I know that I need the help of an attributed string, but I don’t know how to make it compatible with different time display formats (12 and 24 hours), different languages and a local place.
To get time in string representation according user locale, you can use:
let formatter = DateFormatter()
formatter.timeStyle = DateFormatter.Style.short //Or in short formatter.timeStyle = .short
let time = formatter.string(from: Date())
For example it returns you 23:47 for Russia, and 11:47 PM for US.
Then we can check if time format returned with day part for user locale. If so we set another font for last two characters in string, which is our day part.
if let range = time.range(of: formatter.amSymbol) ?? time.range(of: formatter.pmSymbol) {
let fontSize: CGFloat = 8.0
let nsRange = NSRange(range, in: time)
let attrString = NSMutableAttributedString(string: time)
attrString.addAttributes([.font: UIFont.systemFont(ofSize: fontSize)], range: nsRange)
label.attributedText = attrString
} else {
label.text = time
}

how to set date and time limitation in Date Time Picker from Now to 1 Month from Now?

I now that there is a thread that discussing about minimum and maximum date time in here Minimum and maximum date in UIDatePicker
but I am a beginner now in programming, and I have little bit confused with date and time object. I have tried but I can't set up my date time picker properly. So I want to set my date time picker to have minimum and maximum limited time.
the minimum is Now, and the maximum is 1 month from now. what code do I have to use? Thanks in advance
https://i.stack.imgur.com/RxXUX.png
You can try
self.datePicker.minimumDate = Date()
self.datePicker.maximumDate = Calendar.current.date(byAdding: .day, value: 30 , to:Date())!
or
self.datePicker.maximumDate = Calendar.current.date(byAdding: .month, value: 1 , to:Date())!
I recommend to use the date math skills of Calendar. It calculates the same day in the next month. If the day does not exist (for example Feb 30) it returns the next closest available date.
let now = Date()
let calendar = Calendar.current
let dayComponents = calendar.dateComponents([.day], from: now)
let maximumDate = calendar.nextDate(after: now, matching: dayComponents, matchingPolicy: .nextTime)
To get a Date exactly one month in the future from now (taking into account different month lengths), you can use Date.date(byAdding:value:to:), e.g. as in this answer. You'd take the result and set it as maximumDate of your picker.

Swift NumberFormatter formatting all values to 3 decimal places?

I have a large array of doubles, which have a varying number of decimal places, such as:
[11307.3, 1025.64, 1.27826, 1676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604]
I am attempting to format the number so there are commas every thousand and the decimal places are not formatted to a specific number such as 3dp. The array should look like
[11,307.3, 1,025.64, 1.27826, 1,676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604]
I have tried doing this by defining NumberFormatter as such:
let numberFormatter = NumberFormatter()
and then choosing decimal for style:
numberFormatter.numberStyle = NumberFormatter.Style.decimal
The values in the array are display in a table view, and when a user taps on for example the 2nd cell, in a new screen the value 1,025.64 would be displayed.
I used this code to do that:
var formattedPrice = numberFormatter.string(from: NSNumber(value:coinPriceDouble!))
self.coinPriceLbl.text = "\(coinTitleText!): \(Cryptocoin.instance.fiatSymbol)\(formattedPrice!)"
This works perfect for any value that does not have more than 3 decimal places.
If the user chose the 3rd value in the array, it would display 1.278 not 1.27826.
Is there any way to format these values with commas but not force them to a specific number of decimal places?
As vadian said, NumberFormatter is highly customisable.
Just play around its properties, like (you need to customise based on your needs):
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 3
Here the explanation for NumberFormatter's maximumFractionDigits property and related.
Here instead a blog that explains all the related aspects of NumberFormatter A Guide to NSNumberFormatter.
EDIT
Put the following code in a Playground and observe the result:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 3
let formattedNumbers = [11307.3, 1025.64, 1.27826, 1676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604].flatMap { number in
return numberFormatter.string(from: number)
}
print(formattedNumbers)
Link: https://stackoverflow.com/a/27571946/6655075 .
This solved my problem. As I had 3 values displaying, each from a different array, I would end up formatting all 3 whereas I only wanted to format 1 array.
extension Double {
static let twoFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
var formatted: String {
return Double.twoFractionDigits.string(for: self) ?? ""
}
}
I removed
var formattedPrice = numberFormatter.string(from: NSNumber(value:coinPriceDouble!))
And simply used
self.coinPriceLbl.text = "\(coinTitleText!): \(Cryptocoin.instance.fiatSymbol)\(coinPriceDouble!.formatted)"
Edit: As Dávid Pásztor mentioned, I only want to add the comma separator to the values which need it while still maintaining the precision of each value down to the last decimal value.
You could try setting the maximum fraction digits to a largish number.
numberFormatter.maximumFractionDigits = 15

Setting multiple allowedUnits for NSDateComponenstFormatter in Swift

Background:
When formatting an NSTimeInterval using an NSDateComponentsFormatter, it's often handy to use the allowedUnits property to round the resulting string to a relevant time unit.
For example, I may not be interested in getting time to the second, so setting the allowedUnits to minutes gets an appropriately rounded output string:
let duration: NSTimeInterval = 3665.0 // 1 hour, 1 minute and 5 seconds
let durationFormatter = NSDateComponentsFormatter()
durationFormatter.unitsStyle = .Full
durationFormatter.zeroFormattingBehavior = .DropAll
durationFormatter.allowedUnits = .Minute
let formattedDuration = durationFormatter.stringFromTimeInterval(duration)
formattedDuration will be "61 Minutes".
The problem:
How would you go about setting allowedUnits to .Hour and .Minute so the formatted duration would be "1 hour, 1 minute" instead?
The NSDateComponentFormatter Class Reference doesn't really cover how to do this.
As suggested by Leo, you can simply provide the allowedUnits in an array like so:
durationFormatter.allowedUnits = [.Hour, .Minute]
The solution I've come up with is to create a NSCalendarUnit by adding the raw values of the desired allowed units together:
durationFormatter.allowedUnits = NSCalendarUnit(rawValue: NSCalendarUnit.Hour.rawValue + NSCalendarUnit.Minute.rawValue)
I't be interested to know if there's a better syntax or alternative way to do this.

Resources