돈워리 앱을 개발하는 과정에서 키보드가 활성화되지 않은 화면에서 활성화된 화면으로의 전환을 자연스럽게 만들어야 했다. 기본적인 TextField의 활용법과 활성화된 키보드를 다시 비활성화 하는 방법, 활성화 시 화면을 구성하는 방법에 대해서 알아보자.
목차
struct ContentView: View {
@State private var text = ""
var body: some View {
VStack {
TextField("Placeholder", text: $text)
}
}
}
String 타입이 아닌 경우에는 return을 누르는 등 사용자가 편집을 커밋했을 때 값이 업데이트 된다.
TextField("Placeholder", text: $text)
.keyboardType(.default)
위 코드와 같이 keyboardType을 사용하여 키보드의 종류를 바꿀 수 있다.
TextField("Placeholder", text: $text)
.disableAutocorrection(true)
TextField("Placeholder", text: $text)
.textInputAutocapitalization(.never) // 비활성화
.textInputAutocapitalization(.characters) // 모든 글자
.textInputAutocapitalization(.words) // 단어마다
.textInputAutocapitalization(.sentences) // 문장마다
TextField("Placeholder", text: $text)
.textFieldStyle(.automatic)
.textFieldStyle(.plain)
.textFieldStyle(.roundedBorder)
TextField("Placeholder", text: $text)
.textCase(.none)
.textCase(.lowercase) // 소문자
.textCase(.uppercase) // 대문자
TextField에 숫자를 사용하고 싶다면 다음과 같이 NumberFormatter를 사용한다.
struct ContentView: View {
@State private var number = 0
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
var body: some View {
TextField("Placeholder", value: $number, formatter: formatter)
}
}
우선 버튼 또는 submit을 통해 키보드를 비활성화 시키는 방법을 알아보자.
iOS 14까지는 텍스트 필드의 Focus를 UIKit에 있는 기능으로 관리를 하였지만 iOS 15부터 새롭게 등장한 FocusState를 통해 쉽게 텍스트 필드의 Focus를 관리할 수 있게 되었다.
아래 코드를 추가하여 SwiftUI의 View에서 hideKeyboard를 사용할 수 있다.
#if canImport(UIKit)
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
#endif
struct ContentView: View {
@State private var text = ""
var body: some View {
VStack {
TextField("Placeholder", text: $text)
Button("Done") {
hideKeyboard()
}
}
}
}
iOS 15 부터는 @FocusState, onSubmit, submitLabel을 사용할 수 있게 되었다.
struct ContentView: View {
@State private var text = ""
@FocusState private var isFocused: Bool
var body: some View {
VStack {
TextField("Placeholder", text: $text)
.focused($isFocused)
Button("Submit") {
isFocused = false
}
}
}
}
@FocusState 변수 isFocused를 선언하여 .focused에 전달해주고, isFocused를 false (또는 옵셔널로 선언한다면 nil로도 가능하다.) 로 바꾸어 키보드를 사라지게 할 수 있다.
아래 예시는 여기서 가져온 예시이다.
struct ContentView: View {
enum Field {
case firstName
case lastName
case emailAddress
}
@State private var firstName = ""
@State private var lastName = ""
@State private var emailAddress = ""
@FocusState private var focusedField: Field?
var body: some View {
VStack {
TextField("First name", text: $firstName)
.focused($focusedField, equals: .firstName)
.textContentType(.givenName)
.submitLabel(.next)
TextField("Last name", text: $lastName)
.focused($focusedField, equals: .lastName)
.textContentType(.familyName)
.submitLabel(.next)
TextField("Email address", text: $emailAddress)
.focused($focusedField, equals: .emailAddress)
.textContentType(.emailAddress)
.submitLabel(.done)
}
.onSubmit {
switch focusedField {
case .firstName:
focusedField = .lastName
case .lastName:
focusedField = .emailAddress
default:
print("Done")
}
}
}
}
각 TextField마다 submitLabel을 설정해두고 onSubmit을 통해 submit이 되면 자동으로 다음 TextField로 넘어가도록 하였다. 여기서는 @FocusState 변수를 옵셔널로 선언하여 만약 키보드를 dismiss 하고 싶다면 nil로 초기화 하면 될 것이다.
이번에는 공식 문서에서 가져온 예시를 살펴보자.
struct LoginFormView: View {
enum Field: Hashable {
case username, password
}
@State private var username: String = ""
@State private var password: String = ""
@FocusState private var focusField: Field?
var body: some View {
VStack {
TextField("Username", text: $username)
.focused($focusField, equals: .username)
SecureField("Password", text: $password)
.focused($focusField, equals: .password)
Button("로그인") {
if username.isEmpty {
focusField = .username
} else if password.isEmpty {
focusField = .password
} else {
focusField = nil
handleLogin()
}
}
.onAppear {
focusField = .username
}
}
}
위와 같이 로그인 버튼이 눌렸을 때, 만약 사용자가 입력하지 않은 TextField가 있다면 로그인을 시키지 않고 해당 TextField를 활성화 시킬 수도 있다.
다른 형태의 TextField에 같은 focus binding을 사용하면 안된다.
onSubmit은 submit이 되었을 때 클로저를 실행하게 된다. 위 예시처럼 @FocusState 변수를 다음 TextField의 값으로 바꾸어가며 submit을 하였을 때 자동으로 다음 TextField를 활성화 시킬 수 있다.
submitLabel을 사용하면 키보드의 submit 버튼의 종류를 설정할 수 있다. submitLabel들에는 continue, done, join, next 등이 있다. 자세한 종류에 대해서는 공식 문서를 참조하자.
키보드 툴바에 커스텀 버튼을 추가하여 submit을 할 수 있다.
TextField("Placeholder", value: $number, formatter: formatter)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button {
// submit action
} label: {
Text("Done")
}
}
}
위와 같이 toolbar의 placement를 keyboard로 설정하고, Spacer와 Button을 추가하여 submit 버튼을 만들 수 있다.
이번에는 키보드가 활성화 되었을 때, 화면을 클릭하여 키보드를 비활성화 시키는 방법에 대해 알아보자.
extension UIApplication {
func hideKeyboard() {
guard let window = windows.first else { return }
let tapRecognizer = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))
tapRecognizer.cancelsTouchesInView = false
tapRecognizer.delegate = self
window.addGestureRecognizer(tapRecognizer)
}
}
extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
위와 같이 extension을 만들어주고 onAppear에 전달하면 된다.
struct ContentView: View {
@State private var text = ""
var body: some View {
VStack {
TextField("Placeholder", text: $text)
}
.onAppear {
UIApplication.shared.hideKeyboard()
}
}
}
struct ContentView: View {
@State private var text = ""
@FocusState private var isFocused: Bool
var body: some View {
ZStack {
Color.white
TextField("placehodler", text: $text)
.focused($isFocused)
}
.onTapGesture {
isFocused = false
}
}
}
위와 같이 ZStack 안에 모든 영역을 클릭할 수 있게 만든 후 onTapGesture를 사용할 수 있다.
또는 아래와 같이 Background를 직접 만들어서 사용할 수도 있다.
struct Background<Content: View>: View {
private var content: Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content()
}
var body: some View {
Color.white
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.overlay(content)
}
}
struct ContentView : View {
@State private var text = ""
@FocusState private var isFocused: Bool
var body: some View {
Background {
TextField("Placeholder", text: $text)
.focused($isFocused)
}.onTapGesture {
isFocused = false
}
}
}
물론 @FocusState를 사용하지 않고 UIKit을 이용할 수도 있다.
< 추가 예정 >
Apple Developer Documentation - FocusState
Apple Developer Documentation - SubmitLabel
Hacking with Swift - How to dismiss the keyboard for a TextField
Hacking with Swift - Hiding the keyboard
Stack Overflow - How to hide when using SwiftUI
SwiftUI: 탭하여 키보드 숨기는방법 - hideKeyboard()