입력값을 제한할 때는 보통 세 가지를 고려해야 한다.
숫자만 입력한다고 가정 해 보자.
- 사용자는 숫자키보드(numeric)가 아닌 SW키보드를 사용할 가능성이 있다.
- 블루투스나 iPad의 스마트 키보드등의 외장 HW키보드를 사용할 가능성이 있다.
- 복사, 붙여넣기로 값을 입력할 수 있다.
struct LabelView: View {
@State private var value = ""
@State private var input = ""
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
var body: some View {
Form {
Text("Form")
TextField("Field_01", text: $value, prompt: Text("Field_01"))
}
GeometryReader { geometry in
VStack {
Section {
TextField("Field_02", text: $input, prompt: Text("Field_02"))
.textFieldStyle(.roundedBorder)
.padding()
.textContentType(.telephoneNumber)
.keyboardType(.numberPad)
} header: {
Text("Stack")
}
}
.frame(minHeight: (geometry.size.height) / 2)
}
}
}
위의 코드와 같이 TextField의 keyboardType을 변경해 주는 것으로 첫 번째 가능성을 해결할 수 있다.
다만 UIKit 때와 마찬가지로 이외의 가능성을 원천적으로 방지하는 것이 SwiftUI에서도 필요하다.
타이밍
SwiftUI에서 제공하는 View에는 각각이 처리하는 event에 대한 modifier가 존재한다.
TextField에서 사용을 고려해 볼 수 있는 modifier은 대략 다음과 같이 추릴 수 있다.
- onPasteCommand
붙여넣기가 발생했을 때 - onRecieve
값이 입력 될 때 - onSubmit
키보드의 엔터를 눌렀을 때
이외의 modifier를 확인해 보려면 다음 링크가 도움이 된다.
전부 문제를 해결하는 데에 도움이 되지만, 일괄적으로 해결할 수 있는 modifier는 onSubmit과 onRecieve로 다시 추릴 수 있다.
이번에는 onRecieve를 사용해 문제를 해결해 보자.
import SwiftUI
import Combine
struct LabelView: View {
@State private var value = ""
@State private var input = ""
@State private var alertStat = false
var body: some View {
Form {
Text("Form")
TextField("Field_01", text: $value, prompt: Text("Field_01"))
}
GeometryReader { geometry in
VStack {
Section {
TextField("Field_02", text: $input, prompt: Text("Field_02"))
.textFieldStyle(.roundedBorder)
.padding()
.textContentType(.telephoneNumber)
.keyboardType(.numberPad)
.onReceive(Just(input)) { newValue in
let filtered = newValue.filter { "0123456789".contains($0)}
if filtered != newValue {
self.input = filtered
alertStat.toggle()
}
}
.alert("Warning: Input must Digit", isPresented: $alertStat) {
Button(role: .cancel) {
} label: {
Text("Close")
}
}
} header: {
Text("Stack")
}
}
.frame(minHeight: (geometry.size.height) / 2)
}
}
}
핵심은 이 부분이다.
.onReceive(Just(input)) { newValue in
let filtered = newValue.filter { "0123456789".contains($0)}
if filtered != newValue {
self.input = filtered
alertStat.toggle()
}
}
이해하기 위해서는 TextField의 동작 구조를 아는 것이 좋다.
TextField는 현재 'input' State Variable로 Binding 된 상태이고.
이 값이 변경되면 TextField에 다시 반영이 되는 방식으로 돼있다.
이 과정 중 State Variable이 변경되면 SwiftUI가 body를 재작성하게 되고,
이 과정 중 Just가 호출 돼 'input'에 대한 단일 처리를 진행한다.
onRecieve 메서드는 선언된 View를 subscriber로 만들게 되고, 이 경우 publisher는 Just가 된다.
간단하게 설명하면 onRecieve modifier는 선언되는 View를 subscriber로 만들 수 있고,
전달된 Just가 publisher 이므로 파라미터로 전달된 State Varibale를 사용해 반환된 Closure의 결과를
View에 전달하게 되는 구조이다.
시간이 지나면서 여러 방식의 구현이 등장했지만,
현시점까지 꽤나 빈틈없이 '완벽하게' 작동하는 최고의 구현이 아닐까 생각한다.
'학습 노트 > Swift UI Trick' 카테고리의 다른 글
이미지의 평균 색상을 추출하기 (0) | 2023.04.07 |
---|---|
SwiftUI에서 Blur를 사용하는 4가지 방법 (0) | 2022.12.28 |
SwiftUI에서 키보드 숨기기 (0) | 2022.10.15 |