지난번에 구현한 게시글에 이은 채팅방 기능 구현이다.
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
}
}
}
결과화면
'IOS 앱 개발' 카테고리의 다른 글
바디와이짐 커뮤니티 앱 개발 - 설정 (1) | 2024.09.21 |
---|---|
바디와이짐 커뮤니티 앱 개발 - 캘린더 구현 (1) | 2024.09.21 |
바디와이짐 커뮤니티 앱 개발 - 게시글 기능 구현 (1) | 2024.09.21 |
바디와이짐 커뮤니티 앱 개발 - 메인 화면 (2) | 2024.09.21 |
바디와이짐 커뮤니티 앱 개발 - 메인 툴바 (0) | 2024.09.21 |