Dynamically using specific variables with custom UITableViewCell - ios

This question will be a follow-up of this previous one.
I'm at the point where the user can create UITableViewCells and enter an amount to a TextField. It is then printed to a label and should update a variable that will be used with another variable for a calculation (basically amount * currentPrice, this is a wallet).
I re-read Apple's doc about TableViews and re-opened the exercises I did when I followed the Swift course but I'm struggling to understand the principle here, so once again, I think I really need someone to explain differently than what I could read so my brain can understand.
How will the cell know what variable to use here:
Entering the amount:
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
let dNumber = formatter.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
walletTableViewCell.amountLabel.text = String(format:"%.8f", eNumber)
UserDefaults.standard.set(walletTableViewCell.amountLabel.text, forKey: "cellAmount")
walletTableViewCell.amountTextField.text = ""
}
Calculations and display:
func updateCellValueLabel(cryptoPrice: String) {
if walletTableViewCell.amountLabel.text == "" {
walletTableViewCell.amountLabel.text = "0.00000000"
}
let formatter1 = NumberFormatter()
formatter1.locale = Locale(identifier: "en_US")
let str = walletTableViewCell.amountLabel.text
let dNumber = formatter1.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
UserDefaults.standard.set(eNumber, forKey: "currentAmount")
guard let cryptoDoublePrice = CryptoInfo.cryptoPriceDic[cryptoPrice] else { return }
WalletViewController.bitcoinAmountValue = cryptoDoublePrice * eNumber
if WalletViewController.currencyCode != "" {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "\(WalletViewController.currencyCode)")
walletTableViewCell.cryptoValueLabel.text = formatter.string(from: NSNumber(value: WalletViewController.amountValue))
}
UserDefaults.standard.set(WalletViewController.walletDoubleValue, forKey: "walletValue")
}
What I can't process here is how can I save the amount entered to the corresponding crypto UserDefaults file, then load this amount to be used with the corresponding variable that has the current price for calculations.
I don't know how to use the correct data for the correct cell.
Should I make this a single function? Should I pass parameters to the function (how do I pass the correct parameters to the corresponding cell with delegate?)? Do I need an array of parameters that will be used at indexPath to the correct cell (but how do I know the order if the user creates cells at will?)?
I'm kind of lost here.

Related

Swift, converting a number to string, number gets rounded down

I'm having a bit of issue with my code...right now, I am passing a string containing a bunch of numbers, to get converted to a number, comma separators added, then converted back to a string for output. When I add a decimal to my string and pass it in, a number like 996.3658 get truncated to 996.366...
"currentNumber" is my input value, "textOutputToScreen" is my output...
func formatNumber() {
let charset = CharacterSet(charactersIn: ".")
if let _ = currentNumber.rangeOfCharacter(from: charset) {
if let number = Float(currentNumber) {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
guard let formattedNumber = numberFormatter.string(from: NSNumber(value: number)) else { return }
textOutputToScreen = String(formattedNumber)
}
}
else {
if let number = Int(currentNumber) {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
guard let formattedNumber = numberFormatter.string(from: NSNumber(value: number)) else { return }
textOutputToScreen = String(formattedNumber)
}
}
}
Thank you in advance for your help!
The issue there is that you have to set your NumberFormatter minimumFractionDigits to 4. Btw there is no need to initialize a NSNumber object. You can use Formatters string(for: Any) method and pass your Float. Btw I would use a Double (64-bit) instead of a Float (32-bit) and there is no need to initialize a new string g from your formattedNumber object. It is already a String.
Another thing is that you don't need to know the location of the period you can simply use contains instead of rangeOfCharacter method. Your code should look something like this:
extension Formatter {
static let number: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
}
func formatNumber(from string: String) -> String? {
if string.contains(".") {
guard let value = Double(string) else { return nil }
Formatter.number.minimumFractionDigits = 4
return Formatter.number.string(for: value)
} else {
guard let value = Int(string) else { return nil }
Formatter.number.minimumFractionDigits = 0
return Formatter.number.string(for: value)
}
}
let label = UILabel()
let currentNumber = "996.3658"
label.text = formatNumber(from: currentNumber) // "996.3658\n"
If you would like to assign the result to your var instead of a label
if let formatted = formatNumber(from: currentNumber) {
textOutputToScreen = formatted
}

NSMeasurementFormatter shows Imperial weights but not metric weights?

I have implemented an NSMeasurementFormatter and am having an odd issue. When the app is loaded as en_US, I get all my weights loaded into the text boxes as pounds, brilliant. However when I switch my app to en_GB and load the same data, i get nothing appearing in the text boxes.
When i print the objects the print out states the weights in kg so they are converting correctly, but they arent loading into the actual text boxes to be visible to the user.
Is there any clear cause to this? I have spent a few hours trying to work out why it works for one location but not the other! Appreciate the insight, here is the code:
Here is the initial save of an exercise where it either goes one way ot another depending on the apps setup at this point
if self.localeIdentifier == "en_GB" {
let kgWeight = Measurement(value: Double(self.userExerciseWeight.text!)!, unit: UnitMass.kilograms)
newUserExercise.weight = kgWeight as NSObject?
newUserExercise.initialMetricSystem = self.localeIdentifier
print("SAVED AS \(localeIdentifier) METRIC")
} else if self.localeIdentifier == "en_US" {
let lbsWeight = Measurement(value: Double(self.userExerciseWeight.text!)!, unit: UnitMass.pounds)
newUserExercise.weight = lbsWeight as NSObject?
newUserExercise.initialMetricSystem = self.localeIdentifier
print("SAVED AS \(localeIdentifier) IMPERIAL")
}
Then here is my attempt to later reload this objects weight property
let formatter = MeasurementFormatter()
let exerciseWeight = userExercise.weight as! Measurement<Unit>
let localeIdentifier = UserDefaults.standard.object(forKey: "locale")
let locale = Locale(identifier: localeIdentifier as! String)
formatter.locale = locale
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 2
formatter.numberFormatter = numberFormatter
let finalWeight = formatter.string(from: exerciseWeight)
cell.todaysExerciseWeightLabel.text = finalWeight
}
So when set to US, i get a pounds readout to 2 decimal places, but when i set to GB i get nothing visible, i can just see it did the conversion in the print out of the object, pics below of the console that shows for loading both routines, and shots of the results:
UPDATED BELOW WITH MASSFORMATTER INFO
let localeIdentifier = UserDefaults.standard.object(forKey: "locale") as! Locale
let exerciseWeight = userExercise.weight as! Measurement<Unit>
let formatter = MassFormatter()
formatter.numberFormatter.locale = localeIdentifier
formatter.numberFormatter.maximumFractionDigits = 2
if localeIdentifier.usesMetricSystem {
let kgWeight = exerciseWeight.converted(to: .kilograms)
let finalKgWeight = formatter.string(fromValue: kgWeight.value, unit: .kilogram)
cell.todaysExerciseWeightLabel.text = finalKgWeight
print(formatter.string(fromValue: kgWeight.value, unit: .kilogram))
} else {
let kgWeight = exerciseWeight.converted(to: .pounds)
let finalLbWeight = formatter.string(fromValue: exerciseWeight.value, unit: .pound)
cell.todaysExerciseWeightLabel.text = finalLbWeight
print(formatter.string(fromValue: exerciseWeight.value, unit: .pound))
}
}
}
It all boils down to the following piece of code:
let weight = Measurement(value: 10.0, unit: UnitMass.kilograms)
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_GB")
print(formatter.string(from: weight))
The result is an empty String ("").
This is an obvious bug. Please, report it.
A simple workaround is using the older MassFormatter:
//let locale = Locale(identifier: "en_GB")
let locale = Locale(identifier: "en_US")
let weight = Measurement(value: 10.24, unit: UnitMass.pounds)
let formatter = MassFormatter()
formatter.numberFormatter.locale = locale
formatter.numberFormatter.maximumFractionDigits = 2
if locale.usesMetricSystem {
let kgWeight = weight.converted(to: .kilograms)
print(formatter.string(fromValue: kgWeight.value, unit: .kilogram))
} else {
print(formatter.string(fromValue: weight.value, unit: .pound))
}

how to ignore a value of a UITextField that has already been calculated in swift 3?

I'm trying to make an app that is very basic. One part of the app is that there are 4 textFields and a button that calculates the sum of these textFields.
The problem that I'm facing is that say I type the value 10 in the first textField then I press the button. The result would be 10. However, if I press it again ( without typing anything in the other textFields), the result would be 20!! Furthermore, if I type 20 in one of the other textFields, the result would be 40!!
The result SHOULD BE 30 NOT 40!!
one possible option I thought of (haven't tried it yet) is assigning 0 to all of the textFields when pressing the button. But I'd like the app to be smarter and keep tracks of the result.
if it helps, here's the code inside the button that calculates the sum:
#IBAction func calBtnPressed(_ sender: UIButton) {
var benifit:[Double] = []
var textFields: [Double] = []
if initialBalance.text?.isEmpty ?? true {
// do nothing
} else {
if let temp = initialBalance.text {
// these lines of code will convert arabic numbers to English ones in case the user uses Arabic number
let initialStr: String = temp
let initialFormatter: NumberFormatter = NumberFormatter()
initialFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let initialFinal = initialFormatter.number(from: initialStr)
benifit.append(Double(initialFinal!))
}
}
if income.text?.isEmpty ?? true {
// do nothing
} else {
if let temp = income.text {
// these lines of code will convert Arabic numbers to English ones in case the user uses Arabic number
let incomeStr: String = temp
let incomeFormatter: NumberFormatter = NumberFormatter()
incomeFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let incomeFinal = incomeFormatter.number(from: incomeStr)
benifit.append(Double(incomeFinal!))
}
}
if salaries.text?.isEmpty ?? true {
// do nothing
} else {
if let temp = salaries.text {
let salariesStr: String = temp
let salariesFormatter: NumberFormatter = NumberFormatter()
salariesFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let salariesFinal = salariesFormatter.number(from: salariesStr)
textFields.append(Double(salariesFinal!))
}
}
if tools.text?.isEmpty ?? true {
// do nothing
} else {
if let temp = tools.text {
let toolsStr: String = temp
let toolsFormatter: NumberFormatter = NumberFormatter()
toolsFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let toolsFinal = toolsFormatter.number(from: toolsStr)
textFields.append(Double(toolsFinal!))
}
}
if maintinance.text?.isEmpty ?? true {
// do nothing
} else {
if let temp = maintinance.text {
let maintinanceStr: String = temp
let maintinanceFormatter: NumberFormatter = NumberFormatter()
maintinanceFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let maintinanceFinal = maintinanceFormatter.number(from: maintinanceStr)
textFields.append(Double(maintinanceFinal!))
}
}
if other.text?.isEmpty ?? true {
// do nothing
} else {
if let temp = other.text {
let otherStr: String = temp
let otherFormatter: NumberFormatter = NumberFormatter()
otherFormatter.locale = NSLocale(localeIdentifier: "EN") as Locale!
let otherFinal = otherFormatter.number(from: otherStr)
textFields.append(Double(otherFinal!))
}
}
for textField in textFields {
sumExpenses += textField
}
for ben in benifit{
sumBenifit += ben
}
totalExpenses.text = String(sumExpenses)
totalAfterSubtractingExpenses.text = String( sumBenifit - sumExpenses )
sumBenifit -= sumExpenses
}
I think I found your problem.
You use a variable sumBenefit which isn't declared in your func, so I assume it is declared in your UIViewController.
Since it is an instance variable, it will not reset each time you click the button.
If you want to reset the values of sumExpenses and sumBenefits each time the button is pressed, then you'll have to do something like this:
sumExpenses = 0
for textField in textFields {
sumExpenses = Int(textField.text)!
}
sumBenefit = 0
for ben in benefit {
sumBenefit += ben
}
I am also making the assumption that you want a number from your textField in the first for-loop, because if sumExpenses is of type Int (or any other number for that matter) then sumExpenses += textField will not compile. You need to take the text of that textField and convert it to an Int.
Again, I am still not super clear what you are trying to do, but please let me know if this works for you, or if you need further clarification.

Decimal number in text field

I'm trying to get a decimal number from a text field. It only can be a decimal number but if I enter something like 'o,5', than the bullets will spawn a lot faster than every 0.5 second.
My code:
#IBAction func enemyBulletDelayClick(_ sender: AnyObject) {
dismissKeyboard()
let correctNumber = enemyBulletDelayText.text?.replacingOccurrences(of: ",", with: ".")
enemyBulletDelay = Double(correctNumber!)!
enemyBulletDelayText.text = ""
}
(I'm converting each ',' to a '.' for the decimal numbers.)
Otherwise it would give me an error.
I tried to use this code and it worked!
Code:
let formatter = NumberFormatter()
formatter.numberStyle = NumberFormatter.Style.decimal
enemySpawnDelay = (formatter.number(from: enemySpawnDelayText.text!)?.doubleValue)!
If you have a ? you need to unwrap, not put !
There are a bunch of ways to remove . afterwards. Pick whatever you want. This is more focused on the process of what you're doing and then you can decide on using NSNumberFormatter or whatever you want to do.
guard let enemyBulletDelayString = enemyBulletDelayText.text? else {
//put whatever you want to do here if this check doesn't pass
return
}
let numberFormatter = NumberFormatter()
formatter.numberStyle = numberFormatter.Style.decimal
if let formattedNumber = numberFormatter.number(from: enemyBulletDelayString) {
enemySpawnDelay = formattedNumber.doubleValue
} else {
numberFormatter.decimalSeparator = ","
if let formattedNumber = numberFormatter.number(from: enemyBulletDelayString) {
enemySpawnDelay = formattedNumber.doubleValue
}
}
This should work with what you want to do.

Choosing units with MeasurementFormatter

This is similar to a question I asked yesterday but the answer I got doesn't seem to work in this case.
I'm getting altitude values in meters from Core Location. I want to display these in a localized form. As an example, the altitude where I am right now is 1839m above sea level. This should be displayed as 6033 feet. The best I can do with MeasurementFormatter is "1.143 mi".
let meters : Double = 1839
let metersMeasurement = Measurement(value: meters, unit: UnitLength.meters)
let measurementFormatter = MeasurementFormatter()
measurementFormatter.locale = Locale(identifier: "en_US")
let localizedString = measurementFormatter.string(from: metersMeasurement)
The .naturalScale option that answered my previous question doesn't help here. I think this is a limitation of the framework, but I wonder if anyone has a workaround for now.
You just need to convert your UnitLength from meters to feet. You can also create a custom US measurement formatter to display it as needed:
extension Measurement where UnitType == UnitLength {
private static let usFormatted: MeasurementFormatter = {
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.unitOptions = .providedUnit
formatter.numberFormatter.maximumFractionDigits = 0
formatter.unitStyle = .long
return formatter
}()
var usFormatted: String { Measurement.usFormatted.string(from: self) }
}
Playground
let value: Double = 1839
let meters: Measurement<UnitLength> = .init(value: value, unit: .meters)
let feet = meters.converted(to: .feet)
let formatted = feet.usFormatted
print(formatted) // "6,033 feet"\n
I think you are correct there's no way to specify this kind of context. You could do something like:
extension MeasurementFormatter
{
func altitudeString(from measurement: Measurement<UnitLength>) -> String
{
var measurement = measurement
let unitOptions = self.unitOptions
let unitStyle = self.unitStyle
self.unitOptions = .naturalScale
self.unitStyle = .long
var string = self.string(from: measurement)
if string.contains(self.string(from: UnitLength.miles))
{
self.unitStyle = unitStyle
measurement.convert(to: UnitLength.feet)
self.unitOptions = .providedUnit
string = self.string(from: measurement)
}
else if string.contains(self.string(from: UnitLength.kilometers))
{
self.unitStyle = unitStyle
measurement.convert(to: UnitLength.meters)
self.unitOptions = .providedUnit
string = self.string(from: measurement)
}
else
{
self.unitStyle = unitStyle
string = self.string(from: measurement)
}
self.unitOptions = unitOptions
return string
}
}
Maybe there are other culturally specific ways of measuring elevation, but this would seem better than miles and kilometers.

Resources