안녕하세요.
지난번 포스트와 같은 키워드인 이번 포스트는 UITextField를 터치할 때, 키보드 올라오도록 하는 방법과 이때 키보드의 실시간 위치를 파악하지 않고도 키보드 위에 버튼을 쉽게 올릴 수 있는 InputAccessoryView. 지정된 텍스트 필드 이외의 영역을 터치할 때 텍스트 필드 이외의 영역이라면 키보드를 내려가게 하는 (resignFirstResponder() ) 방법에 소개하려고 합니다.
1. InputAccessoryView in UITextField
텍스트 필드가 first responder가 될 경우 시스템 키보드가 자동으로 올라옵니다. (예전에 새로 알게 된 개념2 주제로 정리했었는데. 까먹었다는게 사실..ㅠ) 간단 요약하자면, input view는 사용자가 UITextField 또는 UITextView 컴포넌트를 tap할 때, 그 뷰 "becoms the first responder and display its input view.". 즉 텍스트필드, 텍스트 뷰는 first responder로 지정됩니다. 만약 UITextField에서 이벤트 처리를 하지 못한다면? root view에서는 responder chain으로 rootView인 UIWindow에게 event 전달 전에 UIView와 연결된 UIViewController에게 event를 전달합니다.
즉 터치, 입력 event 발생 시 여러 response chain 중 이벤트를 가장 먼저 받는자가 first responder가 되는 것입니다. 그리고 first responder로 텍스트 필드가 지정되면 (커스텀으로 구현 안한 경우)시스템 키보드가 자동으로 올라옵니다.
텍스트 필드는 inputAccessoryView를 옵셔널로 갖고 있습니다.
그리고 text view ( UITextView)가 first responder가 된다면 input accessory view가 화면에 보여집니다. 그런데 신기한 것은 시스템 키보드 바로 위에 함께 올라옵니다.
2. init InputAccessoryView in UITextField
텍스트 필드를 지정했다면, inputAccessoryView를 초기화 할 컨테이너 뷰가 필요합니다.
텍스트 필드 초기화를 했다면 이제 Input accessory view에 컨테이너 뷰 인스턴스를 할당해야 합니다.
func initInputAccessoryView() {
// constant
let height: CGFloat = 75.0
let origin: CGPoint = .init(x: 0, y: 0)
let size: CGSize = .init(
width: UIScreen.main.bounds.width,
height: height)
let containerView = UIView(
frame: CGRect(origin: origin, size: size))
containerView.backgroundColor = .red
textField.inputAccessoryView = containerView
}
크기와 높이를 지정한 containerView를 만들고 textField의 인스턴스 할당을 했다면,
이렇게 텍스트필드를 탭했을 때 시스템 키보드 위에 input accessory view도 딸려옵니다. 이때 신기한 것은 originr값은 뭘 넣든 고정되어있습니다. size 만 적용되서 시스템 키보드 위에 정확히 붙어서 올라옵니다. 이제 이 컨테이너 뷰 내부에 subview를 추가할 때는 spacing등을 넣어 input accessoryView안에 원하는 위치에 Auto layout하면 됩니다.
저는 그래서 버튼 객체를 커스텀해서 버튼 클래스 내부에 layout을 조정하는 함수를 짰습니다. leading, spacing과 centerY, heightAnchor를 지정해줬습니다.
// 버튼 안 함수
func setInputAccessoryViewLayout(
from accessoryView: UIView?,
spacing: UISpacing = .init()
) {
guard let accessoryView = accessoryView else { return }
accessoryView.addSubview(self)
self.accessoryView = accessoryView
self.spacing = spacing
NSLayoutConstraint.activate([
leadingAnchor.constraint(
equalTo: accessoryView.leadingAnchor,
constant: spacing.leading),
trailingAnchor.constraint(
equalTo: accessoryView.trailingAnchor,
constant: -spacing.trailing),
heightAnchor.constraint(equalToConstant: height),
centerYAnchor.constraint(equalTo: accessoryView.centerYAnchor)])
}
다시 텍스트 필드가 있는 뷰로 돌아와서
var accessoryView: UIView? {
textField.inputAccessoryView
}
func setInputAccessoryViewWithButton() {
searchButton.setInputAccessoryViewLayout(
from: accessoryView,
spacing: .init(leading: 24, trailing: 24))
}
이렇게 함수를 써서 컨테이너 뷰 내부에 버튼의 오토 레이아웃을 잡고 view did load시점에 두 함수들을 호출해주면 됩니다. 그런데 키보드에 return을 클릭해도 텍스트 필드가 안 내려갈 수 있습니다. 그럴 땐 UITextFieldDelegate에서 추가 설정을 해야 합니다.
// MARK: - UITextFieldDelegate
extension GroupChatSearch: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
func textFieldDidEndEditing(_ textField: UITextField) {
textField.resignFirstResponder()
}
}
만약 textField 이외의 화면을 터치할 때 내려가지 않는다면? 역시 textField의 firstResponder를 resign하지 않았기 때문입니다.
이 경우 viewController에서 touchesBegan(_:with:) 메서드를 오버라이딩해서 터치했을 당시 텍스트 필드 이외인 경우 textfield의 firstResponder를 resign하면 됩니다.
class ViewController {
...
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else { return }
let touchLocation = touch.location(in: self.view)
if !naviBottomView.searchBarView.frame.contains(touchLocation) {
// textfield.resignResponder() 호출 메서드
naviBottomView.hideKeyboard()
}
}
}
관련 코드는 아래 에서 볼 수 있습니다 :)
https://github.com/SHcommit/NetworkSocketProgrammingWithSwiftBsd/issues/1
References:
https://ios-development.tistory.com/689
https://developer.apple.com/documentation/uikit/uitextview/1618596-inputaccessoryview/
'iOS > Deep dive!!!' 카테고리의 다른 글
[Test] xcode 14.3에서 code coverage 설정하는 방법 (0) | 2023.06.29 |
---|---|
[iOS] 보이지 않는, 사용되지 않는 subview를 superview로부터 제거하는 방법 | Strong reference (2) | 2023.06.09 |
[iOS] UITextField placeholder 색 변경하는 방법 (2) | 2023.05.30 |
[iOS] PrepareForReuse() 의 중요성 | UICollectionView (0) | 2023.05.29 |