Setting Text View to be Int rather than String? Swift - swift

I have a text view for the user to input an Int.
I am having an issue with saving the result into an Int variable as it default thinks its a string.
What would be the best solution to force the output as an Int? I am using a numerical only keyboard so the user cannot enter strings
code:
#IBOutlet weak var userExerciseWeight: UITextField!
var userExerciseWeightSet = Int()
if let userExerciseWeightSet = Int(self.userExerciseWeight.text!) {
}

You can simply use if let construct for this. textView always will be string. all you need to do is convert textView text to Int.
if let intText = Int(self.textView.text) {
print(intText)
}

I am having an issue with saving the result into an Int variable as it default thinks its a string.
Text view is a view, not a model. What it displays is always a string. When the string happens to represent an Int, your code is responsible for converting Int to String when setting the initial value, and then for converting String back to Int when reading the value back from the view.
if let intVal = Int(textView.text) {
... intVal from text is valid
} else {
... text does not represent an int - e.g. it's empty
}
The same approach applies to displaying and reading values of other types: your program is responsible for formatting and parsing the data into an appropriate type.

Related

Build Recursive Text View in SwiftUI

My goal is to create a SwiftUI view that takes a String and automatically formats that text into Text views. The portion of the string that needs formatting is found using regex and then returned as a Range<String.Index>. This can be used to reconstruct the String once the formatting has been applied to the appropriate Text views. Since there could be multiple instances of text that needs to be formatted, running the formatting function should be done recursively.
struct AttributedText: View {
#State var text: String
var body: some View {
AttributedTextView(text: text)
}
#ViewBuilder
private func AttributedTextView(text: String) -> some View {
if let range = text.range(of: "[0-9]+d[0-9]+", options: .regularExpression) {
//The unattributed text
Text(text[text.startIndex..<range.lowerBound]) +
//Append the attributed text
Text(text[range]).bold() +
//Search for additional instances of text that needs attribution
AttributedTextView(text: String(text[range.upperBound..<text.endIndex]))
} else {
//If the searched text is not found, add the rest of the string to the end
Text(text)
}
}
I get an error Cannot convert value of type 'some View' to expected argument type 'Text', with the recommended fix being to update the recursive line to AttributedTextView(text: String(text[range.upperBound..<text.endIndex])) as! Text. I apply this fix, but still see the same compiler error with the same suggested fix.
A few workarounds that I've tried:
Changing the return type from some View to Text. This creates a different error Cannot convert value of type '_ConditionalContent<Text, Text>' to specified type 'Text'. I didn't really explore this further, as it does make sense that the return value is reliant on that conditional.
Returning a Group rather than a Text, which causes additional errors throughout the SwiftUI file
Neither of these solutions feel very "Swifty". What is another way to go about this? Am I misunderstanding something in SwiftUI?
There are a few things to clarify here:
The + overload of Text only works between Texts which is why it's saying it cannot convert some View (your return type) to Text. Text + Text == Text, Text + some View == ☠️
Changing the return type to Text doesn't work for you because you're using #ViewBuilder, remove #ViewBuilder and it'll work fine.
Why? #ViewBuilder allows SwiftUI to defer evaluation of the closure until later but ensures it'll result in a specific view type (not AnyView). In the case where your closure returns either a Text or an Image this is handy but in your case where it always results in Text there's no need, #ViewBuilder forces the return type to be ConditionalContent<Text, Text> so that it could have different types.
Here's what should work:
private static func attributedTextView(text: String) -> Text {
if let range = text.range(of: "[0-9]+d[0-9]+", options: .regularExpression) {
//The unattributed text
return Text(text[text.startIndex..<range.lowerBound]) +
//Append the attributed text
Text(text[range]).bold() +
//Search for additional instances of text that needs attribution
AttributedTextView(text: String(text[range.upperBound..<text.endIndex]))
} else {
//If the searched text is not found, add the rest of the string to the end
return Text(text)
}
}
I made it static too because there's no state here it's a pure function and lowercased it so it was clear it was a function not a type (the function name looks like a View type).
You'd just call it Self.attributedTextView(text: ...)

Arithmetic Operators on String after converting them, SwiftUI

I have two strings, boughtString and profitString, which I am getting from the user (via the user). After, I am trying to multiply 100/95 and (boughtString+profitString).The way I do this is (and show)
Text("\(Int(Int(100/95)*Int(Int(boughtString)+Int(profitString))))")
But this doesn't seem to be understood properly by Swift, what am I doing wrong? I am doing this through SwiftUI
Int(string) returns an optional Int which must be unwrapped to be used. Since this is SwiftUI which doesn't like iterative code in the View builders, a good way to handle this would be to make read-only computed properties that turn boughtString and profitString into Ints.
Also, Int(100 / 95) is just going to be 1, because integer division discards the fractional part. I assume you don't just want to multiply by 1 because that will do nothing useful. Multiplying first by 100 and then dividing by 95 will give you the result I think you're looking for.
Here is an example of how this might be done:
struct ContentView: View {
#State private var boughtString = ""
#State private var profitString = ""
// Use nil-coalescing operator ?? to provide default values if
// the String is not a valid Int
var bought: Int { Int(boughtString) ?? 0 }
var profit: Int { Int(profitString) ?? 0 }
var body: some View {
VStack {
TextField("Bought", text: $boughtString)
TextField("Profit", text: $profitString)
Text("\(100 * (bought + profit) / 95)")
}
}
}

SwiftUI TextField won't change its contents under weird corner case with Binding<String> computed property

I have a TextField inside a SwiftUI body. I have bound it to a #State var through an intermediary binding which lets me get and set a computed value...
struct ExampleView: View {
#State var symbolToBeValidated: String = ""
var body: some View {
let binding = Binding<String> (get: {
return self.symbolToBeValidated
}, set: {
var newString = $0.uppercased()
self.symbolToBeValidated = newString // <- fig. 1: redundant assignment I wish I didn't have to put
newString = newString.replacingOccurrences(
of: #"[^A-Z]"#,
with: "",
options: .regularExpression
)
self.symbolToBeValidated = newString // <- fig. 2: the final, truly valid assignment
})
let form = Form {
Text("What symbol do you wish to analyze?")
TextField("ex.AAPL", text: binding)
// [...]
I'm using the intermediary Binding so that I can transform the string to always be an Uppercased format only containing letters A-Z (as referenced by my .regularExpression). (I'm trying to make it so that the TextField only shows a validly formatted Stock Symbol on each keypress).
This works, somewhat. The problem I discovered is that if I don't call the assignment twice (as seen in fig 1) the TextField will begin to show numbers and letters (even though it isn't included in the symbolToBeValidated string. This happens, I suspect, because SwiftUI is checking the oldValue against the newValue internally, and because it hasn't changed in the background, it doesn't call a refresh to get the internal value again. The way I've found to thwart this is to include an extra assignment before the .replacingOccurences call.
This results in the number or symbol being flashed on the screen for a blip as it is being typed by the user, then it is correctly removed by the .replacingOccurences call.
There must be a more elegant way to do this. I went down the Formatter class type and tried this alternative only because Formatter resulted in a similar behavior where the errant character would blip on the screen before being removed.
If someone knows a way for this to be intercepted before anything is displayed on the screen, I would appreciate it. This is super nit-picky, but I'm just fishing for the right answer here.
Try this:
extension Binding where Value == String {
public func validated() -> Self {
return .init(
get: { self.wrappedValue },
set: {
var newString = $0.uppercased()
newString = newString.replacingOccurrences(
of: #"[^A-Z]"#,
with: "",
options: .regularExpression
)
self.wrappedValue = newString
}
)
}
}
// ...
TextField("ex.AAPL", text: self.$symbolToBeValidated.validated())
This way also allows you to reuse and test the validation code.

Converting UItextField from referencing outlet collection from String to Float in swift/Xcode

Im stuck on this part of my code, I'm trying to get an average % from the numbers entered into my Textfields. I'm very new at this and would appreciate it if someone can help out and tell me where I'm going wrong and point me in the right direction.
class ingredientViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var abvTextFields: [UITextField]!
#IBOutlet var AbvTotal: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
AbvTotal.layer.cornerRadius = 8.0
}
func abvList(abvTextFields:Int...) -> Float {
let abv1 = Float(abvTextFields.reduce(0,+))
let abv2 = Float(abvTextFields.count)
let abv3 = abv1 / abv2
return abv3
}
Assuming abvTextFields is a valid outlet collection of text fields their content will be strings, therefore trying to sum them using reduce(0,+) won't work. You will need to convert them to Ints first:
let sum = abvTextFields.compactMap({Int($0.text ?? "")}).reduce(0,+)
let validNumbers = abvTextFields.compactMap({Int($0.text ?? "")}).count
let average = sum / validNumbers
The above handles the text fields being empty by nil coalescing an empty string for their content, and then tries to convert the string to an Int. If the string isn't numeric converting to an Int will return nil, so compactMap is used to drop these cases.
It would be possible to do all the conversion within a complex reduce block, but this demonstrates the steps more clearly.
EDIT Note the comment below. Just use Int($0.text!) without the nil coalescing.

How to detect illegal input in Swift string when converting to Double

Converting a string to a double in Swift is done as follows:
var string = "123.45"
string.bridgeToObjectiveC().doubleValue
If string is not a legal double value (ex. "a23e") the call to doubleValue will return 0.0. Since it is not returning nil how am I supposed to discriminate between the legal user input of 0.0 and an illegal input?
This is not a problem that is specific to Swift, With NSString in Objective-C, the doubleValue method returns 0.0 for invalid input (which is pretty horrible!). You are going to have to check the format of the string manually.
See the various options available to you here:
Check that a input to UITextField is numeric only
Here is my solution. Try to parse it and if it's not nil return the double value, otherwise handle it how you want.
var string = "2.1.2.1.2"
if let number = NSNumberFormatter().numberFromString(string) {
return number.doubleValue
} else {
...
}
Here is the solution I would use:
var string = NSString(string:"string")
var double = string.doubleValue
if double == 0.0 && string == "0.0"
{
//double is valid
}
else
{
//double is invalid
}
Firstly, I couldn't reassign the double value of the string to the original string variable, so I assigned it to a new one. If you can do this, assign the original string variable to a new variable before you convert it to a double, then use the new variable in place of string in the second if statement, and replace double with string the first statement.
The way this works is it converts the string to a double in a different variable, then it checks to see if the double equals 0.0 and if the original string equals 0.0 as a string. If they are both equal, the double is valid, as the user originally input 0.0, if not, the double is invalid. Put the code you want to use if the double is invalid in the else clause, such as an alert informing the user their input was invalid.