UIKit을 활용한 채팅 UI... 그런데 이제 SnapKit을 곁들인..
준비물: UIKit, SnapKit, UITableViewCell, 채팅에 대한 정보(송신자, 시간, 내용 등)
구성요소: messageBubble, messageLabel, imageContainerStackView, taimeLabel
목표
- 메시지를 보낸 사람이 본인 / 상대방 에 따라 메시지 버블 색삭 및 위치 다르게 설정
- 텍스트와 이미지를 포함한 메시지 지원
0. 뷰 계층 구성은 SKIP~
1. 메시지 버블 레이아웃
private var leadingConstraint: Constraint?
private var trailingConstraint: Constraint?
messageBubble.snp.makeConstraints { make in
make.top.equalToSuperview().inset(8)
make.width.lessThanOrEqualToSuperview().multipliedBy(0.6)
make.bottom.equalToSuperview().inset(8).priority(.low)
leadingConstraint = make.leading.equalToSuperview().inset(10).constraint
trailingConstraint = make.trailing.equalToSuperview().inset(10).constraint
}
주요 동작
1. 너비 제한: 메시지 버블의 너비는 화면의 최대 60%로 제한하여 가독성 확보
2. 하단 여백: 8pt의 여백을 주되, 다른 Constraints와 충돌 시 우선순위를 낮춰 Constraints 충돌 시, 무시하도록 구현
3. 위치 제어: 송수신자에 따라 leadingConstraint 또는 trailingConstraint를 활성화하여 위치를 동적으로 변경
2. 메시지 내용에 따른 UI 업데이트
a. 텍스트 처리
if let content = message.lastChat.first?.content, !content.isEmpty {
messageLabel.text = content
timeLabel.text = Date.formatDate(from: message.lastChat[0].createdAt)
messageLabel.isHidden = false
} else {
messageLabel.text = nil
messageLabel.isHidden = true
}
- 메시지가 존재하면 텍스트를 표시, 그렇지 않으면 숨김 처리
- 시간은 별도의 메서드를 사용해 텍스트로 변환
- 이미지 처리는 다른 포스트에서~~진행해보겠습니다!!
b. 버블 숨김 처리
messageBubble.isHidden = messageLabel.isHidden && imageContainerStackView.isHidden
- 텍스트와 이미지가 모두 없을 경우, 메시지 버블을 숨김 처리
c. 메시지 방향 설정
if message.isSentByUser {
messageBubble.backgroundColor = AppColorSet.skyblue
trailingConstraint?.activate()
leadingConstraint?.deactivate()
} else {
messageBubble.backgroundColor = AppColorSet.keyColor
leadingConstraint?.activate()
trailingConstraint?.deactivate()
}
- 송신자 / 수신자에 따라 버블의 색상과 위치 동적으로 변경
- 송신자 메시지는 오른쪽 정렬(파란색), 수신자 메시지는 왼쪽 정렬(흰색)
사실 여기까지는 그럭저럭 할만 했는데요...
문제는 바로 이미지 개수에 따라 UI를 다르게 설정해줘야하는 것이죠....😂😂😂
3. 이미지 개수에 따른 동적 이미지 배치
우선 기본적인 베이스는 당연히 이미지가 존재할 때입니다!
a. 행(Row) 생성
let rowCount = Int(ceil(Double(files.count) / 3.0))
- 총 행의 개수 계산, 한 행에 최대 3개의 이미지를 배치
ex) 이미지가 5개 > 2행 생성
b. 파일 개수만큼 행 내의 이미지뷰 생성 & 레이아웃 업데이트
for rowIndex in 0..<rowCount {
let horizontalStackView = UIStackView()
horizontalStackView.axis = .horizontal
horizontalStackView.spacing = 8
horizontalStackView.distribution = .fillEqually
let startIndex = rowIndex * 3
let endIndex = min(startIndex + 3, files.count)
for fileIndex in startIndex..<endIndex {
let imageView = createImageView(urlString: files[fileIndex], processor: processor)
horizontalStackView.addArrangedSubview(imageView)
}
imageContainerStackView.addArrangedSubview(horizontalStackView)
messageLabel.snp.remakeConstraints { make in
make.top.equalTo(messageBubble.snp.top).inset(8)
make.leading.trailing.equalToSuperview().inset(12)
make.bottom.equalTo(imageContainerStackView.snp.top).offset(-8)
}
}
- 가로 스택뷰를 생성하여 행 단위로 이미지를 배치.
- 이미지는 크기 최적화(Downsampling) 후 스택뷰에 추가
- messageLabel의 하단을 imageContainerStackView의 상단에 연결
- 이미지와 텍스트 사이에 8pt의 간격을 유지
결과는..?
요런 느낌으로 구현이 된답니다..!!
전체 코드
private var imageContainerStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.alignment = .leading
return stackView
}()
// 이미지 설정
if let files = message.lastChat.first?.files, !files.isEmpty {
imageContainerStackView.isHidden = false
imageContainerStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
let targetSize = CGSize(width: 70, height: 70)
let processor = DownsamplingImageProcessor(size: targetSize)
let rowCount = Int(ceil(Double(files.count) / 3.0)) // 3개씩 행 생성
for rowIndex in 0..<rowCount {
let horizontalStackView = UIStackView()
horizontalStackView.axis = .horizontal
horizontalStackView.spacing = 8
horizontalStackView.distribution = .fillEqually
let startIndex = rowIndex * 3
let endIndex = min(startIndex + 3, files.count)
for fileIndex in startIndex..<endIndex {
let imageView = createImageView(urlString: files[fileIndex], processor: processor)
horizontalStackView.addArrangedSubview(imageView)
}
imageContainerStackView.addArrangedSubview(horizontalStackView)
messageLabel.snp.remakeConstraints { make in
make.top.equalTo(messageBubble.snp.top).inset(8)
make.leading.trailing.equalToSuperview().inset(12)
make.bottom.equalTo(imageContainerStackView.snp.top).offset(-8)
}
}
} else {
messageLabel.snp.remakeConstraints { make in
make.top.equalTo(messageBubble.snp.top).inset(8)
make.leading.trailing.equalToSuperview().inset(12)
make.bottom.equalToSuperview().inset(8)
}
imageContainerStackView.isHidden = true
}
사실 뭔가 좀 더 간단하고 기깔나게 해보고 싶었는데...
셀을 하나만 쓰고싶은 욕심도 있었고... 해서 그런지
결국 if, for문 덕지덕지가 되어버렸네요.. 허허
+
문제점: 이미지 5개일 때의 UI 설정을 추가해줘야 할 것 같습니다.
이렇게 나와버리네요 ㅎㅎ
이쁘지 않아 살짝 서운합니다.
나중에 이 부분도 한 번 고쳐보도록 하죠!
.
.
.
.
'Swift' 카테고리의 다른 글
[iOS] About Kingfisher (1) | 2024.11.27 |
---|---|
[iOS] About Local Notification (0) | 2024.11.13 |
[iOS] About 동시성 (0) | 2024.11.10 |
[iOS] About WidgetKit - 2(App Group) (0) | 2024.11.02 |
[iOS] About TDD (1) | 2024.10.30 |