Bind properties in Swift to NSTextFields - swift

I'm working on a unit converter written in Swift that will automatically display the updated units within the appropriate NSTextFields. For this example, if the user inputs minutes into the minutesField, the secondsField and hoursField should automatically update to display the converted values from the properties. Below is an example of the code in my view controller:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var secondsField: NSTextField!
#IBOutlet weak var minutesField: NSTextField!
#IBOutlet weak var hoursField: NSTextField!
var sec: Double = 1
var min: Double = 1
var hr: Double = 1
let conv = [[1, 0.0166666666666667, 0.000277777777777778],
[60, 1, 0.0166666666666667],
[3600, 60, 1]]
func updateTextFields() {
secondsField.stringValue = "\(sec)"
minutesField.stringValue = "\(min)"
hoursField.stringValue = "\(hr)"
}
}
extension ViewController: NSTextFieldDelegate {
override func controlTextDidChange(obj: NSNotification) {
let tag = obj.object?.tag() ?? 0
let text = obj.object?.stringValue ?? "1"
let value = Double(text) ?? 1.0
switch tag {
case 0:
// base unit = seconds
sec = value
min = value * conv[0][1]
hr = value * conv[0][2]
case 1:
// base unit = minutes
sec = value * conv[1][0]
min = value
hr = value * conv[1][2]
case 2:
// base unit = hours
sec = value * conv[2][0]
min = value * conv[2][1]
hr = value
default:
"none"
}
updateTextFields()
}
}
The code above updates the text fields but the active field with the input freezes after the first key stroke. For example, if you try to enter 60 into the seconds field, the next key stroke does not do anything. The number 6.0 appears after the first key stroke (see image below). This is likely due to the function that is called after the properties are updated. The properties are updated based on the tag of the active text field.
Is it possible to have a function that will update all the other text fields other than the currently active field?

Is it possible to have a function that will update all the other text
fields other than the currently active field?
Yes. You can connect all text field to the same action.
First connect all your text fields accordingly to your view controller:
#IBOutlet weak var hourField: NSTextField!
#IBOutlet weak var minuteField: NSTextField!
#IBOutlet weak var secondField: NSTextField!
Second create a single var to represent your time interval. You can also add a setter / getter to store it automatically to USerDefaults:
var timeInterval: TimeInterval {
get {
return UserDefaults.standard.double(forKey: "timeInterval")
}
set {
UserDefaults.standard.set(newValue, forKey: "timeInterval")
}
}
Third create a TimeInterval extension to convert the time from seconds to hour/minute
extension TimeInterval {
var second: Double { return self }
var minute: Double { return self / 60 }
var hour: Double { return self / 3600 }
var hourToMinute: Double { return self * 60 }
var hourToSecond: Double { return self * 3600 }
var minuteToHour: Double { return self / 60 }
var minuteToSecond: Double { return self * 60 }
}
Fourth create the action that will update the other text fields:
#IBAction func timeAction(sender: NSTextField) {
// use guard to convert the field string to Double
guard let time = Double(sender.stringValue) else { return }
// switch the field
switch sender {
case hourField:
// convert / update secondary fields
minuteField.stringValue = time.hourToMinute.description
secondField.stringValue = time.hourToSecond.description
// make it persist through launches
timeInterval = time * 3600
case minuteField:
hourField.stringValue = time.minuteToHour.description
secondField.stringValue = time.minuteToSecond.description
timeInterval = time * 60
default:
hourField.stringValue = time.hour.description
minuteField.stringValue = time.minute.description
timeInterval = time
}
}
Last but not least make sure you load the values next time your view will appear:
override func viewWillAppear() {
hourField.stringValue = timeInterval.hour.description
minuteField.stringValue = timeInterval.minute.description
secondField.stringValue = timeInterval.second.description
}
Sample Project

You need to move the setting of the text fields' values out of viewDidLoad() to a separate method. Call that method from viewDidLoad() and also from a didSet property observer on your seconds property.
You also need to create action methods for when the seconds, minutes, or hours fields are changed. In the action method for the seconds field, set the seconds property to secondsField.floatValue. Similarly for the other two fields. In the NIB/storyboard, connects the text fields' actions to the corresponding action method on the view controller.

Related

when i try to get value from UITextField it returns the previous value not the current. #Swift

I have a viewController than contains a textField and a button.
Each time I press the button a different image would appear and the user must write in the textField a caption for the image (images retrieved from a database).
the problem is when I try to save the text value of the field it gives me the previous value that the user entered not the current one.
Anyone can help, please.
Thank you
#IBOutlet weak var field: UITextField!
#IBOutlet weak var submit: UIButton!
public func pr (ind: Int) {
let ref = Database.database().reference()
let doc = db.collection("users").document(organized.uid)
if ind < list.count {
var color1 = hexStringToUIColor(hex: list[ind])
textview.backgroundColor = color1
textview.isEditable = false
let userResponse = self.field.text
var idd = "\(organized.ind)"
let number = Int.random(in: 0 ... 1000000)
let v = "userResponse\(number)"
ref.child(idd).child("values").child(v).setValue(userResponse)
organized.ind += 1 //new place
//increment index by 1 in the database
doc.updateData(["index": FieldValue.increment(Int64(1))])
}
else {
print("congrats all colours")
}
return
}
Try using the following solution, Where you will get an event on text field value change.
textfield.addTarget(self, action:
#selector(ViewController.textFieldDidChange(_:)), for: .editingChanged)
#objc func textFieldDidChange(_ textField: UITextField) {
//print(textField.text)
}

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.

Having Problems calculating the volume with 3 input values.

i'm a fairly new programmer and i've just started on Xcode. Atm i am trying to make a calculator that can calculate the total volume from 3 input values (Height, length & width) but i can't seem to make it calculate it. right now i am able to enter a value and click the button and then it crashes.
Thanks in advance!
here is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBOutlet weak var HeightInput: NSTextField!
#IBOutlet weak var LengthInput: NSTextField!
#IBOutlet weak var WidthInput: NSTextField!
#IBOutlet weak var Result: NSTextField!
#IBAction func Calculate(_ sender: Any) {
var height = Int(HeightInput.stringValue)
var width = Int(WidthInput.stringValue)
var length = Int(LengthInput.stringValue)
if height == 0
{
Result.stringValue = "Error"
}
else{
var result = width! * height! * length!
Result.stringValue = "the volume is: \(result)"
}
}
Try something like this.
#IBAction func Calculate(_ sender: Any) {
let height = Int(HeightInput.stringValue) ?? 0
let width = Int(WidthInput.stringValue) ?? 0
let length = Int(LengthInput.stringValue) ?? 0
let result = height * width * length
if result == 0 {
Result.stringValue = "Error"
else {
Result.stringValue = "The volume is: \(result)"
}
}
When converting your label value to an integer, it will return an optional (meaning it could be nil). When you are calculating your result, you are force-unwrapping these optionals with the !.
I recommend conditionally unwrapping these values. Using ?? meaning that if the value is nil, it will default to whatever you put after the ??.

Multiply a UI text field with number - Swift

Just starting learning swift but stuck when trying to multiply an input with another number and display on a label. I get the error that the number isn't a string and tried to cast but didn't work.
class ViewController: UIViewController {
#IBOutlet weak var entry: UITextField!
#IBOutlet weak var answer: UILabel!
#IBAction func button(_ sender: Any) {
answer.text = entry.text * 2
}
}
You should cast the text into a Double, an Int etc., then convert the calculation to a string.
if let entry = Double(entry.text) {
answer.text = "\(entry * 2)"
}
or
if let entry = Int(entry.text) {
answer.text = "\(entry * 2)"
}
If you know that the entry will hold a number
answer.text = String(Int(entry.text)! * 2)
Using optional unwrapping instead
if let num = Int(entry.text) {
answer.text = String(num * 2)
}

WatchKit : connect 2 sliders to 1 label

tenSlider is going to change the currentBPM value by 10 and pass its result to bpmLabel. This works fine.
However, I also want the onesSlider update that same label, but instead by +1 or -1.
The problem is that it doesn't check the current value and update that value. Instead it just updates its own value and passes it to the bpmLabel.
Anyone know how to connect the two?
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
#IBOutlet var bpmLabel: WKInterfaceLabel!
#IBOutlet var tenSlider: WKInterfaceSlider!
#IBOutlet var onesSlider: WKInterfaceSlider!
var currentBPM = Int()
#IBAction func tenSliderDidChange(value: Int) {
currentBPM = value
updateLabel()
}
#IBAction func onesSliderDidChange(value: Int) {
currentBPM = value
updateLabel()
}
func updateLabel() {
bpmLabel.setText("\(currentBPM)")
}
You're always changing the currentBPM value to the value that is set by the slider. So if you set the ones slider the currentBPM value contains the values for one slider and same thing for tens. Since you can't access the value from sliders directly I suggest going this way:
var ones = Int()
var tens = Int()
var currentBPM: Int {
return tens * 10 + ones
// return tens + ones - depending on how did you set the min, max and step values on the ten slider
}
#IBAction func tenSliderDidChange(value: Int) {
tens = value
updateLabel()
}
#IBAction func onesSliderDidChange(value: Int) {
ones = value
updateLabel()
}
func updateLabel() {
bpmLabel.setText("\(currentBPM)")
}