앨런 slack에서 어떤분이 카카오톡 채팅창을 공부할겸 만들어보셨다고 해서 나도 한번 만들어봐야겠다 싶어서 만들어보기 위해서 머릿속으로 어떻게 만들까 떠올려보다가 처음에 들었던 문제점하나는 "말풍선"이었다
우선 말풍선같은경우에는 가로로도 세로로도 text에 따라서 늘어나야하는데 문제는 코너에 둥근부분은 그대로 둔상태에서 이미지 모양이 바뀌어야했다
처음엔 이렇게 생각했다 "그냥 textLabel에 background컬러에다가 cornerRadius를 줘야지" 뭐...이렇게 해도 될거같긴한데 문제는 카카오톡이나 imessage를 보면 채팅창 말풍선에 꼬리가 있다. 그냥 네모 모양이면 그렇게 해보겠는데 말풍선모양인걸보면 이미지가 분명한거다.
그래서 아니 대체 어떻게 이미지를 반응형으로 늘리지? 라는 고민을 하게되었고 이것저것 구글링을 하다가 Image Stretching이라는걸 알게되었다 이건 한마디로 말하면 내가 넣을 이미지를 어떻게 늘릴지를 정해주는거다
우선 이렇게 내가 넣을말풍선을 image asset에다가 넣어주고
점 세개가 있는 부분을 누르고 show slicing을 눌러준다
그러면 이런식으로 start slicing이라는 버튼이 이미지 위에 나타나게 되는데 이걸클릭하면
이런식으로 좌우로늘릴지 좌우위아래로 늘릴지 위아래로 늘릴지 결정할수있는 버튼이 보인다 내가 하려는 연습의 경우에는 말풍선이 위아래 좌우로 늘어나야하기 때문에 가운데 버튼을 눌러준다
그러면 이렇게 영역을 지정할 수 있는 모양으로 바뀌게 되는데
중요한건 "어두운 부분" = "늘어나는 부분"이다
저기 보이는 점선을 위아래로 좌우로 움직여서 늘어날 부분을 정할 수 있다
나는 이걸 처음써봐서 저렇게 처음에 하고 view를 그려봤는데 좌우로만 늘어났다
이런식으로 어두운부분을 위아래로도 설정을 해줘야한다 저렇게 점선 두개가 겹쳐있는 부분에서 점선하나를 드래그해서 영역을지정해주면된다 그리고 각각의 모서리들은 늘어나면 안되기때문에 건드리면안된다
그리고 중요한부분이
이부분에서 Slicing에서 Center이 Tiles인데 저걸 눌러서 Stretches로 바꿔줘야한다 그리고 그위에 Slices에 숫자를 넣을수있는 부분이 있는데 거기서 숫자를 조정해서 세부적으로 균일하게 조정해줄수도있다
그렇게 하면 이렇게 text에 따라서 코너부분을 제외하고 모양이 바뀌는걸 확인할 수 있다
이건 사실 아직도 잘 모르겠고 스스로 정리를한 부분이라 틀린부분일 수 있다. 나같은 경우 cell에 데이터를 전달할때 didSet을 사용한다
var chat: ChatModel? {
didSet {
guard let data = chat else { return }
content.text = data.content
dataLabel.text = data.date
bubbleImage.image = UIImage(named: data.send ? ImageConstant.bubbleImageYellow : ImageConstant.bubbleImageGray)
data.send ? setMySend() : setOtherSend()
}
}
이런식으로? 근데 여기서는 뭐냐면 채팅데이터가 있는데 거기에 send라는 속성이 Bool로 선언이되어있고 true면 내가보낸거니까 왼쪽에 뜨고 false면 상대방이 보낸거라 오른쪽에 뜨게 만들고 싶었다(image 색깔도 다르고)그래서 변수를 사용하고 싶은데 init함수에서 print를 찍어보면 nil이 뜬다...
그래서 저걸 어떻게해야하나 하다가 didSet안에다가 함수를 넣고 변수에 값이 할당될때마다
private func setMySend() {
content.snp.remakeConstraints { make in
make.centerY.equalToSuperview()
make.trailing.equalToSuperview().inset(30)
make.leading.greaterThanOrEqualToSuperview().inset(200)
make.top.bottom.equalToSuperview().inset(30)
}
}
private func setOtherSend() {
content.snp.remakeConstraints { make in
make.centerY.equalToSuperview()
make.leading.equalToSuperview().inset(30)
make.trailing.lessThanOrEqualToSuperview().inset(200)
make.top.bottom.equalToSuperview().inset(30)
}
}
이런식으로 layout을 잡도록했다... 원래는 makeConstraints를 사용했는데 데이터를 append하고 reload를 하니까 view가 다 깨져서 저게 왜저럴까 생각을 하다가 데이터가 들어가면 layout을 업데이트 해주는거니까 remake를 써야하지않을까라는생각에 써보니까 다행히 내가 생각한대로 동작을 했다. 저런식으로 데이터를 받게되면 cell이 init이 된후에 데이터가 들어가기때문에 당연히 init시점에서는 data에 아무것도 안들어온상태인걸 알 수 있다 그래서 저런 상황에서는 데이터를 불러오는 시점에 layout을 업데이트 해줘야한다
카카오톡을 보면 내가 카톡을 보낼때마다 scroll이 가장최신 메세지가 아래로 내려오게 구현이 되어있는걸 알수있다 맨위로 올라가있어도 내가 카톡을 보내면 가장 아래부분으로 내려온다.
이부분이 없으니까 메세지를 추가할때마다 메세지가 추가되는건지도 모르겠고 매번 스크롤을 내려야해서 불편한점이 이만저만이 아니었다 그래서 구글링을 해보니 indexPath로 scroll위치를 옮겨주는 함수가 있었다
chatTableView.scrollToRow(at: IndexPath(row: viewModel.getData().count-1, section: 0), at: .bottom, animated: true)
나같은 경우엔 section이 없으니까 0번 section의 데이터중에 가장마지막 indexrow로 scroll을 위아래로 옮기게 했다 count에다가 1을 뺀 이유는 index이기때문에다 데이터가 3개면 count는 3개지만 index는 012해서 마지막게 2이기 때문이다
그리고 중요한부분은 reloadData를 하는경우 reloadData이후에 호출을해아한다는점이다. 그렇게 안하면 앱이 터진다...
이런식으로 구현하면된다
extension ViewController {
@objc func appendMyChatButtonTapped() {
viewModel.addData(ChatModel.basicChat)
scrollToButton()
}
@objc func appendOtherChatButtonTapped() {
viewModel.addData(.init(content: "상대방님의 메세지입니다", date: "22년2월29일", send: false))
scrollToButton()
}
private func scrollToButton() {
chatTableView.reloadData()
chatTableView.scrollToRow(at: IndexPath(row: viewModel.getData().count-1, section: 0), at: .bottom, animated: true)
}
}