🚨 Issue
돌보고 있던 사이드 프로젝트에서 키보드로 인해 특정 영역이 보이지 않아 불편한 점이 보여서 이를 해결해보고싶었다.
아래 내용이 보이지가 않는다 🥲 사용자 입장에서 매우 불편.
💡 Idea
다른분들은 어떻게 해결했는지 레퍼런스를 찾아봤다.
보통 키보드가 등장할 때 뷰의 y 좌표를 위로 조정하고, 사라질 때 다시 아래로 내리는 방식으로 하는것같았다.
이 방식은 원래 텍스트뷰를 위로 올릴 때 사용하던 방식이기는 했는데, 아래 내용까지 보일 수 있도록 다시 적용해보기로 했다.
✅ 채팅 테이블뷰 아래 내용까지 보일 수 있도록 테이블뷰가 위쪽으로 스크롤 되도록 해결 과정
1) 처음 테이블뷰도 마찬가지로 frame의 y를 뺐다가 더하는 쪽으로 했는데 아래와 같이 처참한 결과 발생..
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc
func keyboardWillShow(_ notification: Notification) { // keyboardFrameEndUserInfoKey : 키보드가 차지하는 frame의 CGRect값 반환
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
chatBackGround.frame.origin.y -= (keyboardHeight - AppContext.shared.safeAreaInsets.bottom)
messageChatTableView.frame.origin.y -= (keyboardHeight - AppContext.shared.safeAreaInsets.bottom)
}
}
@objc
func keyboardWillHide(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
chatBackGround.frame.origin.y += (keyboardHeight - AppContext.shared.safeAreaInsets.bottom)
messageChatTableView.frame.origin.y += (keyboardHeight - AppContext.shared.safeAreaInsets.bottom)
}
}
- 이 코드로 인해 저 처참한 결과가 발생했다.
- 내가 바라는 결과는 메시지의 마지막 내용이 입력창 위로 자연스럽게 스크롤되면서 이와동시에 카카오톡처럼 맨 처음 메시지 끝까지 수동으로 스크롤할 수 있는 형태였다. 다만 이 코드는 채팅 리스트의 y값 자체를 위로 올려버리는거라서 저렇게 상단바 위로까지 올라가고 + 메시지가 위로 스크롤할정도로 많지가 않아서 올라갈 필요도 없었다.
- 일단 기존 카카오톡과 UI가 유사하니, 카카오톡 채팅방은 어떻게 만들어졌나 살펴보니, 다음과 같았다 (발그림 주의..)
- 채팅 입력 시 키보드가 올라가면, 키보드랑 채팅 리스트가 함께 올라가는데, 이때 위 상단바는 채팅 리스트 위에 떠있는 형식이고 항상 위에 고정된 형태이다.
2) 위 카카오톡 사진처럼 상단바 부분을 self.view.bringSubviewToFront() 메소드를 사용해서 맨 앞단에 위치하게 해서 키보드가 나타날 때 이상하게 떠다녀 보이지 않도록 함
- 저렇게 이상하게 떠돌아다니지는 않게는 됐고 입력창 위까지 위치시키는데까지는 성공했으나, 메시지 처음 내용까지 스크롤 시키는 것이 불가능했다.
- 더 정확히는 스크롤은 끝까지 가능하지만, tableview의 y값을 감소시켰기 때문에 상단바가 위의 값을 가리게 되어서 끝까지 스크롤되지 않는것처럼 보이는것이다.
- 여기서 고민하게 된것은 "y값을 조정하지 않고도 키보드 + 입력창 크기에 맞춰서 마지막 데이터까지 보여주고, 위로 끝까지 스크롤할 수는 없을까?" 하고 여러가지 레퍼런스를 찾아보던중 다음과 같은 레퍼런스를 찾게됐다.
https://www.hackingwithswift.com/example-code/uikit/how-to-adjust-a-uiscrollview-to-fit-the-keyboard
- 이 레퍼런스들을 보니까 tableView의 contentInset을 계산해서 키보드가 나타날 때 / 사라질 때 나누어서 지정하는 것으로 보고, contentInset에 대해 알아보고 크기를 계산해보았다.
3) UITableView의 ContentInset 적용 및 마지막 행으로 스크롤되도록 적용
https://developer.apple.com/documentation/uikit/uiscrollview/1619406-contentinset
- 공식문서를 참고해보면 contentInset은 "컨텐트 뷰로부터 얼마나 안쪽으로 공백이 있는가"에 대한 내용이다.
- 따라서 키보드가 나타날 때, 테이블뷰는 이미 입력창의 top에 제약이 걸려있으므로 keyboard height만 inset을 주고, 마지막 리스트로 스크롤 되게 했다. (scrollToRow -> 특정 row로 스크롤되게 하는 메소드)
@objc
func keyboardWillShow(_ notification: Notification) { // keyboardFrameEndUserInfoKey : 키보드가 차지하는 frame의 CGRect값 반환
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
chatBackGround.frame.origin.y -= keyboardHeight
messageContentsTableView.contentInset.bottom = keyboardHeight
messageContentsTableView.scrollToRow(at: IndexPath(row: messageContentsTableView.numberOfRows(inSection: 0) - 1, section: 0), at: .bottom, animated: true) // 맨 마지막 내용으로 이동하도록
}
}
@objc
func keyboardWillHide(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
chatBackGround.frame.origin.y += keyboardHeight
messageContentsTableView.contentInset.bottom = 0
messageContentsTableView.scrollToRow(at: IndexPath(row: messageContentsTableView.numberOfRows(inSection: 0) - 1, section: 0), at: .bottom, animated: true) // 맨 마지막 내용으로 이동하도록
}
}
frame.origin.y와 contentInset, scrollToRow에 대해 알 수 있었던 좋은 경험이었다.
*contentInset vs contentOffset
- 차이점이 궁금해서 찾아보니 contentInset은 컨텐츠 안에 상하좌우 여백을 주는것이고 contentOffset은 어떤 x, y좌표로 스크롤하는지를 지정하는것같다.