[SwiftUI] TextField, Dismissing keyboard

Charlie·2022년 6월 12일
1

SwiftUI

목록 보기
1/4

돈워리 앱을 개발하는 과정에서 키보드가 활성화되지 않은 화면에서 활성화된 화면으로의 전환을 자연스럽게 만들어야 했다. 기본적인 TextField의 활용법과 활성화된 키보드를 다시 비활성화 하는 방법, 활성화 시 화면을 구성하는 방법에 대해서 알아보자.

목차

  1. TextField
  2. Dismiss Keyboard (1)
  3. Dismiss Keyboard (2)
  4. 활성화 시 화면 재구성하기


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을 사용하여 키보드의 종류를 바꿀 수 있다.

  • default : 현재 입력 방법에 대한 기본 키보드
  • alphabet : 알파벳 입력을 위한 키보드
  • asciiCapable : 표준 ASCII 문자를 표시하는 키보드
  • asciiCapableNumberPad : ASCII 숫자만 출력하는 숫자 키보드
  • decimalPad : 숫자와 소수점이 있는 키보드
  • numbersAndPunctuation : 숫자와 구두점 키보드
  • numberPad : PIN 입력을 위한 숫자 키보드
  • phonePad : 전화번호 입력을 위한 숫자 키보드
  • namePhonePad : 사람의 이름이나 전화번호 입력을 위한 키보드
  • emailAddress : 이메일 주소 입력을 위한 키보드
  • URL : URL 입력 키포드
  • webSearch : 웹 검색어 및 URL 입력을 위한 키보드
  • twitter : '@', '#' 문자를 쉽게 사용할 수 있는 Twitter용 키보드

기타 Modifiers

자동 수정

TextField("Placeholder", text: $text)
	.disableAutocorrection(true)

자동 대문자화

TextField("Placeholder", text: $text)
	.textInputAutocapitalization(.never)		// 비활성화
	.textInputAutocapitalization(.characters)	// 모든 글자
	.textInputAutocapitalization(.words)		// 단어마다
    .textInputAutocapitalization(.sentences)	// 문장마다

TextFieldStyle

TextField("Placeholder", text: $text)
	.textFieldStyle(.automatic)
	.textFieldStyle(.plain)
	.textFieldStyle(.roundedBorder)

대문자, 소문자

 TextField("Placeholder", text: $text)
	.textCase(.none)
	.textCase(.lowercase)	// 소문자
	.textCase(.uppercase)	// 대문자

TextField 에 숫자 사용하기

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)
    }
}


Dismiss Keyboard (1)

우선 버튼 또는 submit을 통해 키보드를 비활성화 시키는 방법을 알아보자.

iOS 14까지는 텍스트 필드의 Focus를 UIKit에 있는 기능으로 관리를 하였지만 iOS 15부터 새롭게 등장한 FocusState를 통해 쉽게 텍스트 필드의 Focus를 관리할 수 있게 되었다.

UIKit 사용 (iOS 15 이전)

아래 코드를 추가하여 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()
            }
        }
    }
}

@FocusState 사용 (iOS 15 이후)

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로도 가능하다.) 로 바꾸어 키보드를 사라지게 할 수 있다.

자동으로 다음 TextField 활성화

아래 예시는 여기서 가져온 예시이다.

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로 초기화 하면 될 것이다.

입력하지 않은 TextField 활성화

이번에는 공식 문서에서 가져온 예시를 살펴보자.

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을 사용하면 안된다.


Submit

onSubmit

onSubmit은 submit이 되었을 때 클로저를 실행하게 된다. 위 예시처럼 @FocusState 변수를 다음 TextField의 값으로 바꾸어가며 submit을 하였을 때 자동으로 다음 TextField를 활성화 시킬 수 있다.

submitLabel

submitLabel을 사용하면 키보드의 submit 버튼의 종류를 설정할 수 있다. submitLabel들에는 continue, done, join, next 등이 있다. 자세한 종류에 대해서는 공식 문서를 참조하자.

키보드 툴바에 버튼을 추가하여 submit 하기

키보드 툴바에 커스텀 버튼을 추가하여 submit을 할 수 있다.

TextField("Placeholder", value: $number, formatter: formatter)
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    Spacer()
                    Button {
                        // submit action
                    } label: {
                        Text("Done")
                    }
                    
                }
            }

위와 같이 toolbar의 placement를 keyboard로 설정하고, Spacer와 Button을 추가하여 submit 버튼을 만들 수 있다.



Dismiss Keyboard (2)

이번에는 키보드가 활성화 되었을 때, 화면을 클릭하여 키보드를 비활성화 시키는 방법에 대해 알아보자.

UIKit 사용

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()
        }
    }
}

배경을 만들고 onTapGesture 사용

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을 이용할 수도 있다.



활성화 시 화면 재구성하기

< 추가 예정 >



Reference

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()

profile
Hello

0개의 댓글