본문 바로가기
Swift

[iOS] About 채팅 UI

by dsungc 2024. 11. 20.

 

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 설정을 추가해줘야 할 것 같습니다. 

이미지 5장일 때

이렇게 나와버리네요 ㅎㅎ

이쁘지 않아 살짝 서운합니다.

 

나중에 이 부분도 한 번 고쳐보도록 하죠!

 

.

.

.

.

 

 

'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