Running Pace Calculator in Swift - swift

I'm a complete newbie to Swift. I have created a running pace calculator as experiment in Playground and it works perfectly, but I struggle to figure out how to connect it to a UI.
Things in particular I struggle with currently:
Casting a text string to an Int label
Does the function that does the calculation go in the IBAction?
Playground Code
import UIKit
func PaceCalculator (minutes:Double, seconds:Double, distance:Double) -> Double{
return ((minutes*60) + seconds) / distance
}
var paceInSeconds = PaceCalculator(28, 26, 10.1)
var paceInMinutes = paceInSeconds / 60
var roundedMinutes = Double(floor(paceInMinutes))
var decimalSeconds = paceInMinutes - roundedMinutes
var intPace = Int(floor(roundedMinutes))
var seconds = Int(floor(decimalSeconds * 60))
println("Your average pace is \(intPace):\(seconds)/km")
Incomplete Swift Code
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func calculatePaceButton(sender: UIButton) {
}
#IBOutlet weak var minutesTextField: UITextField!
#IBOutlet weak var distanceTextField: UITextField!
#IBOutlet weak var paceLabel: UILabel!
}
Storyboard
Disclaimer: I have no programming experience either. So be gentle on the jargon and explanation.)

Hope this will work for you :
You didn't added secondsTextField as IBOutlet please check that too.
class ViewController: UIViewController {
#IBOutlet weak var minutesTextField: UITextField!
#IBOutlet weak var distanceTextField: UITextField!
#IBOutlet weak var paceLabel: UILabel!
#IBOutlet weak var secondsTextField: UITextField!
#IBAction func calculatePaceButton(sender: UIButton) {
var paceInSeconds = PaceCalculator((minutesTextField.text as NSString).doubleValue, seconds: (secondsTextField.text as NSString).doubleValue, distance: (distanceTextField.text as NSString).doubleValue)
var paceInMinutes = paceInSeconds / 60.0
var roundedMinutes = Double(floor(paceInMinutes))
var decimalSeconds = paceInMinutes - roundedMinutes
var intPace = Int(floor(roundedMinutes))
var seconds = Int(floor(decimalSeconds * 60))
paceLabel.text = "\(intPace)"
}
func PaceCalculator (minutes:Double, seconds:Double, distance:Double) -> Double{
return ((minutes*60) + seconds) / distance
}
}

Here I would like to share my solution to calculate pace. The calculation is based on inputs of times and locations, which are more generic and useful.
typealias ShortFullTupleStrings = (short: String, full: String)
class PaceCalculator {
private static func relatedTimeString(
for value: TimeInterval)
-> ShortFullTupleStrings?
{
let fm = DateComponentsFormatter()
switch abs(value) {
case 0 ..< 24*3600: // within one day
fm.allowedUnits = [.year, .day, .hour, .minute, .second]
case 24*3600 ..< 24*3600*10: // within 1-10 days
fm.allowedUnits = [.year, .day, .hour, .minute]
case 24*3600*10 ..< 24*3600*365: // within 10-365 days
fm.allowedUnits = [.year, .day, .hour]
default: // within 365-1000 days
fm.allowedUnits = [.year, .day]
}
fm.unitsStyle = .short
let short = fm.string(from: value)
fm.unitsStyle = .full
let full = fm.string(from: value)
if let short = short, let full = full {
return (short, full)
} else {
return nil
}
}
static var isMetric: Bool {
let locale = NSLocale.current
let metricSystem = locale.usesMetricSystem
return metricSystem
}
static func paceFrom( _
dt1: Date, to dt2: Date,
distanceFrom loc1: CLLocation, to loc2: CLLocation) ->
ShortFullTupleStrings?
{
let timeInterval = dt2.timeIntervalSince(dt1)
let dist = loc2.distance(from: loc1)
let pace: ShortFullTupleStrings?
if !dist.isZero {
let paceV: TimeInterval
if isMetric {
paceV = timeInterval / (dist / 1000.0)
} else {
paceV = timeInterval / (dist / 1609.344)
}
pace = relatedTimeString(for: paceV)
} else {
pace = nil
}
return pace
}
}
relatedTimeString is a helper func to get a time string in the format of short form of # yrs, # days, # hrs, # min, # sec, and full form of # years, ..., depending on none zero values. For example, 6 min, 5 sec in short form, or 6 minutes, 5 seconds in full form.
In this way, the func is more generic and supports localization and accessibilities.

Related

using String with UIStepper - Swift

i have a label and a UIStepper, i need to increase the number of that label without lossing the letters ( Kd ) . my label will be like this "5.000 Kd" and when i increase the number i don't want to loss the ( Kd ) label.
this is my code
import UIKit
import GMStepper
class ViewController: UIViewController {
#IBOutlet var stepper: GMStepper!
#IBOutlet var price: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func stepper(_ sender: Any) {
let label = "5.000 Kd" as NSString
price.text! = String(label.doubleValue * stepper.value)
}
}
If you are hard-coding the string contents of the label, just maintain a numeric value and rebuild the label contents each time that value changes:
class ViewController: UIViewController {
#IBOutlet var stepper: GMStepper!
#IBOutlet var priceLabel: UILabel!
var price: Double = 5.0
#IBAction func stepper(_ sender: Any) {
let newValue = price * stepper.value
//Format the price with 3 decimal places
let priceString = String(format: "%.3f", newValue)
Construct a string with the formatted price and " Kd" and put it in the label
priceLabel.text = "\(priceString) Kd")
}
}
Consider using NumberFormatter to format currency:
let cf = NumberFormatter()
cf.currencyCode = "KWD"
cf.numberStyle = .currency
cf.string(from: 5000)
That respects the user's Locale.

Mortgage Calculator Swift

I am a beginner with Xcode and Swift. Below is my formula for the mortgage calculator. It is working correct in Playground but when I transfer it to the ViewController tab that is when Xcode is giving me a bunch of errors. Can someone please help?
let r: Double = interestRate / 1200
let n: Double = years * 12
let p: Double = pow(1 + r, n)
let monthPay = loan * r * p / (p - 1)
print(monthPay)
So in my View Controller
valueA is Loan Amount
valueB is Number of Payments
valueC is Interest Rate
underneath that you will have a calculate button that will print the results underneath in a label currently named results. It may be that when i rename everything to the values is when the issues occur.
ViewController Code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var valueA: UITextField!
#IBOutlet weak var valueB: UITextField!
#IBOutlet weak var valueC: UITextField!
#IBOutlet weak var results: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button(_ sender: Any) {
let a = Int(valueA.text!)
let b = Int(valueB.text!)
let c = Int(valueC.text!)
let answer = a! * c! * pow(1 + c!, b!) / (pow(1 + c!, b!))
results.text = "$\(answer)"
}
}
Your issue is that you are converting your textfield values to integers and trying to multiply them by the result of pow's method which returns a Double. Note that if the user enters an invalid value your app will crash if you force unwrap the result. Yo can use nil coalescing operator ?? to provide a default value .zero in case of failure. Try like this:
class ViewController: UIViewController {
#IBOutlet weak var valueA: UITextField! // loan amount
#IBOutlet weak var valueB: UITextField! // Number of Payments
#IBOutlet weak var valueC: UITextField! // Interest Rate
#IBOutlet weak var results: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func button(_ sender: Any) {
let loanAmount = Double(valueA.text!) ?? .zero
let numberOfPayments = Double(valueB.text!) ?? .zero
let interestRate = Double(valueC.text!) ?? .zero
let rate = interestRate / 100 / 12
let answer = loanAmount * rate / (1 - pow(1 + rate, -numberOfPayments))
results.text = Formatter.currency.string(for: answer)
}
}
extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.numberStyle = .currency
return formatter
}()
}

Cannot assign to value: 'setText' is a method

Im using Xcode Version 11.0 (11A420a) and Swift to make an iPhone and Apple Watch app I have some code that has made a simple timer label and start and stop buttons and Id like to also have that on the Apple Watch.
In the Xcode viewcontroller.swift file I have this code and it works great.
var startTime = TimeInterval()
var startofTime = Date()
var timer:Timer = Timer()
var endTime: Date!
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var Start: UIButton!
#IBOutlet weak var Stop: UIButton!
#IBAction func startAct(_ sender: Any) {
alarmTime = Date()
startofTime = Date()
Start.isHidden = true
Stop.isHidden = false
if (!timer.isValid) {
let aSelector : Selector = #selector(ViewController.updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = Date.timeIntervalSinceReferenceDate
}
}
#IBAction func stopAction(_ sender: Any) {
progressView.progress = 0.0
progress.completedUnitCount = 1024
Start.isHidden = false
Stop.isHidden = true
endTime = Date()
timer.invalidate()
}
#objc func updateTime() {
let currentTime = Date.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - startTime
// print(elapsedTime)
// print(Int(elapsedTime))
//calculate the hours in elapsed time.
let hours = UInt8(elapsedTime / 3600.0)
elapsedTime -= (TimeInterval(hours) * 3600)
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFraction = String(format: "%02d", fraction)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
timerLabel.text = "\(hours):\(strMinutes):\(strSeconds).\(strFraction)"
}
When I tried to copy it to the watch InterfaceController.swift I got most of it done by just coping and pasting but I get a error about the label.
!Cannot assign to value: 'setText' is a method
Can anyone help me with the label for the watch. Something like this -
timerLabel.setText = "\(hours):\(strMinutes):\(strSeconds).\(strFraction)"
if I just put
timerLabel.setText((strFraction))
It works to display the milliseconds but id like to combine them all please.
If I do this
timerLabel.setText((strSeconds)(strFraction))
I get this error
!Cannot call value of non-function type 'String'
This is the full watch code so far, just need the last line
//
// InterfaceController.swift
// WatchKit Extension
//
// Created by Kurt on 3/10/19.
// Copyright © 2019 Kurt. All rights reserved.
//
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
override func awake(withContext context: Any?) {
super.awake(withContext: context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
var startTime = TimeInterval()
var startofTime = Date()
var timer:Timer = Timer()
var endTime: Date!
#IBOutlet weak var timerLabel: WKInterfaceLabel!
#IBAction func Start() {
startofTime = Date()
if (!timer.isValid) {
let aSelector : Selector = #selector(InterfaceController.updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
startTime = Date.timeIntervalSinceReferenceDate
}
}
#objc func updateTime() {
let currentTime = Date.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - startTime
// print(elapsedTime)
// print(Int(elapsedTime))
//calculate the hours in elapsed time.
let hours = UInt8(elapsedTime / 3600.0)
elapsedTime -= (TimeInterval(hours) * 3600)
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFraction = String(format: "%02d", fraction)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
timerLabel.setText(\(hours):\(strMinutes):\(strSeconds).\(strFraction)) !error
}
}
You can do it as,
timerLabel.setText("\(hours):\(strMinutes):\(strSeconds).\(strFraction)")
The setText method isn't available for the UILabel class in Swift.
Assuming you've created a setText extension method to the UILabel class,
you can do it this way:
timerLabel.setText("\(hours):\(strMinutes):\(strSeconds).\(strFraction)")
The setText method isn't available for the UILabel class in Swift. You can use text propriety of UILabel.
timerLabel.text = "\(hours):\(strMinutes):\(strSeconds).\(strFraction)"
For UILabel, text is a property & :setText is setter method for this property. So either use this property as:
timerLabel.text = "\(hours):\(strMinutes):\(strSeconds).\(strFraction)"
Or, use setter method as:
timerLabel.setText("\(hours):\(strMinutes):\(strSeconds).\(strFraction)")
But for iWatch, WKInterfaceLabel does not have any text property. You can only change the text at runtime using :setText method. For more info: see here

Tying to add a percentage to a calculation of 2 UITextFields

I am using Swift 5 and I am relatively a novice and self taught at 60 it not easy to get the answers but here goes- I have 2 textfields one value is already passed from another view controller, then I have an Item cost field and a button to calculate these fields ok this works fine, but what I wish to do is have another textfield entry that I can add a markup percentage so for example textfield 1 has 50 and textfield 2 I enter 3, I would then like to add a markup value of say 4% so when I calculate these fields I get a total plus the markup percentage that's been added
I can get everything to work but can't find a way to add that pesky markup
import UIKit
class CostingsViewController: UIViewController {
//Item Cost entered into this field
#IBOutlet weak var itemCost: UITextField!
//Markup value entered into here
#IBOutlet weak var markUP: UITextField!
//This value is passed to this viewcontroller from another veiwcontroller
#IBOutlet weak var newLabel: UILabel!
//This value is calculated on the IBAction
#IBOutlet weak var totalCost: UITextField!
var finalName = ""
override func viewDidLoad() {
super.viewDidLoad()
newLabel.text = finalName
// Do any additional setup after loading the view.
}
#IBAction func calculateCost(_ sender: Any) {
//Enter the markUP calculation here
totalCost.text = String(format: "%.2f",Double(newLabel.text!)! * Double(itemCost.text!)!)
self.view.endEditing(true)
}
}
totalCost.text = String(format: "%.2f",Double(newLabel.text!)! * Double(itemCost.text!)!)
this works just fine but the Mark up I can't seem to get it to work - ive checked out lots of tutorials but seems to be many ways but none suit what I am trying
If you can say
totalCost.text = String(format: "%.2f",Double(newLabel.text!)! * Double(itemCost.text!)!)
Then you can say
let v1 = Double(newLabel.text!)!
let v2 = Double(itemCost.text!)!
let v3 = // do your math here
totalCost.text = String(format: "%.2f", v3)
I'm not condoning your code; I'm just saying that you shouldn't have any difficulty breaking it into pieces so that you can manipulate the Double values more easily.
Mathematically, it you want to add 4% to a value, it means multiplying it by 1.04.
That means:
let itemCost = Double(itemCost.text!)!
let markUp = Double(markUP.text!)!
let total = itemCost * (1 + markUp / 100)
totalCost.text = String(format: "%.2f", total)
As a side note, please, use NumberFormatter to convert numbers to a string and viceversa, especially when we are talking currencies.
The calculation of the grand total is:
// the total, not rounded (e.g. if there was one item at a unit price of 0.10 and 3% markup (i.e. 0.03), this `unrounded` will have 0.103)
let unrounded = Double(quantity) * unitPrice * (markUp + 1.0)
// You might then round that value to two decimal places, like so:
let grandTotal = (unrounded * 100.0).rounded(.toNearestOrAwayFromZero) / 100.0
That having been said, I would suggest a few other things:
You may notice that in the above, I’m not referencing UIKit controls like text fields and labels. You really want to delineate between the “model” (the prices, quantities, the total, etc.) and the “view” (the text fields, the labels, etc.).
The view objects generally, by convention, include a suffix that indicates the type of view object. So you might have markUpTextField or quantityLabel. This way, you not only won’t confuse them with the corresponding model values, but you can clearly tell what sort of object it is.
As you update a text field, you should update the model. E.g. as you change the markUpTextField, you update the markUp numeric model object.
When you’re calculating the total, you should calculate it from the model objects only. You shouldn’t be referencing any UIKit objects.
This isn’t absolutely critical, but it’s an extremely good habit to get into as it’s a central tenet of MVC (and MVVM and MVP and ...) programming patterns. The benefits of this really come into play when you eventually start using table/collection views where your UIKit controls are reused for visible items and no longer are reliable sources of information. It will also be extremely useful when you start getting into unit testing of your code, and you pull business logic out of your view controllers and move them into some mediating object like a “view model” or what have you.
You should avoid using String(format:) to create strings for the UI. Instead, use NumberFormatter. That solves two problems:
You want to accept and produce “localized” numbers in your UI. E.g., in Germany, they write a number of one million to two decimal places as 1.000.000,00. In India, it can be 10,00,000.00. Etc. By using NumberFormatter, you minimize the amount you have to code to deal with all of these international formats.
If you use a NumberFormatter with a numberStyle of .percent for your markup value, it will do the necessary division by 100 for you.
You may want to set the delegate of the UITextField objects to be your view controller (which you can do either in IB or programmatically) and then have a UITextFieldDelegate extension to your view controller whose shouldChangeCharactersIn will accept the change only if the resulting text can be changed to a number using the above formatters.
You might also want a textFieldDidEndEditing that formats the entered value nicely when the user is done.
Reflecting the above observations, you end up with something like:
class CostingsViewController: UIViewController {
// MARK: Outlets
#IBOutlet weak var quantityLabel: UILabel!
#IBOutlet weak var priceTextField: UITextField!
#IBOutlet weak var markUpTextField: UITextField!
#IBOutlet weak var totalLabel: UILabel!
// MARK: Model objects
var quantity: Int? { didSet { updateTotal() } }
var price: Double? { didSet { updateTotal() } }
var markUp: Double? { didSet { updateTotal() } }
var total: Double? { didSet { totalLabel.text = priceFormatter.string(for: total) } }
// MARK: Private formatters
private var priceFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
private var quantityFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 0
return formatter
}()
private var percentFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 2
return formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
// I'm going to set these here, but maybe these were supplied by the presenting view controller
quantity = 3
price = 1000
markUp = 0
// update the UI controls
quantityLabel.text = quantityFormatter.string(for: quantity)
priceTextField.text = priceFormatter.string(for: price)
markUpTextField.text = percentFormatter.string(for: markUp)
totalLabel.text = priceFormatter.string(for: total)
}
}
private extension CostingsViewController {
private func updateTotal() {
// calculate total
let quant = quantity ?? 0
let cost = price ?? 0
let percent = markUp ?? 0
let unrounded = Double(quant) * cost * (percent + 1.0)
// round the result
let rounded = (unrounded * 100.0).rounded(.toNearestOrAwayFromZero) / 100.0
// update our model
total = rounded
}
}
extension CostingsViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// some useful constants
let decimalSeparator = priceFormatter.decimalSeparator ?? "."
let percentSymbol = percentFormatter.percentSymbol ?? "%"
// figure out what the string value will be after replacing the characters
let oldText = textField.text ?? ""
let updateRange = Range(range, in: oldText)!
let text = oldText.replacingCharacters(in: updateRange, with: string).filter(("01234567890" + decimalSeparator).contains)
// update the appropriate model object
switch textField {
case priceTextField:
if text == "" {
price = 0
return true
} else if let value = priceFormatter.number(from: text)?.doubleValue {
price = value
return true
} else {
return false
}
case markUpTextField:
if text == "" {
markUp = 0
return true
} else if let value = percentFormatter.number(from: text + percentSymbol)?.doubleValue {
markUp = value
return true
} else {
return false
}
default:
return true
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
switch textField {
case priceTextField: textField.text = priceFormatter.string(for: price)
case markUpTextField: textField.text = percentFormatter.string(for: markUp)
default: break
}
}
}
A further refinement: When creating data types to hold the prices, I’d advise against using binary floating points like Float or Double. These types cannot actually perfectly capture fractional decimal values. I’d use Decimal type instead. This will help avoid rounding problems that can result if you start adding up many binary floating point values.
If you do that, you end up with something like:
class CostingsViewController: UIViewController {
// MARK: Outlets
#IBOutlet weak var quantityLabel: UILabel!
#IBOutlet weak var priceTextField: UITextField!
#IBOutlet weak var markUpTextField: UITextField!
#IBOutlet weak var totalLabel: UILabel!
// MARK: Model objects
var quantity: Int? { didSet { updateTotal() } }
var price: Decimal? { didSet { updateTotal() } }
var markUp: Decimal? { didSet { updateTotal() } }
var total: Decimal? { didSet { totalLabel.text = priceFormatter.string(for: total) } }
// MARK: Private formatters
private var priceFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
formatter.generatesDecimalNumbers = true
return formatter
}()
private var quantityFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 0
return formatter
}()
private var percentFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 2
formatter.generatesDecimalNumbers = true
return formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
// I'm going to set these here, but maybe these were supplied by the presenting view controller
quantity = 3
price = Decimal(1000)
markUp = Decimal(0)
// update the UI controls
quantityLabel.text = quantityFormatter.string(for: quantity)
priceTextField.text = priceFormatter.string(for: price)
markUpTextField.text = percentFormatter.string(for: markUp)
totalLabel.text = priceFormatter.string(for: total)
}
}
private extension CostingsViewController {
private func updateTotal() {
// calculate total
let quant = Decimal(quantity ?? 0)
let cost = price ?? Decimal(0)
let percent = markUp ?? Decimal(0)
var unrounded = quant * cost * (percent + Decimal(1))
// round the result
var rounded = Decimal()
NSDecimalRound(&rounded, &unrounded, 2, .bankers)
// update our model
total = rounded
}
}
extension CostingsViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// some useful constants
let decimalSeparator = priceFormatter.decimalSeparator ?? "."
let percentSymbol = percentFormatter.percentSymbol ?? "%"
// figure out what the string value will be after replacing the characters
let oldText = textField.text ?? ""
let updateRange = Range(range, in: oldText)!
let text = oldText.replacingCharacters(in: updateRange, with: string).filter(("01234567890" + decimalSeparator).contains)
// update the appropriate model object
switch textField {
case priceTextField:
if text == "" {
price = Decimal(0)
return true
} else if let value = priceFormatter.number(from: text)?.decimalValue {
price = value
return true
} else {
return false
}
case markUpTextField:
if text == "" {
markUp = Decimal(0)
return true
} else if let value = percentFormatter.number(from: text + percentSymbol)?.decimalValue {
markUp = value
return true
} else {
return false
}
default:
return true
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
switch textField {
case priceTextField: textField.text = priceFormatter.string(for: price)
case markUpTextField: textField.text = percentFormatter.string(for: markUp)
default: break
}
}
}
Finally, as I alluded to above, we’d generally like to get a lot of this code out of the view controller (using MVVP or MVP or whatever). That’s beyond the scope of this question, but I mention it for the sake of completeness.

How to detect user inactivity in OS X writing in Swift Cocoa?

I have searched answers in stackoverflow and none of them matches my needs. I am creating time tracking app on Swift Cocoa macOS, like Hubstaff time tracking app. At the moment runs a timer and I want to detect user's inactivity after x period of time and to send a Notification that he has been Idle x period of time. I'm new to iOS and macOS development. Can I have an example of how to do it?
Here is my code:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var label: NSTextField!
#IBOutlet weak var playImage: NSButton!
var timer : Timer!
var isTimerWorking : Bool = false
var startTime : Date!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func playPause(_ sender: NSButton) {
if isTimerWorking {
endTimer()
playImage.image = NSImage(named: NSImage.Name("play"))
sender.state = .off
} else {
startTimer()
playImage.image = NSImage(named: NSImage.Name("stop"))
sender.state = .off
}
}
func startTimer() {
startTime = Date()
timer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(self.timerCounter),
userInfo: nil,
repeats: true
)
isTimerWorking = true
}
func endTimer() {
if timer != nil {
timer.invalidate()
label.stringValue = "00:00:00"
}
isTimerWorking = false
}
#objc func timerCounter() {
let currentTime = Date().timeIntervalSince(startTime)
let hour = Int(fmod(currentTime/3600, 60))
let minute = Int(fmod(currentTime/60, 60))
let second = Int(fmod(currentTime, 60))
let hourValue = String(format:"%02d", hour)
let minuteValue = String(format:"%02d", minute)
let secondValue = String(format:"%02d", second)
label.stringValue = "\(hourValue):\(minuteValue):\(secondValue)"
}
}
In my own time tracking app I am using
var lastEvent:CFTimeInterval = 0
lastEvent = CGEventSource.secondsSinceLastEventType(CGEventSourceStateID.hidSystemState, eventType: CGEventType(rawValue: ~0)!)
print(lastEvent)
to get the user idle time.