반응형
하려는 것
- 숫자가 길어지면 읽기가 힘듭니다.
- 1000000000와 1,000,000,000가 있다면 전자는 일십백천만... 하면서 1개씩 세야 겠지만 숫자를 자주 다루신 분들이면 후자가 10억이라는걸 바로 알 수 있습니다.
- ,를 붙여서 보여주는건 간단합니다. 문제는 유저가 숫자를 입력할 때 입니다. 입력할 때는 어떻게 ,를 넣을 수 있을까요?
문제를 나눠서 생각해 보자
- 우선, 유저가 입력하는 값은 모두 숫자일지 모르겠지만 보여야 되는 값은 숫자 뿐만 아니라 ,를 포함하기 때문에 문자입니다.
- 가장 단순한 해결책은 유저가 보는 값을 저장하는 변수와 유저가 입력하는 값을 저장하는 변수를 따로 두고, 각자 업데이트를 하면 됩니다. 유저가 입력하면 입력값 저장하는 변수 업데이트 -> 필요하면 , 삽입 후 유저가 보는 값 업데이트
- 하지만 잘 생각해 보면 굳이 따로 변수를 둘 필요는 없습니다. 유저가 입력하자 마자 ,를 삽입하는 동작을 하면 변수 1개로 충분합니다. 바로 바로 업데이트 한다면 ,는 맨 뒤에 올 수 없기 때문에 유저가 ,를 지울 일이 없습니다.
- 이를 간단히 코드로 나타내면 아래과 같습니다.
@State private var number: String = ""
TextField("", text: $number)
.keyboardType(.decimalPad)
.onChange(of: number) { number = number.formattedAsCommaSeparatedNumber }
extension String {
var formattedAsCommaSeparatedNumber: String {
let digitsOnly = self.filter { $0.isNumber }
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.groupingSeparator = ","
if let number = Int(digitsOnly) {
return numberFormatter.string(from: NSNumber(value: number)) ?? ""
}
return self
}
}
- 우선 유저의 입력값을 저장할 변수 number를 선언했습니다. number는 값의 변화에 따라 UI가 편화해야 하니 @State로 선언했습니다.
- 간단한 TextField를 선언하고, 숫자만 입력할 수 있게 하기 위해 .keyboardType(.decimalPad) 옵션을 줬습니다. 이렇게 하면 일반적인 키보드 대신 아래와 같이 숫자만 입력할 수 있는 키보드가 됩니다. 주의할 점은 이렇게 한다고 숫자만 입력할 수 있는건 아니라는 겁니다. 키보드를 연결하면 .keyboardType에 상관없이 문자도 입력 가능합니다.
- 문자가 입력될 때를 대비해서 digitsOnly에 숫자만 남기게 했습니다. 이건 ,를 제거하고자 하는 의도도 있지만, 숫자 외의 문자가 입력되면 값이 그냥 무시 돼 버립니다. (입력하자마자 삭제됨)
- 다음으로 3번째 자리마다 ,를 삽입해야 하는데 직접 구현해도 되지만 NumberFormatter()를 사용해 구현했습니다.
- 이 함수는 .onChange에서 number가 바뀔 때 마다 호출됩니다.
소수점도 고려하자
- 저 구현은 소수점이 없을때는 잘 동작하지만 소수점이 등장하면 좀 복잡해 집니다. 아래 코드에서 소수점의 .이 걸리기 때문에 조금 다른 방식으로 접근해야 합니다.
let digitsOnly = self.filter { $0.isNumber }
- 일단 정수 부분과 소수점 부분을 나눠서 생각해 봅시다! 소수점 부분은 ,가 필요 없습니다. 즉, 정수 부분은 기존과 똑같은 로직을 적용하고, 소수점 부분은 그래로 붙이면 됩니다.
var formatted: String {
let parts = self.split(separator: ".", maxSplits: 1, omittingEmptySubsequences: false)
let integerPart = parts.first?.filter { $0.isNumber } ?? ""
let decimalPart = (parts.count > 1 ? parts.last?.filter { $0.isNumber } : nil) ?? ""
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.groupingSeparator = ","
if let number = Int(integerPart) {
let integerPartFormatted = numberFormatter.string(from: NSNumber(value: number)) ?? ""
return "\(integerPartFormatted)\(self.contains(".") ? "." : "")\(decimalPart)"
}
return self
}
- parts에 .을 기준으로 앞부분, 뒷부분을 저장하고 앞부분은 정수 부분 (integerPart), 뒷부분은 소수점 부분(decimalPart)로 나눴습니다.
- 기존과 똑같이 numberFormatter를 사용해서 ,를 넣어주고 소수점 부분은 그대로 뒤에 붙여 줍니다. 정수 부분 뒤에 원래 문자에 .이 있으면 .을 1개 넣고 없으면 빈 문자열을 넣습니다. 이렇게 하면 유저가 ..을 입력했을 때 .이 들어 있어서 contains가 true지만 true여도 .은 1개만 넣기 때문에 .이 여러개인 이상한 숫자가 들어가지 않습니다.
리팩토링?
이건 굉장히 자주 쓸 거 같아서 바로 분리해서 리팩토링 했습니다.
struct NumberField: View {
var label: String
@Binding var number: String
init(_ label: String, number: Binding<String>) {
self.label = label
self._number = number
}
var body: some View {
TextField(label, text: $number)
.keyboardType(.decimalPad)
.onChange(of: number) {
number = number.formatted
}
}
}
extension String {
var formatted: String {
let parts = self.split(separator: ".", maxSplits: 1, omittingEmptySubsequences: false)
let integerPart = parts.first?.filter { $0.isNumber } ?? ""
let decimalPart = (parts.count > 1 ? parts.last?.filter { $0.isNumber } : nil) ?? ""
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.groupingSeparator = ","
if let number = Double(integerPart) {
let integerPartFormatted = numberFormatter.string(from: NSNumber(value: number)) ?? ""
return "\(integerPartFormatted)\(self.contains(".") ? "." : "")\(decimalPart)"
}
return self
}
func toDouble() -> Double {
return Double(self.replacingOccurrences(of: ",", with: "")) ?? 0.0
}
}
- SwiftUI에서 기본 제공되는 View들의 이름이 TextField, SecureField 등이여서 비슷하게 NumberField로 했습니다. 이런 사소한 이름이 별로 중요해 보이지 않지만 인지 부하를 줄이는데 매우 도움이 됩니다.
- Parameter의 이름들도 TextField와 똑같이 label, number로 했고 label은 unnamed parameter로 처리했습니다.
- 저는 종류별로 따로 모아 놓는 것 보다 (String Extension을 따로 놓는다거나) 관련 있는 것들 끼리 모아 놓는걸 좋아해서 ,를 삽입하는데 필요한 함수와 ,가 삽입된 String을 다시 Double로 변환 해 주는 함수를 같은 파일에 놨습니다.
반응형
'👨💻 프로그래밍 > 🍎 iOS 개발' 카테고리의 다른 글
🍎 iOS에서 서버를 거치지 않고 알림 보내기 (UserNotifications) (0) | 2024.08.02 |
---|---|
🍎 XCTest로 Swift에서 Unit Test 하기 (0) | 2024.07.28 |
🍎 WidgetKit 이용해서 애플 위젯 개발하기 (0) | 2024.06.22 |
🍎 Swift Data를 써서 iCloud 연동 기능 만들기 (1) | 2023.12.24 |
🍎 취미 iOS 앱 개발 후기 (0) | 2023.08.03 |