Can DateComponentsFormatter format fractional seconds? - macos

I want to print, e.g., the value 1.5 as "1.5 sec", like Safari's timeline does, and I'm trying to use DateComponentsFormatter for it.
Unfortunately, its .allowedUnits only goes down to .second (even though the enum has .nanosecond). I tried setting .allowsFractionalUnits = true, but I'm still getting "0 sec".
Is there any way to get fractional seconds out of DateComponentsFormatter?

For positional units style, DateComponentsFormatter can be used together with NumberFormatter to get locale-aware string.
func format(_ seconds: TimeInterval) -> String {
let components = DateComponentsFormatter()
components.allowedUnits = [.minute, .second]
components.allowsFractionalUnits = true // does not work as expected
components.unitsStyle = .positional
components.zeroFormattingBehavior = .pad
let fraction = NumberFormatter()
fraction.maximumIntegerDigits = 0
fraction.minimumFractionDigits = 0
fraction.maximumFractionDigits = 3
fraction.alwaysShowsDecimalSeparator = false
let fractionalPart = NSNumber(value: seconds.truncatingRemainder(dividingBy: 1))
return components.string(from: seconds)! + fraction.string(from: fractionalPart)!
}
print(Locale.current) // ru_RU (current)
print(format(600.5)) // 10:00,5 <- note locale specific decimal separator
Unfortunately, using this method for other date styles is even more hacky.

Related

Heartrate with no decimal places

Im doing a watch app for the Apple Watch in Xcode and the sample code from the Apple Developer site SpeedySloth: Creating a Workout has the HeartRate rounded to one decimal place, e.g. 61.0
How can I fix this?
case HKQuantityType.quantityType(forIdentifier: .heartRate):
/// - Tag: SetLabel
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
label.setText("\(roundedValue) BPM")
I tried changing both the 1's in there to 0 but that gave me a 6.1 BPM or a 0.0 BPM
Thanks
A simple solution is to round to an integer and show the integer.
let value = // ... some Double ...
let s = String(Int(value.rounded(.toNearestOrAwayFromZero)))
label.setText(s + " BPM")
Properly, however, you should put formatting a string based on a number into the hands of a NumberFormatter. That's its job: to format a number.
let i = value.rounded(.toNearestOrAwayFromZero)
let nf = NumberFormatter()
nf.numberStyle = .none
let s = nf.string(from: i as NSNumber)!
// ... and now show the string

line spacing in dynamically created swift 3/xcode labels

I'm having an issue where I am getting a list of skills back from an api and I want them to stack one on top of the other in two different sections, a left column and a right column. It works well but if the skill is longer than the width of the label it drops to a new line with the same spacing as the rest of the labels. The skill Adobe Creative Suite looks like Adobe Creative as one and Suite as another. I would like Suite to be underneath Adobe Creative but much closer so you can tell it's all one skill.
My code is here:
lblLeft.text = ""
lblRight.text = ""
if let expertiseCount = helper.expertise {
for i in 0..<expertiseCount.count {
if i % 2 == 0 {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
let attrString = NSMutableAttributedString(string: lblLeft.text! + "\(expertiseCount[i].name ?? "")\n")
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range: NSMakeRange(0, attrString.length))
lblLeft.attributedText = attrString
} else {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
let attrString = NSMutableAttributedString(string: lblRight.text! + "\(expertiseCount[i].name ?? "")\n")
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range: NSMakeRange(0, attrString.length))
lblRight.attributedText = attrString
}
}
}
I've already tried line spacing and that just changes the size between all lines so the space between Adobe Creative and Suite takes on that change as well.
Try:
lblLeft.numberOfLines = 0
lblLeft.lineBreakMode = .byWordWrapping
lblLeft.sizeToFit()
By setting number of lines to zero and turning word wrapping on, the label will grow to the required number of lines. sizeToFit() should size it properly.

Swift 3 Local Notifications - Setting Automatic Timezone

So this worked flawlessly in Swift 2 but in Swift 3 it has problems:
func myNotifications () {
let interval = 2.0
var daysOut = 2.0
let myArray =getArray()
for i in 0..<myArray.count {
let message = ("\(myArray[i])\n-\(myArray[i])")
let localNotification = UILocalNotification()
localNotification.fireDate = Date(timeIntervalSinceNow: (60*60*24*daysOut))
localNotification.alertBody = message
localNotification.timeZone = NSTimeZone.autoupdatingCurrent
localNotification.category = "Message"
UIApplication.shared.scheduleLocalNotification(localNotification)
daysOut += interval
}
let arrayCount = Double(myArray.count)
let lastNotif_Date = Date(timeIntervalSinceNow: (60*60*24*interval*(quoteCount+1)))
userPref_NSDefault.set(lastNotif_Date, forKey: "notification_EndDate")
}
Specifically, this line no longer works which is a big deal because I have users in multiple timezones and I want to make sure this works:
localNotification.timeZone = NSTimeZone.autoupdatingCurrent
I get the error message:
Type "NSTimeZone" has no member "autoUpdatingCurrent"
Any ideas? I tried "timeZone.automatic", ".automatic", and some other variations but haven't been able to figure it out.
autoupdatingCurrent != autoupdatingCurrent
NSTimeZone has been renamed to TimeZone

How do you set previewLayer.connection.videoOrientation in Swift 2?

In Objective-C you can do this:
if ( UIDeviceOrientationIsPortrait( deviceOrientation ) || UIDeviceOrientationIsLandscape( deviceOrientation ) ) {
AVCaptureVideoPreviewLayer *previewLayer = (AVCaptureVideoPreviewLayer *)self.previewView.layer;
previewLayer.connection.videoOrientation = (AVCaptureVideoOrientation)deviceOrientation;
}
But in Swift, what's the best way to translate this last line? You can't do "deviceOrientation as AVCaptureVideoOrientation".
Best I could come up with so far is:
if(UIDeviceOrientationIsPortrait(deviceOrientation) || UIDeviceOrientationIsLandscape(deviceOrientation)) {
let myPreviewLayer: AVCaptureVideoPreviewLayer = self.previewLayer
myPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientation.init(rawValue: deviceOrientation.rawValue-1)!
}
But this seems inelegant. The -1 is there because it's just an enum at the end of the day, and the UIDeviceOrientation enum has an "Unknown" one at position 0 that the AVCaptureVideoOrientation does not have...
This does a great job on swift 4:
previewLayer.connection?.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)!
try this
let avLayer = (self.previewView.layer as AVCaptureVideoPreviewLayer)
let connection = avLayer.connection
let orientation = AVCaptureVideoOrientation(rawValue: self.interfaceOrientation.rawValue)!
connection.videoOrientation = orientation
This is the conversion I got:
if UIDeviceOrientationIsPortrait(deviceOrientation) || UIDeviceOrientationIsLandscape(deviceOrientation) {
var previewLayer: AVCaptureVideoPreviewLayer = previewView.layer
previewLayer.connection.videoOrientation = deviceOrientation
}
The converter I used tends to not know what the code is supposed to look like some times (Int vs. CGFloat) so it might not work without de-bugging. let me know if it does what you want, for example, the variable could probably be changed to a constant.
connection.videoOrientation = AVCaptureVideoOrientation.Portrait
You can find the constants AVCaptureVideoOrientation values in swift header files. (Swift 2)

Swift 2 get minutes and seconds from double

I am currently storing time in seconds (for example 70.149 seconds). How would I easily get the minutes and seconds? What I mean is 70.149 seconds is 1min and 10sec. How would I be able to do this easily in swift?
Here is an example of what I want to do.
let time:Double = 70.149 //This can be any value
let mins:Int = time.minutes //In this case it would be 1
let secs:Int = time.seconds //And this would be 10
How would I do this using Swift 2 OS X (not iOS)
Something like this:
let temp:Int = Int(time + 0.5) // Rounding
let mins:Int = temp / 60
let secs:Int = temp % 60
This would be a relatively simple solution. It's worth noting this would truncate partial seconds. You'd have to use floating point math on the third line and call trunc()/ceil()/floor() on the result before conversion to an Int if you wanted control over that.
let time:Double = 70.149 //This can be any value
let mins:Int = Int(time) / 60 //In this case it would be 1
let secs:Int = Int(time - Double(mins * 60)) //And this would be 10
Swift 5
The date components formatter has lots of advantages when displaying human readable text to a user. This function will take a double.
func humanReadable(time:Double) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .full
if let str = formatter.string(from: time) {
return str
}
return "" // empty string if number fails
}
// will output '1 minute, 10 seconds' for 70.149

Resources