IOS 앱 개발

바디와이짐 커뮤니티 앱 개발 - 채팅방 기능

vitamin3000 2024. 9. 21. 19:20

지난번에 구현한 게시글에 이은 채팅방 기능 구현이다.

 

1. 서버에서 "공개" 채팅방을 불러온다.

2. 검색 기능 구현

3. 공개/비공개 채팅방에 따른 구별 기능

4. 채팅방 내부에서 텍스트 또는 이미지를 보낼 수 있음.

이때 내가 보낸건 오른쪽에 다른이가 보낸 건 왼쪽에 위치하도록 하였음

닉네임, 내용, 보낸 시간이 출력됨

 

 

작성한 코드

 

채팅방을 불러오는 코드

//
//  ChatMainViewController.swift
//  body_gym
//
//  Created by 차재식 on 1/26/24.
//

import Foundation
import UIKit
import FirebaseDatabase
import FirebaseAuth


struct ChatRoom {
    let id: String
    let author: String
    let name: String
    let password: String
    let timestamp: Double // 채팅방이 생성된 시간의 타임스탬프
}

class ChatRoomNavigator {
    weak var navigationController: UINavigationController?

    init(navigationController: UINavigationController?) {
        self.navigationController = navigationController
    }
    
    func navigateToChatRoom(_ chatRoom: ChatRoom) {
        let chatRoomViewController = ChatRoomViewController()
        chatRoomViewController.chatRoom = chatRoom
        navigationController?.pushViewController(chatRoomViewController, animated: true)
    }
}




class ChatMainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    var originalChatRooms: [ChatRoom] = []
    var chatRooms: [ChatRoom] = []
    var tableView: UITableView!
    var chatRoomNavigator: ChatRoomNavigator?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 테이블 뷰 생성 및 셀 등록
        tableView = UITableView()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ChatRoomCell")
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.contentInsetAdjustmentBehavior = .never // 이 줄 추가
        view.addSubview(tableView)

        let topBar = UIView()
        topBar.backgroundColor = .white
        topBar.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(topBar)

        let titleLabel = UILabel()
        titleLabel.text = "채팅방"
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(titleLabel)

        let searchButton = UIButton()
        searchButton.setTitle("검색", for: .normal)
        searchButton.setTitleColor(.black, for: .normal) // 글씨색을 검은색으로 설정
        searchButton.addTarget(self, action: #selector(searchButtonTapped), for: .touchUpInside)
        searchButton.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(searchButton)
        
        let refreshButton = UIButton()
        refreshButton.setTitle("새로고침", for: .normal)
        refreshButton.setTitleColor(.black, for: .normal) // 글씨색을 검은색으로 설정
        refreshButton.addTarget(self, action: #selector(refreshButtonTapped), for: .touchUpInside)
        refreshButton.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(refreshButton)
        refreshButton.isHidden = true

        let addButton = UIButton()
        addButton.setTitle("추가", for: .normal)
        addButton.setTitleColor(.black, for: .normal) // 글씨색을 검은색으로 설정
        addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
        addButton.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(addButton)
        
        let separator = UIView()
        separator.backgroundColor = .black // 실선의 색상을 검은색으로 설정
        separator.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(separator)
        
        // ChatRoomNavigator 인스턴스 초기화
        chatRoomNavigator = ChatRoomNavigator(navigationController: self.navigationController)
    
        NSLayoutConstraint.activate([
            topBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            topBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            topBar.heightAnchor.constraint(equalToConstant: 50),

            separator.topAnchor.constraint(equalTo: topBar.bottomAnchor),
            separator.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            separator.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            separator.heightAnchor.constraint(equalToConstant: 0.3), // 실선의 높이를 1로 설정

            tableView.topAnchor.constraint(equalTo: topBar.bottomAnchor), // separator.bottomAnchor를 topBar.bottomAnchor로 변경
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            titleLabel.centerXAnchor.constraint(equalTo: topBar.centerXAnchor),
            titleLabel.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),

            searchButton.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 10),
            searchButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
            
            refreshButton.leadingAnchor.constraint(equalTo: searchButton.trailingAnchor, constant: 10),
            refreshButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),

            addButton.trailingAnchor.constraint(equalTo: topBar.trailingAnchor, constant: -10),
            addButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
        ])
        fetchChatRooms()
    }
    
    // 새로고침 버튼이 눌렸을 때 호출될 메소드
    @objc func refreshButtonTapped() {
        fetchChatRooms()
    }
    
    func fetchChatRooms() {
        let ref = Database.database().reference()
        ref.child("Chats").observe(.value, with: { [weak self] snapshot in
            guard let self = self, let chatRoomsDict = snapshot.value as? [String: [String: Any]] else { return }

            var allChatRooms: [ChatRoom] = []

            // 모든 채팅방을 가져옵니다.
            for (id, data) in chatRoomsDict {
                guard let author = data["author"] as? String,
                      let name = data["name"] as? String,
                      let password = data["password"] as? String,
                      let timestamp = data["timestamp"] as? Double else { continue }

                let chatRoom = ChatRoom(id: id, author: author, name: name, password: password, timestamp: timestamp)
                allChatRooms.append(chatRoom)
            }

            // 타임스탬프를 기준으로 내림차순 정렬합니다.
            self.originalChatRooms = allChatRooms.sorted(by: { $0.timestamp > $1.timestamp })

            // chatRooms 배열에는 공개방만 저장합니다.
            self.chatRooms = self.originalChatRooms.filter { $0.password.isEmpty }

            // 메인 스레드에서 화면을 업데이트합니다.
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }) { error in
            print(error.localizedDescription)
        }
    }

    
    // MARK: - Table view data source
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return chatRooms.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ChatRoomCell", for: indexPath)
        let chatRoom = chatRooms[indexPath.row]

        // DateFormatter 인스턴스 생성
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd" // 원하는 날짜 형식 설정
        dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) // 타임스탬프가TC 기준일 경우 설정

        // timestamp를 Date 객체로 변환
        let date = Date(timeIntervalSince1970: chatRoom.timestamp / 1000) // 밀리초 단위이므로 1000으로 나눔

        // Date 객체를 원하는 문자열 형식으로 변환
        let dateString = dateFormatter.string(from: date)

        // 셀의 텍스트에 채팅방의 제목, 작성자 이름, 그리고 날짜를 포함시킵니다.
        cell.textLabel?.text = "\(chatRoom.name) - \(chatRoom.author) - \(dateString)"
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let chatRoom = chatRooms[indexPath.row]
        // 비밀번호가 있는 경우 비밀번호 입력을 요구합니다.
        if !chatRoom.password.isEmpty {
            // 비밀번호 입력을 위한 AlertController 생성
            let passwordAlert = UIAlertController(title: "비밀번호 입력", message: "이 채팅방에 입장하려면 비밀번호를 입력해야 합니다.", preferredStyle: .alert)
            passwordAlert.addTextField { textField in
                textField.placeholder = "비밀번호"
                textField.isSecureTextEntry = true // 비밀번호를 안전하게 입력받기 위해
            }
            let enterAction = UIAlertAction(title: "입장", style: .default) { [weak self, weak passwordAlert] _ in
                guard let textField = passwordAlert?.textFields?.first, let inputPassword = textField.text else { return }
                if inputPassword == chatRoom.password {
                    // 비밀번호가 일치하면 채팅방으로 이동
                    self?.chatRoomNavigator?.navigateToChatRoom(chatRoom)
                    
                } else {
                    // 비밀번호가 일치하지 않으면 경고 메시지 표시
                    self?.showPasswordError()
                }
            }
            passwordAlert.addAction(enterAction)
            let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
            passwordAlert.addAction(cancelAction)
            
            // AlertController를 표시합니다.
            present(passwordAlert, animated: true, completion: nil)
        } else {
            // 비밀번호가 없는 경우 바로 채팅방으로 이동
            chatRoomNavigator?.navigateToChatRoom(chatRoom)
        }
    }

    func showPasswordError() {
        let errorAlert = UIAlertController(title: "오류", message: "비밀번호가 일치하지 않습니다.", preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
        errorAlert.addAction(okAction)
        present(errorAlert, animated: true, completion: nil)
    }

    /*
    func navigateToChatRoom(_ chatRoom: ChatRoom) {
        // 채팅방으로 이동하는 코드를 여기에 작성합니다.
        // 예: 해당 채팅방의 ViewController를 push 하거나 present 합니다.
    }
 */
    @objc func searchButtonTapped() {
        let alertController = UIAlertController(title: "검색", message: "제목 또는 작성자를 선택하고 검색어를 입력하세요.", preferredStyle: .alert)

        // 제목 또는 작성자를 선택하는 액션 추가
        let titleAction = UIAlertAction(title: "제목", style: .default) { [weak self] _ in
            self?.search(by: .title)
        }
        alertController.addAction(titleAction)

        let authorAction = UIAlertAction(title: "작성자", style: .default) { [weak self] _ in
            self?.search(by: .author)
        }
        alertController.addAction(authorAction)

        // 취소 액션 추가
        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)

        present(alertController, animated: true, completion: nil)
    }

    enum SearchType {
        case title
        case author
    }

    func search(by type: SearchType) {
        let alertController = UIAlertController(title: "검색어 입력", message: nil, preferredStyle: .alert)
        alertController.addTextField()

        let searchAction = UIAlertAction(title: "검색", style: .default) { [weak self, weak alertController] _ in
            guard let keyword = alertController?.textFields?.first?.text else { return }
            self?.filterChatRooms(by: type, keyword: keyword)
        }
        alertController.addAction(searchAction)

        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)

        present(alertController, animated: true, completion: nil)
    }
    
    
    func filterChatRooms(by type: SearchType, keyword: String) {
        switch type {
        case .title:
            chatRooms = originalChatRooms.filter { $0.name.contains(keyword) }
        case .author:
            chatRooms = originalChatRooms.filter { $0.author.contains(keyword) }
        }
        tableView.reloadData()
    }

    func resetFilter() {
        // originalChatRooms 배열에서 password가 빈 문자열인 방만 필터링하여 chatRooms 배열에 할당합니다.
        chatRooms = originalChatRooms.filter { $0.password.isEmpty }

        // tableView가 nil인지 확인합니다.
        if tableView != nil {
            tableView.reloadData()
        }
    }

    @objc func addButtonTapped() {
        let roomTypeAlert = UIAlertController(title: "방 유형 선택", message: "생성할 채팅방 유형을 선택해주세요.", preferredStyle: .actionSheet)

        let publicRoomAction = UIAlertAction(title: "공개방", style: .default) { [weak self] _ in
            self?.presentCreateRoomAlert(isSecret: false)
        }

        let secretRoomAction = UIAlertAction(title: "비밀방", style: .default) { [weak self] _ in
            self?.presentCreateRoomAlert(isSecret: true)
        }

        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)

        roomTypeAlert.addAction(publicRoomAction)
        roomTypeAlert.addAction(secretRoomAction)
        roomTypeAlert.addAction(cancelAction)

        present(roomTypeAlert, animated: true, completion: nil)
    }
    
    func presentCreateRoomAlert(isSecret: Bool) {
        let alert = UIAlertController(title: "새 채팅방 생성", message: "방 이름을 입력해주세요.", preferredStyle: .alert)

        alert.addTextField { textField in
            textField.placeholder = "방 이름"
        }

        if isSecret {
            alert.addTextField { textField in
                textField.placeholder = "비밀번호"
                textField.isSecureTextEntry = true
            }
        }

        let createAction = UIAlertAction(title: "생성", style: .default) { [weak self] _ in
            guard let name = alert.textFields?.first?.text,
                  let password = isSecret ? alert.textFields?.last?.text : "",
                  let self = self else { return }

            // 사용자 닉네임 가져오기
            guard let uid = Auth.auth().currentUser?.uid else { return }
            let usersRef = Database.database().reference().child("Users").child(uid)
            
            usersRef.observeSingleEvent(of: .value, with: { snapshot in
                let nickname = snapshot.childSnapshot(forPath: "nickname").value as? String ?? "익명"

                let ref = Database.database().reference().child("Chats")
                let chatRoomId = ref.childByAutoId().key ?? UUID().uuidString
                // 채팅방 정보에 타임스탬프를 추가
                let chatRoomInfo: [String: Any] = [
                    "id": chatRoomId,
                    "author": nickname,
                    "name": name,
                    "password": password,
                    "timestamp": ServerValue.timestamp() // 서버 타임스탬프 추가
                ]

                ref.child(chatRoomId).setValue(chatRoomInfo) { error, _ in
                    if let error = error {
                        print("데이터베이스 저장 에러: \(error.localizedDescription)")
                    } else {
                        print("새로운 채팅방이 성공적으로 생성되었습니다.")
                        // 채팅방 목록을 다시 가져오거나, UI를 업데이트하는 로직을 추가할 수 있습니다.
                    }
                }
            }) { error in
                print("닉네임 가져오기 에러: \(error.localizedDescription)")
            }
        }

        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)

        alert.addAction(createAction)
        alert.addAction(cancelAction)

        present(alert, animated: true, completion: nil)
    }
}

 

 

채팅방 생성 기능 구현

//
//  ChatMainViewController.swift
//  body_gym
//
//  Created by 차재식 on 1/26/24.
//

import Foundation
import UIKit
import FirebaseDatabase
import FirebaseAuth


struct ChatRoom {
    let id: String
    let author: String
    let name: String
    let password: String
    let timestamp: Double // 채팅방이 생성된 시간의 타임스탬프
}

class ChatRoomNavigator {
    weak var navigationController: UINavigationController?

    init(navigationController: UINavigationController?) {
        self.navigationController = navigationController
    }
    
    func navigateToChatRoom(_ chatRoom: ChatRoom) {
        let chatRoomViewController = ChatRoomViewController()
        chatRoomViewController.chatRoom = chatRoom
        navigationController?.pushViewController(chatRoomViewController, animated: true)
    }
}




class ChatMainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    var originalChatRooms: [ChatRoom] = []
    var chatRooms: [ChatRoom] = []
    var tableView: UITableView!
    var chatRoomNavigator: ChatRoomNavigator?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 테이블 뷰 생성 및 셀 등록
        tableView = UITableView()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ChatRoomCell")
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.contentInsetAdjustmentBehavior = .never // 이 줄 추가
        view.addSubview(tableView)

        let topBar = UIView()
        topBar.backgroundColor = .white
        topBar.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(topBar)

        let titleLabel = UILabel()
        titleLabel.text = "채팅방"
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(titleLabel)

        let searchButton = UIButton()
        searchButton.setTitle("검색", for: .normal)
        searchButton.setTitleColor(.black, for: .normal) // 글씨색을 검은색으로 설정
        searchButton.addTarget(self, action: #selector(searchButtonTapped), for: .touchUpInside)
        searchButton.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(searchButton)
        
        let refreshButton = UIButton()
        refreshButton.setTitle("새로고침", for: .normal)
        refreshButton.setTitleColor(.black, for: .normal) // 글씨색을 검은색으로 설정
        refreshButton.addTarget(self, action: #selector(refreshButtonTapped), for: .touchUpInside)
        refreshButton.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(refreshButton)
        refreshButton.isHidden = true

        let addButton = UIButton()
        addButton.setTitle("추가", for: .normal)
        addButton.setTitleColor(.black, for: .normal) // 글씨색을 검은색으로 설정
        addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
        addButton.translatesAutoresizingMaskIntoConstraints = false
        topBar.addSubview(addButton)
        
        let separator = UIView()
        separator.backgroundColor = .black // 실선의 색상을 검은색으로 설정
        separator.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(separator)
        
        // ChatRoomNavigator 인스턴스 초기화
        chatRoomNavigator = ChatRoomNavigator(navigationController: self.navigationController)
    
        NSLayoutConstraint.activate([
            topBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            topBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            topBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            topBar.heightAnchor.constraint(equalToConstant: 50),

            separator.topAnchor.constraint(equalTo: topBar.bottomAnchor),
            separator.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            separator.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            separator.heightAnchor.constraint(equalToConstant: 0.3), // 실선의 높이를 1로 설정

            tableView.topAnchor.constraint(equalTo: topBar.bottomAnchor), // separator.bottomAnchor를 topBar.bottomAnchor로 변경
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            titleLabel.centerXAnchor.constraint(equalTo: topBar.centerXAnchor),
            titleLabel.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),

            searchButton.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 10),
            searchButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
            
            refreshButton.leadingAnchor.constraint(equalTo: searchButton.trailingAnchor, constant: 10),
            refreshButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),

            addButton.trailingAnchor.constraint(equalTo: topBar.trailingAnchor, constant: -10),
            addButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
        ])
        fetchChatRooms()
    }
    
    // 새로고침 버튼이 눌렸을 때 호출될 메소드
    @objc func refreshButtonTapped() {
        fetchChatRooms()
    }
    
    func fetchChatRooms() {
        let ref = Database.database().reference()
        ref.child("Chats").observe(.value, with: { [weak self] snapshot in
            guard let self = self, let chatRoomsDict = snapshot.value as? [String: [String: Any]] else { return }

            var allChatRooms: [ChatRoom] = []

            // 모든 채팅방을 가져옵니다.
            for (id, data) in chatRoomsDict {
                guard let author = data["author"] as? String,
                      let name = data["name"] as? String,
                      let password = data["password"] as? String,
                      let timestamp = data["timestamp"] as? Double else { continue }

                let chatRoom = ChatRoom(id: id, author: author, name: name, password: password, timestamp: timestamp)
                allChatRooms.append(chatRoom)
            }

            // 타임스탬프를 기준으로 내림차순 정렬합니다.
            self.originalChatRooms = allChatRooms.sorted(by: { $0.timestamp > $1.timestamp })

            // chatRooms 배열에는 공개방만 저장합니다.
            self.chatRooms = self.originalChatRooms.filter { $0.password.isEmpty }

            // 메인 스레드에서 화면을 업데이트합니다.
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }) { error in
            print(error.localizedDescription)
        }
    }

    
    // MARK: - Table view data source
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return chatRooms.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ChatRoomCell", for: indexPath)
        let chatRoom = chatRooms[indexPath.row]

        // DateFormatter 인스턴스 생성
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd" // 원하는 날짜 형식 설정
        dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) // 타임스탬프가TC 기준일 경우 설정

        // timestamp를 Date 객체로 변환
        let date = Date(timeIntervalSince1970: chatRoom.timestamp / 1000) // 밀리초 단위이므로 1000으로 나눔

        // Date 객체를 원하는 문자열 형식으로 변환
        let dateString = dateFormatter.string(from: date)

        // 셀의 텍스트에 채팅방의 제목, 작성자 이름, 그리고 날짜를 포함시킵니다.
        cell.textLabel?.text = "\(chatRoom.name) - \(chatRoom.author) - \(dateString)"
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let chatRoom = chatRooms[indexPath.row]
        // 비밀번호가 있는 경우 비밀번호 입력을 요구합니다.
        if !chatRoom.password.isEmpty {
            // 비밀번호 입력을 위한 AlertController 생성
            let passwordAlert = UIAlertController(title: "비밀번호 입력", message: "이 채팅방에 입장하려면 비밀번호를 입력해야 합니다.", preferredStyle: .alert)
            passwordAlert.addTextField { textField in
                textField.placeholder = "비밀번호"
                textField.isSecureTextEntry = true // 비밀번호를 안전하게 입력받기 위해
            }
            let enterAction = UIAlertAction(title: "입장", style: .default) { [weak self, weak passwordAlert] _ in
                guard let textField = passwordAlert?.textFields?.first, let inputPassword = textField.text else { return }
                if inputPassword == chatRoom.password {
                    // 비밀번호가 일치하면 채팅방으로 이동
                    self?.chatRoomNavigator?.navigateToChatRoom(chatRoom)
                    
                } else {
                    // 비밀번호가 일치하지 않으면 경고 메시지 표시
                    self?.showPasswordError()
                }
            }
            passwordAlert.addAction(enterAction)
            let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
            passwordAlert.addAction(cancelAction)
            
            // AlertController를 표시합니다.
            present(passwordAlert, animated: true, completion: nil)
        } else {
            // 비밀번호가 없는 경우 바로 채팅방으로 이동
            chatRoomNavigator?.navigateToChatRoom(chatRoom)
        }
    }

    func showPasswordError() {
        let errorAlert = UIAlertController(title: "오류", message: "비밀번호가 일치하지 않습니다.", preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
        errorAlert.addAction(okAction)
        present(errorAlert, animated: true, completion: nil)
    }

    /*
    func navigateToChatRoom(_ chatRoom: ChatRoom) {
        // 채팅방으로 이동하는 코드를 여기에 작성합니다.
        // 예: 해당 채팅방의 ViewController를 push 하거나 present 합니다.
    }
 */
    @objc func searchButtonTapped() {
        let alertController = UIAlertController(title: "검색", message: "제목 또는 작성자를 선택하고 검색어를 입력하세요.", preferredStyle: .alert)

        // 제목 또는 작성자를 선택하는 액션 추가
        let titleAction = UIAlertAction(title: "제목", style: .default) { [weak self] _ in
            self?.search(by: .title)
        }
        alertController.addAction(titleAction)

        let authorAction = UIAlertAction(title: "작성자", style: .default) { [weak self] _ in
            self?.search(by: .author)
        }
        alertController.addAction(authorAction)

        // 취소 액션 추가
        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)

        present(alertController, animated: true, completion: nil)
    }

    enum SearchType {
        case title
        case author
    }

    func search(by type: SearchType) {
        let alertController = UIAlertController(title: "검색어 입력", message: nil, preferredStyle: .alert)
        alertController.addTextField()

        let searchAction = UIAlertAction(title: "검색", style: .default) { [weak self, weak alertController] _ in
            guard let keyword = alertController?.textFields?.first?.text else { return }
            self?.filterChatRooms(by: type, keyword: keyword)
        }
        alertController.addAction(searchAction)

        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)

        present(alertController, animated: true, completion: nil)
    }
    
    
    func filterChatRooms(by type: SearchType, keyword: String) {
        switch type {
        case .title:
            chatRooms = originalChatRooms.filter { $0.name.contains(keyword) }
        case .author:
            chatRooms = originalChatRooms.filter { $0.author.contains(keyword) }
        }
        tableView.reloadData()
    }

    func resetFilter() {
        // originalChatRooms 배열에서 password가 빈 문자열인 방만 필터링하여 chatRooms 배열에 할당합니다.
        chatRooms = originalChatRooms.filter { $0.password.isEmpty }

        // tableView가 nil인지 확인합니다.
        if tableView != nil {
            tableView.reloadData()
        }
    }

    @objc func addButtonTapped() {
        let roomTypeAlert = UIAlertController(title: "방 유형 선택", message: "생성할 채팅방 유형을 선택해주세요.", preferredStyle: .actionSheet)

        let publicRoomAction = UIAlertAction(title: "공개방", style: .default) { [weak self] _ in
            self?.presentCreateRoomAlert(isSecret: false)
        }

        let secretRoomAction = UIAlertAction(title: "비밀방", style: .default) { [weak self] _ in
            self?.presentCreateRoomAlert(isSecret: true)
        }

        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)

        roomTypeAlert.addAction(publicRoomAction)
        roomTypeAlert.addAction(secretRoomAction)
        roomTypeAlert.addAction(cancelAction)

        present(roomTypeAlert, animated: true, completion: nil)
    }
    
    func presentCreateRoomAlert(isSecret: Bool) {
        let alert = UIAlertController(title: "새 채팅방 생성", message: "방 이름을 입력해주세요.", preferredStyle: .alert)

        alert.addTextField { textField in
            textField.placeholder = "방 이름"
        }

        if isSecret {
            alert.addTextField { textField in
                textField.placeholder = "비밀번호"
                textField.isSecureTextEntry = true
            }
        }

        let createAction = UIAlertAction(title: "생성", style: .default) { [weak self] _ in
            guard let name = alert.textFields?.first?.text,
                  let password = isSecret ? alert.textFields?.last?.text : "",
                  let self = self else { return }

            // 사용자 닉네임 가져오기
            guard let uid = Auth.auth().currentUser?.uid else { return }
            let usersRef = Database.database().reference().child("Users").child(uid)
            
            usersRef.observeSingleEvent(of: .value, with: { snapshot in
                let nickname = snapshot.childSnapshot(forPath: "nickname").value as? String ?? "익명"

                let ref = Database.database().reference().child("Chats")
                let chatRoomId = ref.childByAutoId().key ?? UUID().uuidString
                // 채팅방 정보에 타임스탬프를 추가
                let chatRoomInfo: [String: Any] = [
                    "id": chatRoomId,
                    "author": nickname,
                    "name": name,
                    "password": password,
                    "timestamp": ServerValue.timestamp() // 서버 타임스탬프 추가
                ]

                ref.child(chatRoomId).setValue(chatRoomInfo) { error, _ in
                    if let error = error {
                        print("데이터베이스 저장 에러: \(error.localizedDescription)")
                    } else {
                        print("새로운 채팅방이 성공적으로 생성되었습니다.")
                        // 채팅방 목록을 다시 가져오거나, UI를 업데이트하는 로직을 추가할 수 있습니다.
                    }
                }
            }) { error in
                print("닉네임 가져오기 에러: \(error.localizedDescription)")
            }
        }

        let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)

        alert.addAction(createAction)
        alert.addAction(cancelAction)

        present(alert, animated: true, completion: nil)
    }
}

 

 

채팅방 세부사항 기능 구현

 

//
//  ChatRoomViewController.swift
//  body_gym
//
//  Created by 차재식 on 1/26/24.
//

import Foundation
import UIKit
import FirebaseDatabase
import Kingfisher
import FirebaseAuth
import FirebaseStorage



struct Message {
    let sender: String
    let content: String
    let timestamp: Int64
    let type: String
    let isCurrentUser: Bool
}

struct Report {
    let reportId: String
    let author: String
    let postTitle: String
    let reportReason: String
}

class ImageViewController: UIViewController {
    let imageView = UIImageView()

    init(image: UIImage) {
        super.init(nibName: nil, bundle: nil)
        imageView.image = image
        imageView.contentMode = .scaleAspectFit
        view.addSubview(imageView)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: view.topAnchor),
            imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class ChatRoomViewController: UIViewController {
    var chatRoom: ChatRoom!
    var ref: DatabaseReference!
    var scrollView: UIScrollView!
    var stackView: UIStackView!
    var chatViewController: ChatViewController!
    //var messageId: String? // 메시지 ID를 저장하는 프로퍼티를 추가합니다.
    var currentUserNickname: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // 배경색을 설정합니다.
        self.view.backgroundColor = .white
        scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)

        stackView = UIStackView()
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(stackView)
        
        //댓글 입력창 키도브 문제와 탭 관련 코드
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapOutside))
        view.addGestureRecognizer(tapGesture)
        
        // 키보드가 나타날 때
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)

        // 키보드가 사라질 때
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
        
        // 삭제 버튼 생성
        let deleteButton = UIBarButtonItem(title: "Delete", style: .plain, target: self, action: #selector(deleteButtonTapped))

        // 신고 버튼 생성
        let reportButton = UIBarButtonItem(title: "Report", style: .plain, target: self, action: #selector(reportButtonTapped))

        navigationItem.rightBarButtonItems = [deleteButton, reportButton] // 두 개 이상의 버튼을 추가할 때

        let inputFieldHeight: CGFloat = 50

        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),

            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
        ])

        // Firebase 데이터베이스 참조를 가져옵니다.
        let db = Database.database().reference()

        // 특정 ChatRoom을 가져옵니다.
        db.child("Chats").child(chatRoom.id).observeSingleEvent(of: .value) { (snapshot) in
            // Snapshot의 value를 딕셔너리로 변환합니다.
            guard let value = snapshot.value as? [String: Any] else {
                print("ChatRoom 데이터를 가져오는 데 실패했습니다.")
                return
            }

            // 딕셔너리에서 필요한 정보를 추출하여 ChatRoom 인스턴스를 생성합니다.
            self.chatRoom = ChatRoom(
                id: self.chatRoom.id,
                author: value["author"] as? String ?? "",
                name: value["name"] as? String ?? "",
                password: value["password"] as? String ?? "",
                timestamp: value["timestamp"] as? Double ?? 0.0
            )

            // navigationItem.title 설정
            if let chatRoomName = self.chatRoom?.name {
                self.navigationItem.title = chatRoomName
            }

            // 이제 ChatRoom 인스턴스를 가지고 있으므로, ChatViewController를 생성할 수 있습니다.
            self.chatViewController = ChatViewController(chatRoom: self.chatRoom, nibName: nil, bundle: nil)

            // ChatViewController를 생성하고, ChatRoomViewController에 추가합니다.
            self.addChild(self.chatViewController)
            self.view.addSubview(self.chatViewController.view)
            self.chatViewController.didMove(toParent: self)
                
            // ChatViewController의 뷰의 제약 조건을 설정합니다.
            self.chatViewController.view.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                self.chatViewController.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
                self.chatViewController.view.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
                self.chatViewController.view.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
                self.chatViewController.view.heightAnchor.constraint(equalToConstant: 50),

                // scrollView의 바닥 제약 조건을 chatViewController.view의 상단으로 설정합니다.
                self.scrollView.bottomAnchor.constraint(equalTo: self.chatViewController.view.topAnchor)
            ])

            // 이제 ChatViewController가 준비되었으므로, 메시지를 불러옵니다.
            self.loadMessages()
        }
    }
    
    
    @objc func tapOutside() {
        self.view.endEditing(true)
    }
    
    @objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            if self.view.frame.origin.y == 0 {
                self.view.frame.origin.y -= keyboardSize.height
            }
        }
    }

    @objc func keyboardWillHide(notification: NSNotification) {
        if self.view.frame.origin.y != 0 {
            self.view.frame.origin.y = 0
        }
    }
    
    @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
        if gesture.state == .began {
            guard let messageView = gesture.view as? ChatMessageView else { return }
            // 채팅을 보낸 사용자인지 확인
            if messageView.senderLabel.text == self.currentUserNickname {
                let alert = UIAlertController(title: nil, message: "메시지를 삭제하시겠습니까?", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "예", style: .default, handler: { _ in
                    // 메시지 삭제 코드
                    let ref = Database.database().reference().child("Chats").child(self.chatRoom.id).child(messageView.messageId ?? "")
                    ref.removeValue()
                }))
                alert.addAction(UIAlertAction(title: "아니오", style: .cancel, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }
        }
    }

    func loadMessages() {
            ref = Database.database().reference()

            ref.child("Chats").child(chatRoom.id).observe(.childAdded, with: { [weak self] (snapshot) in
                if let chatData = snapshot.value as? [String: Any] {
                    let id = snapshot.key // 메시지 ID를 가져옵니다.
                    let sender = chatData["sender"] as? String ?? ""
                    let content = chatData["content"] as? String ?? ""
                    let timestamp = chatData["timestamp"] as? Int64 ?? 0
                    let type = chatData["type"] as? String ?? ""

                    // ChatMessageView 인스턴스 생성 및 설정
                    let messageView = ChatMessageView()
                    messageView.messageId = id // 메시지 ID를 저장합니다.
                    // '길게 누르기' 제스처를 추가합니다.
                    let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self!.handleLongPress(_:)))
                    messageView.addGestureRecognizer(longPress)
                    // ...

                    self?.addChatMessageView(sender: sender, content: content, timestamp: timestamp, type: type)
                }
            }) { (error) in
                print(error.localizedDescription)
            }
        }



    func getCurrentUserNickname(completion: @escaping (String?) -> Void) {
        guard let uid = Auth.auth().currentUser?.uid else {
            completion(nil)
            return
        }

        let db = Database.database().reference()
        db.child("Users").child(uid).child("nickname").observeSingleEvent(of: .value) { (snapshot) in
            guard let nickname = snapshot.value as? String else {
                print("Error: Could not retrieve nickname.")
                completion(nil)
                return
            }
            completion(nickname)
        }
    }
    
    func addChatMessageView(sender: String, content: String, timestamp: Int64, type: String) {
        getCurrentUserNickname { [weak self] (currentUserNickname) in
            guard let self = self, let currentUserNickname = currentUserNickname else { return }
            let isCurrentUser = (sender == currentUserNickname)

            // Message 모델을 만듭니다.
            let message = Message(sender: sender, content: content, timestamp: timestamp, type: type, isCurrentUser: isCurrentUser)

            // 새로운 ChatMessageView를 생성하고 설정합니다.
            let chatMessageView = ChatMessageView()
            chatMessageView.updateWithChatData(message: message)

            // UIStackView에 ChatMessageView를 추가합니다.
            self.stackView.addArrangedSubview(chatMessageView)
        }
    }
    // 스크롤뷰를 맨 아래로 이동시키는 메소드입니다.
    func scrollToBottom(animated: Bool = true) {
        if scrollView.contentSize.height > scrollView.frame.height {
            let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.frame.height)
            scrollView.setContentOffset(bottomOffset, animated: animated)
        }
    }
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        scrollToBottom(animated: false)
    }
    
    // 삭제 버튼이 눌렸을 때 호출될 메소드
    @objc func deleteButtonTapped() {
        print("Delete button tapped.")
        
        // 현재 사용자의 닉네임을 가져옵니다.
        getCurrentUserNickname { [weak self] currentNickname in
            guard let currentNickname = currentNickname else {
                print("Could not retrieve current user nickname.")
                return
            }
            
            // 현재 채팅방의 개설자 닉네임과 현재 사용자의 닉네임을 비교합니다.
            if self?.chatRoom.author == currentNickname || self?.chatRoom.author == "관장님" || self?.chatRoom.author == "관리자" {
                // 닉네임이 같다면 알림창을 띄웁니다.
                let alertController = UIAlertController(title: "채팅방 삭제", message: "정말 삭제하시겠습니까?", preferredStyle: .alert)
                
                // '예' 버튼을 추가합니다.
                let yesAction = UIAlertAction(title: "예", style: .default) { _ in
                    // '예' 버튼을 누르면 채팅방을 삭제합니다.
                    if let chatRoom = self?.chatRoom {
                        self?.deleteChatRoom(chatRoom: chatRoom)
                    }
                    // 채팅방 목록 화면으로 돌아갑니다.
                    self?.navigationController?.popViewController(animated: true)
                }
                alertController.addAction(yesAction)
                
                // '아니오' 버튼을 추가합니다.
                let noAction = UIAlertAction(title: "아니오", style: .cancel, handler: nil)
                alertController.addAction(noAction)
                
                // 알림창을 띄웁니다.
                self?.present(alertController, animated: true, completion: nil)
            } else {
                // 닉네임이 다르다면 아무런 동작을 수행하지 않습니다.
                print("You are not the author of this chat room.")
            }
        }
    }

    func deleteChatRoom(chatRoom: ChatRoom) {
        print("Deleting chat room...")
        
        // 현재 채팅방의 ID를 가진 레퍼런스를 가져옵니다.
        let db = Database.database().reference()
        let chatRoomRef = db.child("Chats").child(chatRoom.id)
        
        // 해당 레퍼런스를 삭제합니다.
        chatRoomRef.removeValue { (error, _) in
            if let error = error {
                // 에러가 발생한 경우 에러 메시지를 출력합니다.
                print("Failed to delete chat room: \(error.localizedDescription)")
            } else {
                // 성공적으로 삭제한 경우 메시지를 출력합니다.
                print("Chat room successfully deleted.")
            }
        }
    }
    
    @objc func reportButtonTapped() {
        // 신고 버튼이 눌렸을 때의 동작을 작성합니다.
        let alertController = UIAlertController(title: "신고", message: "이 채팅방을 신고하시겠습니까?", preferredStyle: .alert)
        
        alertController.addTextField { (textField) in
            textField.placeholder = "신고 사유를 입력해주세요..."
        }
        
        let reportAction = UIAlertAction(title: "신고", style: .destructive) { _ in
            // 신고 로직을 작성합니다.
            if let reportReason = alertController.textFields?.first?.text {
                // Firebase에 신고 정보를 저장합니다.
                let reportRef = Database.database().reference().child("report")
                let reportId = reportRef.childByAutoId().key ?? ""
                
                let date = Date()
                let dateFormatter = DateFormatter()
                dateFormatter.dateFormat = "yyyy-MM-dd/HH:mm"
                let reportTime = dateFormatter.string(from: date) // 현재 시간을 'yyyy-MM-dd/HH:mm' 형식의 문자열로 변환합니다.
                
                let reportData = ["reportId": reportId,
                                  "reportReason": reportReason,
                                  "author": self.chatRoom?.author ?? "",
                                  "postTitle": self.chatRoom?.name ?? "",
                                  "reportTime": reportTime] // 신고 시간을 추가합니다.
                reportRef.child(reportId).setValue(reportData)
            }
        }
        
        let cancelAction = UIAlertAction(title: "취소", style: .cancel)
        
        alertController.addAction(reportAction)
        alertController.addAction(cancelAction)
        
        self.present(alertController, animated: true)
    }
}


class ChatMessageView: UIView {
    let senderLabel: UILabel
    let contentLabel: UILabel
    let timestampLabel: UILabel
    let chatImageView: UIImageView
    var chatImageViewLeadingConstraint: NSLayoutConstraint?
    var chatImageViewTrailingConstraint: NSLayoutConstraint?
    var chatImageViewHeightConstraint: NSLayoutConstraint?
    var messageId: String?


    override init(frame: CGRect) {
        senderLabel = UILabel()
        contentLabel = UILabel()
        timestampLabel = UILabel()
        chatImageView = UIImageView()
        chatImageView.contentMode = .scaleAspectFit // 이미지의 원본 비율을 유지

        super.init(frame: frame)

        addSubview(senderLabel)
        addSubview(contentLabel)
        addSubview(timestampLabel)
        addSubview(chatImageView)

        senderLabel.translatesAutoresizingMaskIntoConstraints = false
        contentLabel.translatesAutoresizingMaskIntoConstraints = false
        timestampLabel.translatesAutoresizingMaskIntoConstraints = false
        chatImageView.translatesAutoresizingMaskIntoConstraints = false
        self.translatesAutoresizingMaskIntoConstraints = false

        contentLabel.numberOfLines = 0
        
        
        chatImageViewLeadingConstraint = chatImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10)
        chatImageViewTrailingConstraint = chatImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10)
        chatImageViewLeadingConstraint?.isActive = false
        chatImageViewTrailingConstraint?.isActive = false
        
        chatImageViewHeightConstraint = chatImageView.heightAnchor.constraint(equalToConstant: 100)
        chatImageViewHeightConstraint?.isActive = false
        
        
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
        chatImageView.addGestureRecognizer(tapGestureRecognizer)
        chatImageView.isUserInteractionEnabled = true
        
        NSLayoutConstraint.activate([
            senderLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 5),
            senderLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
            senderLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
            contentLabel.topAnchor.constraint(equalTo: senderLabel.bottomAnchor, constant: 5),
            contentLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
            contentLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
            timestampLabel.topAnchor.constraint(equalTo: contentLabel.bottomAnchor, constant: 5),
            timestampLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10),
            timestampLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
            
            // 이미지 뷰의 너비를 100으로 설정
            chatImageView.widthAnchor.constraint(equalToConstant: 100),

            // 이미지 뷰의 위치를 조정
            chatImageView.topAnchor.constraint(equalTo: senderLabel.bottomAnchor, constant: 5),
            timestampLabel.topAnchor.constraint(equalTo: chatImageView.bottomAnchor, constant: 5),
            
            // 이미지 뷰의 좌우 위치를 조정하는 제약 조건
            chatImageViewLeadingConstraint!,
            chatImageViewTrailingConstraint!
        ])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func imageTapped() {
        guard let image = chatImageView.image else { return }
        let imageVC = ImageViewController(image: image)
        // 현재 뷰 컨트롤러를 가져오는 코드는 앱의 구조에 따라 다를 수 있습니다.
        if let viewController = self.findViewController() {
            viewController.present(imageVC, animated: true, completion: nil)
        }
    }

    override var intrinsicContentSize: CGSize {
        let contentSize = contentLabel.sizeThatFits(CGSize(width: contentLabel.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        let senderSize = senderLabel.sizeThatFits(CGSize(width: senderLabel.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        let timestampSize = timestampLabel.sizeThatFits(CGSize(width: timestampLabel.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        let chatImageSize = chatImageView.image != nil ? CGSize(width: 100, height: 100) : CGSize.zero // 크기를 고정한 값으로 변경

        let height = contentSize.height + senderSize.height + timestampSize.height + chatImageSize.height + 20 // 패딩과 간격의 합계
        return CGSize(width: UIView.noIntrinsicMetric, height: height)
    }

    func updateWithChatData(message: Message) {
        senderLabel.text = message.sender
        senderLabel.textColor = .black

        if message.type == "text" {
            contentLabel.text = message.content
            chatImageView.isHidden = true
            chatImageViewHeightConstraint?.constant = 0
        } else if message.type == "image", let url = URL(string: message.content) {
            contentLabel.text = ""
            chatImageView.isHidden = false
            chatImageViewHeightConstraint?.constant = 100
            chatImageViewHeightConstraint?.isActive = true
                chatImageView.kf.setImage(with: url) {
                    result in
                    switch result {
                    case .success(_):
                        self.invalidateIntrinsicContentSize() // 이미지 로드 후 intrinsic content size 업데이트
                    case .failure(let error):
                        print("Error: \(error)") // 이미지 로드 실패 시 에러 출력
                    }
                }
        } else {
            chatImageView.isHidden = true
            chatImageViewHeightConstraint?.constant = 0
        }

            if chatImageView.isHidden {
                self.invalidateIntrinsicContentSize() // 이미지 숨김 후 intrinsic content size 업데이트
            }

        let date = Date(timeIntervalSince1970: TimeInterval(message.timestamp))
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MM-dd/HH:mm"
        let dateString = dateFormatter.string(from: date)
        timestampLabel.text = dateString
        timestampLabel.textColor = .black

        if message.isCurrentUser {
            senderLabel.textAlignment = .right
            contentLabel.textAlignment = .right
            timestampLabel.textAlignment = .right
            chatImageViewLeadingConstraint?.isActive = false
            chatImageViewTrailingConstraint?.isActive = true
        } else {
            senderLabel.textAlignment = .left
            contentLabel.textAlignment = .left
            timestampLabel.textAlignment = .left
            chatImageViewLeadingConstraint?.isActive = true
            chatImageViewTrailingConstraint?.isActive = false
        }

        self.invalidateIntrinsicContentSize()
    }
}

class ChatViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    let messageInputField: UITextField
    let sendButton: UIButton
    let attachImageButton: UIButton
    let imagePicker: UIImagePickerController
    var chatRoom: ChatRoom! // 이 부분은 수정하지 않습니다.

    // chatRoom을 매개변수로 받는 생성자를 추가합니다.
    init(chatRoom: ChatRoom, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        self.chatRoom = chatRoom // 여기에서 chatRoom을 설정합니다.
        

        messageInputField = UITextField()
        sendButton = UIButton()
        attachImageButton = UIButton()
        imagePicker = UIImagePickerController()

        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        //self.chatRoom = chatRoom

        messageInputField.placeholder = "메시지를 입력하세요"
        sendButton.setTitle("전송", for: .normal)
        attachImageButton.setTitle("이미지 첨부", for: .normal)
        
        messageInputField.backgroundColor = .white // 배경색을 흰색으로 설정
        messageInputField.textColor = .black // 텍스트 색상을 검정색으로 설정

        sendButton.backgroundColor = .white // 배경색을 파란색으로 설정
        sendButton.setTitleColor(.black, for: .normal) // 텍스트 색상을 흰색으로 설정

        attachImageButton.backgroundColor = .white // 배경색을 파란색으로 설정
        attachImageButton.setTitleColor(.black, for: .normal) // 텍스트 색상을 흰색으로 설정


        sendButton.addTarget(self, action: #selector(sendMessage), for: .touchUpInside)
        attachImageButton.addTarget(self, action: #selector(attachImage), for: .touchUpInside)

        imagePicker.delegate = self

        // 뷰에 추가
        view.addSubview(messageInputField)
        view.addSubview(sendButton)
        view.addSubview(attachImageButton)

        // AutoLayout 설정
        messageInputField.translatesAutoresizingMaskIntoConstraints = false
        sendButton.translatesAutoresizingMaskIntoConstraints = false
        attachImageButton.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            messageInputField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
            messageInputField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            messageInputField.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -10),

            sendButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
            sendButton.trailingAnchor.constraint(equalTo: attachImageButton.leadingAnchor, constant: -10),
            sendButton.widthAnchor.constraint(equalToConstant: 60),

            attachImageButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
            attachImageButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
            attachImageButton.widthAnchor.constraint(equalToConstant: 100)
        ])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("ChatViewController viewDidLoad")

        // 뷰의 배경색을 흰색으로 설정
        view.backgroundColor = .white

        // chatRoom의 값 확인 및 navigationItem.title 설정
        if let chatRoomName = self.chatRoom?.name {
            navigationItem.title = chatRoomName
            print("ChatViewController chatRoom.name: \(chatRoomName)")

            // chatRoomName을 화면 상단, Back 버튼의 오른쪽에 출력
            let rightBarButtonItem = UIBarButtonItem(title: chatRoomName, style: .plain, target: self, action: nil)
            navigationItem.rightBarButtonItem = rightBarButtonItem
        } else {
            print("ChatViewController chatRoom이 설정되지 않았습니다.")
        }

        // chatRoom의 값 확인
        if let chatRoomId = self.chatRoom?.id {
            print("ChatViewController chatRoom.id: \(chatRoomId)")
        } else {
            print("ChatViewController chatRoom이 설정되지 않았습니다.")
        }
        
    }
    @objc func sendMessage() {
        guard let messageContent = messageInputField.text, !messageContent.isEmpty else {
            print("메시지가 비어있습니다.")
            return
        }

        // chatRoom과 chatRoom.id의 값 확인
        if let chatRoomId = self.chatRoom?.id {
            print("chatRoom.id: \(chatRoomId)")
        } else {
            print("chatRoom이 설정되지 않았습니다.")
            return
        }

        // 현재 로그인한 사용자의 닉네임을 가져옵니다.
        getCurrentUserNickname { (nickname) in
            guard let senderNickname = nickname else {
                print("사용자 닉네임을 가져오는 데 실패했습니다.")
                return
            }
            print("senderNickname: \(senderNickname)") // 이 위치로 print 문을 이동

            // 메시지 데이터를 만듭니다.
            let messageData = [
                "sender": senderNickname,
                "content": messageContent,
                "timestamp": Int64(Date().timeIntervalSince1970 * 1000), // 현재 시간을 밀리초로 변환
                "type": "text"
            ] as [String : Any]

            // 메시지를 Firebase 데이터베이스에 저장합니다.
            let db = Database.database().reference()
            db.child("Chats").child(self.chatRoom.id).childByAutoId().setValue(messageData) { (error, ref) in
                if let error = error {
                    print("데이터베이스에 메시지를 저장하는 데 실패했습니다: \(error.localizedDescription)")
                } else {
                    print("메시지를 성공적으로 저장했습니다.")
                    self.messageInputField.text = "" // 메시지 전송 후 텍스트 필드를 비웁니다.
                }
            }
        }
    }

        @objc func attachImage() {
            imagePicker.sourceType = .photoLibrary
            present(imagePicker, animated: true, completion: nil)
        }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        getCurrentUserNickname { (nickname) in
            if let nickname = nickname {
                self.handleImageSelected(info: info, nickname: nickname)
            } else {
                print("Error: Could not retrieve the current user's nickname.")
            }
        }
    }

    func handleImageSelected(info: [UIImagePickerController.InfoKey : Any], nickname: String) {
        if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            let storageRef = Storage.storage().reference().child("image/\(UUID().uuidString).jpg")
            if let uploadData = pickedImage.jpegData(compressionQuality: 0.5) {
                storageRef.putData(uploadData, metadata: nil) { (metadata, error) in
                    if error != nil {
                        print("Failed to upload image:", error!)
                        return
                    }
                    
                    print("Successfully uploaded image.")
                    
                    storageRef.downloadURL { (url, error) in
                        guard let downloadURL = url else {
                            print("Failed to get download url:", error!)
                            return
                        }
                        
                        print("Successfully got download URL.")
                        
                        // 채팅방 ID에 대한 참조를 얻은 후, 해당 위치에서 새로운 메시지 ID를 생성
                        let ref = Database.database().reference().child("Chats").child(self.chatRoom.id).childByAutoId()
                        let messageData: [String: Any] = [
                            "content": downloadURL.absoluteString,
                            "mine": true,
                            "sender": nickname,
                            "timestamp": Int(Date().timeIntervalSince1970 * 1000),
                            "type": "image",
                            "id": ref.key ?? "", // 생성된 메시지 ID를 사용합니다.
                            // 필요한 항목을 추가하거나 제거해주세요.
                            // "author": "Test_new",
                            // "name": "open_chat",
                            // "password": "",
                        ]
                        
                        ref.updateChildValues(messageData) { (error, ref) in
                            if error != nil {
                                print("Failed to save message data:", error!)
                                return
                            }
                            
                            print("Successfully saved message data.")
                        }
                    }
                }
            }
        }
        dismiss(animated: true, completion: nil)
    }

        func getCurrentUserNickname(completion: @escaping (String?) -> Void) {
            guard let uid = Auth.auth().currentUser?.uid else {
                completion(nil)
                return
            }

            let db = Database.database().reference()
            db.child("Users").child(uid).child("nickname").observeSingleEvent(of: .value) { (snapshot) in
                guard let nickname = snapshot.value as? String else {
                    print("Error: Could not retrieve nickname.")
                    completion(nil)
                    return
                }
                completion(nickname)
            }
        }
    }

extension UIView {
    func findViewController() -> UIViewController? {
        if let nextResponder = self.next as? UIViewController {
            return nextResponder
        } else if let nextResponder = self.next as? UIView {
            return nextResponder.findViewController()
        } else {
            return nil
        }
    }
}

 

 

결과화면