이런 구닥다리 UI에서 나름 세련된 UI로 변경을 시도해봤다.
변경 적용사항
- TextView에 입력한 내용은 즉각적으로 UI에 반영
- 통신을 하고 결과를 받기까지 user에게 통신중이라는 것을 알려줄 수 있는 indicator구현
1. input값을 먼저 반영하기
방법은 여러가지 겠지만, viewModel에서 callback함수를 통해 viewcontroller에게 상태를 전달하는 방식으로 구현했다. 그리고 DispatchQeue.main{ }로직을 전부 제거했다.
- 콜백 함수 정의 및 기존 메서드 수정
// viewModel
var onMessagesUpdated: (() -> Void)?
//기존 메서드
func processUserMessage(message content: String, model: GPTModel) {
let userMessage = RequestMessageModel(role: .user, content: content)
messageRepository.addMessage(userMessage)
apiService.sendRequestToOpenAI(messageRepository.getMessages(), model: model, APIkey: APIKeyManager.openAIAPIKey) { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let receivedMessages):
receivedMessages.forEach { responseMessage in
self?.messageRepository.addMessage(responseMessage)
}
self?.onMessagesUpdated?()
case .failure(let error):
self?.onError?(error.localizedDescription)
}
}
}
}
//리펙토링
func processUserMessage(message content: String, model: GPTModel, completion: @escaping () -> Void) {
let userMessage = RequestMessageModel(role: .user, content: content)
messageRepository.addMessage(userMessage)
self.onMessagesUpdated?() //통신 전 UI업데이트를 위해 콜백함수 호출
apiService.sendRequestToOpenAI(messageRepository.getMessages(),
model: model,
APIkey: APIKeyManager.openAIAPIKey) { [weak self] result in
switch result {
case .success(let receivedMessages):
receivedMessages.forEach { responseMessage in
self?.messageRepository.addMessage(responseMessage)
}
self?.onMessagesUpdated?()
case .failure(let error):
self?.onError?(error.localizedDescription)
}
completion()
}
}
- ViewController에서 상태에 따른 UI구현
private func bindViewModel() {
viewModel.onMessagesUpdated = { [weak self] in
DispatchQueue.main.async {
self?.chatMessageCollectionView.reloadData()
self?.scrollToBottom()
}
}
DispatchQueue.main.async {
self.viewModel.onError = { [weak self] errorMessage in
DispatchQueue.main.async {
self?.configureErrorAlert()
}
}
}
}
2. indicator 구현
lazy var로 만들어 필요하기 전엔 메모리에 올라가지 않도록 구현
- indicator를 정의해주고 시작하는 메서드와 멈추고 superView에서 제거해주는 메서드를 만들어준다.
//viewcontroller
private lazy var openAIAPIResponseIndicatorView: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .medium)
indicator.center = self.view.center
return indicator
}()
// MARK: - Indicator
private func showOpenAIAPIResponseIndicator() {
self.view.addSubview(openAIAPIResponseIndicatorView)
openAIAPIResponseIndicatorView.startAnimating()
}
private func hideOpenAIAPIResponseIndicator() {
openAIAPIResponseIndicatorView.stopAnimating()
openAIAPIResponseIndicatorView.removeFromSuperview()
}
- 보여줄곳에 메서드 실행
@objc private func doneButtonTapped(_ sender: UIButton) {
guard let userInput = detailChatStackView.userInputTextView.text, !userInput.isEmpty else {
return
}
detailChatStackView.userInputTextView.text = ""
DispatchQueue.main.async {
self.showOpenAIAPIResponseIndicator()
}
viewModel.processUserMessage(message: userInput, model: .gpt3Turbo) { [weak self] in
DispatchQueue.main.async {
self?.hideOpenAIAPIResponseIndicator()
}
}
}
완성 😊
전체 코드는 아래 링크에서 확인 가능합니다.
'🍎swift' 카테고리의 다른 글
UICollectionViewDelegateFlowLayout 활용 Cell크기 동적 할당 (1) | 2024.04.15 |
---|---|
Swift 배열, 동시성 문제 (0) | 2024.04.04 |
URLRequestBuilder만들기 .. feat 리팩토링 (0) | 2024.04.02 |
TLI - Concurrency (0) | 2024.01.17 |
[Swift] Stroyboard Navigation Controller를 활용한 화면이동 (1) | 2023.12.21 |