📱 iOS 플젝 개발일지

[iOS/Swift] 키보드로 인해 채팅창(UITableView)이 가려지는 불편함 해결

dev_zoe 2023. 4. 21. 02:15
반응형

🚨 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://seizze.github.io/2019/11/17/iOS%EC%97%90%EC%84%9C-%ED%82%A4%EB%B3%B4%EB%93%9C%EC%97%90-%EB%8F%99%EC%A0%81%EC%9D%B8-%EC%8A%A4%ED%81%AC%EB%A1%A4%EB%B7%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0.html

 

iOS에서 키보드에 동적인 스크롤뷰 만들기

Breadcrumbs

seizze.github.io

https://www.hackingwithswift.com/example-code/uikit/how-to-adjust-a-uiscrollview-to-fit-the-keyboard

 

How to adjust a UIScrollView to fit the keyboard - free Swift 5.4 example code and tips

Was this page useful? Let us know! 1 2 3 4 5

www.hackingwithswift.com

- 이 레퍼런스들을 보니까 tableView의 contentInset을 계산해서 키보드가 나타날 때 / 사라질 때 나누어서 지정하는 것으로 보고, contentInset에 대해 알아보고 크기를 계산해보았다.

 

3) UITableView의 ContentInset 적용 및 마지막 행으로 스크롤되도록 적용

https://developer.apple.com/documentation/uikit/uiscrollview/1619406-contentinset

 

contentInset | Apple Developer Documentation

The custom distance that the content view is inset from the safe area or scroll view edges.

developer.apple.com

- 공식문서를 참고해보면 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좌표로 스크롤하는지를 지정하는것같다.

반응형