# Digit group separators in a SwiftUI app

Recently one user of my iOS app [Wins (Goals Tracker)](https://apple.co/3mk0NjK) asked me for one small improvement - my app should display numbers with digit group separators (AKA thousands separators). Small change but can improve the UX of my app.

Without digit group separators, big numbers look like this:

**10000**

Thousands are not separated and the numbers are harder to read. With digit group separators the same number will look like this:

**10 000**  
**10.000**  
**10,000**

It depends on the users' locale. In iOS you set it in **Settings** / **General** / **Language & Region** / **Region**. It means that depending on the user's region, the user will see **10 000** or **10.000** or **10,000**. Many non-English-speaking countries use periods as digit group separators. Most English-speaking countries use a comma. In some countries, it's just space. But thankfully we don't need to worry too much and remember which separator to use. We just need to specify which numbers should have the separator.

## Text view

I needed to format numbers in 2 different places. The first one was the Text view.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688473205628/17f84673-dfb2-46c1-987f-dc60a2404998.png align="center")

The first thing I needed to do is to create and configure the NumberFormatter.

```swift
struct GoalView: View {
	// other properties
	
	private let numberFormatter: NumberFormatter = {
		let formatter = NumberFormatter()
		formatter.numberStyle = .decimal
		formatter.usesGroupingSeparator = true
		return formatter
	}()
	
    var body: some View {
        // body of my GoalView
    }
}
```

Previously, when I didn't use digit group separators, my Text in the body of the GoalView looked like this:

```swift
HStack(spacing: 4) {
	Text("\(goal.currently)")
	Text("/")
	Text("\(goal.goal)")
	Text(goal.currency ?? "USD")
}
```

To use digit group separators, my new code looks like this:

```swift
HStack(spacing: 4) {
	Text("\(numberFormatter.string(for: goal.currently) ?? "0")")
	Text("/")
	Text("\(numberFormatter.string(for: goal.goal) ?? "0")")
	Text(goal.currency ?? "USD")
}
```

We are using the [string(for:)](https://developer.apple.com/documentation/foundation/formatter/1415993-string) method on numberFormatter, so we are changing the number into a String and applying the style we previously configured for NumberFormatter.

## TextField

I also wanted to format numbers using digit group separators when users type data in the TextField.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688473359751/994714ea-dbf2-45e3-9c6d-0e9dae861621.png align="center")

One way of doing this is to use TextField like this:

```swift
TextField("Goal", value: $goal, format: .number)
```

But one problem with this solution is that the text is changed to the number (and formatted) only when the user presses Enter. To format the numbers on the fly, when users edit the text in the TextField, I did something else.

First, we need to use NumberFormatter as before:

```swift
struct AddGoalView: View {
	// other properties
	
	private let numberFormatter: NumberFormatter = {
		let formatter = NumberFormatter()
		formatter.numberStyle = .decimal
		formatter.usesGroupingSeparator = true
		return formatter
	}()

	@State private var goal = 0
	@State private var goalString = ""
	
    var body: some View {
        // body of my GoalView
    }
}
```

One important thing here - I am using 2 properties per each TextField, for example **goal** (Int) and **goalString** (String).

My TextFields in the body of my **AddGoalView** look like this:

```swift
TextField("0", text: $goalString)
	.keyboardType(.numberPad)
	.onChange(of: goalString) { _ in
	    goalString = goalString.filter { $0.isNumber }
	    goal = Int(goalString) ?? 0
	    goalString = numberFormatter.string(for: goal) ?? "0"
    }
```

The solution is very simple. I am using .onChange modifier. When goalString in the TextField changes, my app does 3 things:

1. **goalString** is filtered and we keep only characters that represent a number
    
2. **goalString** is converted to Int and assigned to the **goal** property
    
3. the value of **goal** is assigned to **goalString** as a string formatted as specified for **numberFormatter**
    

## Limitations

Please mind that there are some limitations here. If the user copies and pastes **10,000.50**, it will be converted to the Int value of **1000050** and this converted to **1,000,050**.

That's because of this line of code I showed you before:

```swift
goalString = goalString.filter { $0.isNumber }
```

I solved this problem in my other app [Numi](https://apple.co/3WjbjnR) - in Numi you can copy and paste numbers with decimal fractions and they are correctly formatted based on the user's locale. But Numi is an app for personal finances and decimal fractions are something very common when you work with money. In Wins I only use Integers, so I didn't need this.

In Numi I have more complex functions triggered when users edit the text in TextFields, involving properties like **formatter.minimumFractionDigits**, **formatter.maximumFractionDigits** and **formatter.roundingMode**. But this is a topic for another article :)  
Just please be aware of this and rewrite the logic in .onChange modifier based on your needs.

## Thank you for reading!

If you want to support my work, please like, comment, share the article and most importantly...

📱Check out my apps on the App Store:  
[https://apps.apple.com/developer/next-planet/id1495155532](https://apps.apple.com/developer/next-planet/id1495155532)

☕ If you like what I do, consider supporting me on Ko-fi! Every little bit means the world!  
[https://ko-fi.com/kslazinski](https://ko-fi.com/kslazinski)
