SwiftUI Textfields and Ints - swiftui

I started today to use SwiftUI an I want to code a small calculator. Therefore I want a textfield where the user can write a number. But the usual textfields only accepts strings
what can I do ?

If you want to force the TextField keyboard to be numeric, just do this:
TextField("Input", text: $input)
.keyboardType(.decimalPad)
where the keyboardType is a UIKeyboardType.

with SwiftUI 2.0 you could use something like this:
struct ContentView: View {
#State var theInt: Int?
#State var text = ""
var body: some View {
VStack {
TextField("enter a number", text: $text)
.onChange(of: text) {
let txt = $0.filter { "-0123456789".contains($0) }
if allowed(txt) {
theInt = Int(txt)
text = txt
} else {
text = String(txt.dropLast())
}
}
}
}
func allowed(_ str: String) -> Bool {
let num = str.starts(with: "-") ? String(str.dropFirst()) : str
return CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: num))
}
}

Related

Stop character repetition in TextField without NumberFormatter

For example in a textfield I want to be able to repeat all numbers, but user should be able to use decimal point only once. So, the user should not be able to do: "12...4" but should only be able to do it once "12.4". Any particular way to do do it? Is it possible to do it with SwiftUI without using UIKit?
import SwiftUI
import Combine
struct TipsView: View {
#State private var amount = ""
var body: some View {
NavigationView {
Form {
Section (header: Text("Amount")) {
HStack (spacing: 1) {
Text("£")
TextField("Amount", text: $amount)
.onReceive(Just(amount)) { _ in
}
}
}
}
}
}
}
I have used a String separator to separate the input string into two different arrays. Meaning, one array would contain values before the decimal point and the other would contain values after the decimal point.
import SwiftUI
struct DecimalView: View {
#State private var amount = ""
var body: some View {
Form {
Section(header: Text("Amount")) {
HStack {
Text("£")
TextField("Enter amount", text: $amount)
.onChange(of: amount) { _ in
let filtered = amount.filter {"0123456789.".contains($0)}
if filtered.contains(".") {
let splitted = filtered.split(separator: ".")
if splitted.count >= 2 {
let preDecimal = String(splitted[0])
let afterDecimal = String(splitted[1])
amount = "\(preDecimal).\(afterDecimal)"
}
}
}
}
}
}
}
}

SwiftUI TextField force lowercase

I would like to use a specific TextField for url entries where the user could only enter lowercase characters, but I can't find any TextField modifier for this purpose. Is there any solution?
TextField has a .autocapitalization() method.
You can use like this without custom binding:
TextField("URL", text: $url)
.keyboardType(.URL)
.autocapitalization(.none)
For iOS 15 SwiftUI have a new .textInputAutocapitalization() method:
.textInputAutocapitalization(.never)
This means that any text input by the user will be .lowercased()
You can create a custom binding and set your state URL variable to the lowercased version of the input through it:
struct ContentView: View {
#State var url: String = ""
var body: some View {
let binding = Binding<String>(get: {
self.url
}, set: {
self.url = $0.lowercased()
})
return VStack {
TextField("Enter URL", text: binding)
}
}
}
XCODE 13
SwiftUI - IOS 15.0
FROM:
.autocapitalization(.none)
TO:
.textInputAutocapitalization(.never)
Example:
TextField("Enter URL", text: $url)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
if all you want is to "end up" with a lowercase string after the user press return, you could do this:
#State var txt: String = ""
var body: some View {
TextField("", text: $txt, onEditingChanged: { _ in
self.txt = self.txt.lowercased()
})
}
a more complicated but more flexible way, is something like this:
class LowerCaseStringFormatter: Formatter {
override func string(for obj: Any?) -> String? {
guard let str = obj as? NSString else { return nil }
return str.lowercased as String
}
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
obj?.pointee = string.lowercased() as NSString
return true
}
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
return true
}
}
and call it like this:
TextField("type something...", value: $txt, formatter: LowerCaseStringFormatter())

SwiftUI action at deselection of a Lists row

I have a SwiftUI List, that changes an attribute on a row, e.g. color on a tap.
Now I want to start an action e.g. reset the color, if another row is tapped.
I´m looking for an event, that the row receives ,if it is deselected.
Here my example code:
struct ContentView: View {
#State var data : [String] = ["first","second","third","4th","5th"]
var body: some View {
List {
ForEach (data, id: \.self) {
item in
ColoredRow(text: item)
}
}
}
}
struct ColoredRow: View {
var text: String = ""
#State var col : Color = Color.white
var body: some View{
Text("\(text)")
.background(col)
.onTapGesture {
self.col = Color.red
}
// .onDeselect {
// print("disappeare \(self.text)")
// self.col = Color.white
// }
}
}
Let' recall that SwiftUI is reactive (ie. state-driven, not event-driven), so if we wan't to change something in UI we need to find a way to change it via state (either UI or model, but state).
So, below is slightly modified your code to show possible approach. Tested with Xcode 11.2 / iOS 13.2.
struct ContentView: View {
#State var data : [String] = ["first","second","third","4th","5th"]
#State private var selectedItem: String? = nil
var body: some View {
List {
ForEach (data, id: \.self) {
item in
ColoredRow(text: item, selection: self.$selectedItem)
}
}
}
}
struct ColoredRow: View {
var text: String = ""
#Binding var selection: String?
#State var col : Color = Color.white
var body: some View{
Text("\(text)")
.background(selection == text ? Color.red : Color.white)
.onTapGesture {
self.selection = (self.selection == self.text ? nil : self.text)
}
}
}

Format text field to 2 decimal places without entering a decimal (SwiftUI)

In a text field, I'd like, when a user enters a number e.g. 12345, it gets formatted as 123.45. The user never needs to enter a decimal place, it just uses the 2 right most numbers as the decimal places. The field should only allow numbers too. This is for a SwiftUI project. Thanks in advance for any assistance.
Because there of a two way binding between what you enter and what is being shown in the TextField view it seems not possible to interpolate the displayed number entered. I would suggest a small hack:
create a ZStack with a TextField and a Text View superimposed.
the foreground font of the entered text in the TextField is clear or white .foregroundColor(.clear)
the keyboard is only number without decimal point: .keyboardType(.numberPad)
use .accentColor(.clear) to hide the cursor
the results are displayed in a Text View with formatting specifier: "%.2f"
It would look like
This is the code:
struct ContentView: View {
#State private var enteredNumber = ""
var enteredNumberFormatted: Double {
return (Double(enteredNumber) ?? 0) / 100
}
var body: some View {
Form {
Section {
ZStack(alignment: .leading) {
TextField("", text: $enteredNumber)
.keyboardType(.numberPad).foregroundColor(.clear)
.textFieldStyle(PlainTextFieldStyle())
.disableAutocorrection(true)
.accentColor(.clear)
Text("\(enteredNumberFormatted, specifier: "%.2f")")
}
}
}
}
}
With Swift UI the complete solution is
TextField allow numeric value only
Should accept only one comma (".")
Restrict decimal point upto x decimal place
File NumbersOnlyViewModifier
import Foundation
import SwiftUI
import Combine
struct NumbersOnlyViewModifier: ViewModifier {
#Binding var text: String
var includeDecimal: Bool
var digitAllowedAfterDecimal: Int = 1
func body(content: Content) -> some View {
content
.keyboardType(includeDecimal ? .decimalPad : .numberPad)
.onReceive(Just(text)) { newValue in
var numbers = "0123456789"
let decimalSeparator: String = Locale.current.decimalSeparator ?? "."
if includeDecimal {
numbers += decimalSeparator
}
if newValue.components(separatedBy: decimalSeparator).count-1 > 1 {
let filtered = newValue
self.text = isValid(newValue: String(filtered.dropLast()), decimalSeparator: decimalSeparator)
} else {
let filtered = newValue.filter { numbers.contains($0)}
if filtered != newValue {
self.text = isValid(newValue: filtered, decimalSeparator: decimalSeparator)
} else {
self.text = isValid(newValue: newValue, decimalSeparator: decimalSeparator)
}
}
}
}
private func isValid(newValue: String, decimalSeparator: String) -> String {
guard includeDecimal, !text.isEmpty else { return newValue }
let component = newValue.components(separatedBy: decimalSeparator)
if component.count > 1 {
guard let last = component.last else { return newValue }
if last.count > digitAllowedAfterDecimal {
let filtered = newValue
return String(filtered.dropLast())
}
}
return newValue
}
}
File View+Extenstion
extension View {
func numbersOnly(_ text: Binding<String>, includeDecimal: Bool = false) -> some View {
self.modifier(NumbersOnlyViewModifier(text: text, includeDecimal: includeDecimal))
}
}
File ViewFile
TextField("", text: $value, onEditingChanged: { isEditing in
self.isEditing = isEditing
})
.foregroundColor(Color.neutralGray900)
.numbersOnly($value, includeDecimal: true)
.font(.system(size: Constants.FontSizes.fontSize22))
.multilineTextAlignment(.center)

SwiftUI: Add ClearButton to TextField

I am trying to add a ClearButton to TextField in SwiftUI when the particular TextField is selected.
The closest I got was creating a ClearButton ViewModifier and adding it to the TextField using .modifer()
The only problem is ClearButton is permanent and does not disappear when TextField is deselected
TextField("Some Text" , text: $someBinding).modifier(ClearButton(text: $someBinding))
struct ClearButton: ViewModifier {
#Binding var text: String
public func body(content: Content) -> some View {
HStack {
content
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
}
}
}
}
Use ZStack to position the clear button appear inside the TextField.
TextField("Some Text" , text: $someBinding).modifier(ClearButton(text: $someBinding))
struct ClearButton: ViewModifier
{
#Binding var text: String
public func body(content: Content) -> some View
{
ZStack(alignment: .trailing)
{
content
if !text.isEmpty
{
Button(action:
{
self.text = ""
})
{
Image(systemName: "delete.left")
.foregroundColor(Color(UIColor.opaqueSeparator))
}
.padding(.trailing, 8)
}
}
}
}
Use .appearance() to activate the button
var body: some View {
UITextField.appearance().clearButtonMode = .whileEditing
return TextField(...)
}
For reuse try with this:
func TextFieldUIKit(text: Binding<String>) -> some View{
UITextField.appearance().clearButtonMode = .whileEditing
return TextField("Nombre", text: text)
}
=== solution 1(best): Introspect https://github.com/siteline/SwiftUI-Introspect
import Introspect
TextField("", text: $text)
.introspectTextField(customize: {
$0.clearButtonMode = .whileEditing
})
=== solution 2: ViewModifier
public struct ClearButton: ViewModifier {
#Binding var text: String
public init(text: Binding<String>) {
self._text = text
}
public func body(content: Content) -> some View {
HStack {
content
Spacer()
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
.opacity(text == "" ? 0 : 1)
.onTapGesture { self.text = "" } // onTapGesture or plainStyle button
}
}
}
Usage:
#State private var name: String
...
Form {
Section() {
TextField("NAME", text: $name).modifier(ClearButton(text: $name))
}
}
=== solution 3: global appearance
UITextField.appearance().clearButtonMode = .whileEditing
You can add another Binding in your modifier:
#Binding var visible: Bool
then bind it to opacity of the button:
.opacity(visible ? 1 : 0)
then add another State for checking textField:
#State var showClearButton = true
And lastly update the textfield:
TextField("Some Text", text: $someBinding, onEditingChanged: { editing in
self.showClearButton = editing
}, onCommit: {
self.showClearButton = false
})
.modifier( ClearButton(text: $someBinding, visible: $showClearButton))
Not exactly what you're looking for, but this will let you show/hide the button based on the text contents:
HStack {
if !text.isEmpty {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle")
}
}
}
After initializing a new project we need to create a simple view modifier which we will apply later to our text field. The view modifier has the tasks to check for content in the text field element and display a clear button inside of it, if content is available. It also handles taps on the button and clears the content.
Let’s have a look at that view modifier:
import SwiftUI
struct TextFieldClearButton: ViewModifier {
#Binding var text: String
func body(content: Content) -> some View {
HStack {
content
if !text.isEmpty {
Button(
action: { self.text = "" },
label: {
Image(systemName: "delete.left")
.foregroundColor(Color(UIColor.opaqueSeparator))
}
)
}
}
}
}
The code itself should be self explanatory and easy to understand as there is no fancy logic included in our tasks.
We just wrap the textfield inside a HStack and add the button, if the text field is not empty. The button itself has a single action of deleting the value of the text field.
For the clear icon we use the delete.left icon from the SF Symbols 2 library by Apple, but you could also use another one or even your own custom one.
The binding of the modifier is the same as the one we apply to the text field. Without it we would not be able to check for content or clear the field itself.
Inside the ContentView.swift we now simply add a TextField element and apply our modifier to it — that’s all!
import SwiftUI
struct ContentView: View {
#State var exampleText: String = ""
var body: some View {
NavigationView {
Form {
Section {
TextField("Type in your Text here...", text: $exampleText)
.modifier(TextFieldClearButton(text: $exampleText))
.multilineTextAlignment(.leading)
}
}
.navigationTitle("Clear button example")
}
}
}
The navigation view and form inside of the ContentView are not required. You could also just add the TextField inside the body, but with a form it’s much clearer and beautiful. 🙈
And so our final result looks like this:
I found this answer from #NigelGee on "Hacking with Swift".
.onAppear {
UITextField.appearance().clearButtonMode = .whileEditing
}
It really helped me out.
Simplest solution I came up with
//
// ClearableTextField.swift
//
// Created by Fred on 21.11.22.
//
import SwiftUI
struct ClearableTextField: View {
var title: String
#Binding var text: String
init(_ title: String, text: Binding<String>) {
self.title = title
_text = text
}
var body: some View {
ZStack(alignment: .trailing) {
TextField(title, text: $text)
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
.onTapGesture {
text = ""
}
}
}
}
struct ClearableTextField_Previews: PreviewProvider {
#State static var text = "some value"
static var previews: some View {
Form {
// replace TextField("Original", text: $text) with
ClearableTextField("Clear me", text: $text)
}
}
}