IOS 앱 개발

바디와이짐 커뮤니티 앱 개발 - 게시글 기능 구현

vitamin3000 2024. 9. 21. 19:12

지난 메인화면 구현에 이은 게시글 기능 구현이다.

 

1.  게시글을 올릴 때 글과 사진을 올릴 수 있다.

2. 게시글에 대한 댓글을 작성 가능하다

3. 1과 2에. 대한. 수정과 삭제는 "관리자 또는 관장님"의 닉네임을 가졌거나. 직접 작성한 사람만 가능하다.

4. 게시글 검색 기능 구현

5. 게시글 '신고'기능을 구현해야한다.  구현하지 않으면 앱스토어 등록에서 반려당한다.

 

작성한 코드

게시글 불러오기

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

import Foundation
struct Post {
    var title: String
    let writer: String
    let timestamp: Int
    var postId: String?
    var imageUrl : String?
    var content : String?

    init(dictionary: [String: Any]) {
        self.title = dictionary["title"] as? String ?? "title 없음"
        self.writer = dictionary["writer"] as? String ?? "익명"
        self.timestamp = dictionary["timestamp"] as? Int ?? 0
        self.postId = dictionary["postId"] as? String
        self.imageUrl = dictionary["imageUrl"] as? String
        self.content = dictionary["content"] as? String
    }
}

import Foundation
import UIKit
import FirebaseDatabase

class BoardMainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
    
    var posts = [Post]() {
        didSet {
            // "관리자" 또는 "관장님" 작성자의 게시글을 상단에 배치합니다.
            posts.sort { ($0.writer == "관리자" || $0.writer == "관장님") && !($1.writer == "관리자" || $1.writer == "관장님") }
        }
    }
    let tableView = UITableView()
    let headerLabel = UILabel()
    let separatorView = UIView()
    // '글 작성' 버튼 UI 요소
    let writePostButton = UIButton(type: .system)
    var filteredPosts = [Post]()
    let searchController = UISearchController(searchResultsController: nil)
    let searchButton = UIButton(type: .system)
    var isCustomSearchActive = false
    private var lastViewController: UIViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 화면 상단에 "자유게시판" 라벨과 구분선 추가
        setupHeaderView()
        // '글 작성' 버튼 설정 및 레이아웃 추가
        //setupWritePostButton()
        
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        
        // 검색 컨트롤러 설정
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "검색"
        navigationItem.searchController = nil
        navigationController?.navigationBar.isHidden = true
        definesPresentationContext = true
        
        // '검색' 버튼 설정
        searchButton.setTitle("검색", for: .normal)
        searchButton.addTarget(self, action: #selector(didTapSearchButton), for: .touchUpInside)
        view.addSubview(searchButton)
        
        searchButton.translatesAutoresizingMaskIntoConstraints = false
        
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: separatorView.bottomAnchor, constant: 8),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            searchButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
            searchButton.centerYAnchor.constraint(equalTo: headerLabel.centerYAnchor)
        ])
        
        fetchPosts()
        
    }
    
    

    func setupHeaderView() {
        headerLabel.text = "자유게시판"
        headerLabel.textColor = .black // 텍스트 색상을 변경합니다.
        headerLabel.backgroundColor = .white
        headerLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
        headerLabel.textAlignment = .center
        view.addSubview(headerLabel)
        
        separatorView.backgroundColor = .black
        view.addSubview(separatorView)
        
        headerLabel.translatesAutoresizingMaskIntoConstraints = false
        separatorView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            headerLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            headerLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            headerLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            
            separatorView.heightAnchor.constraint(equalToConstant: 0.3),
            separatorView.topAnchor.constraint(equalTo: headerLabel.bottomAnchor, constant: 8),
            separatorView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            separatorView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
        ])
        
        // '글 작성' 버튼 레이아웃 설정
        writePostButton.setTitle("글 작성", for: .normal)
        writePostButton.addTarget(self, action: #selector(didTapWritePostButton), for: .touchUpInside)
        view.addSubview(writePostButton)
        
        writePostButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            writePostButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
            writePostButton.centerYAnchor.constraint(equalTo: headerLabel.centerYAnchor)
        ])
    }

    // '글 작성' 버튼 액션
    @objc func didTapWritePostButton() {
        let createPostVC = CreatePostViewController()
        // 네비게이션 컨트롤러가 있다면, 그것을 사용해서 새로운 뷰 컨트롤러를 푸시합니다.
        // 없다면, 모달 형태로 표시합니다.
        if let navigationController = navigationController {
            navigationController.pushViewController(createPostVC, animated: true)
        } else {
            let navController = UINavigationController(rootViewController: createPostVC) // 모달을 위한 네비게이션 컨트롤러를 추가
            present(navController, animated: true, completion: nil)
        }
    }
    
    @objc func didTapSearchButton() {
        // 검색 옵션을 선택하기 위한 얼럿 컨트롤러를 생성합니다.
        let alertController = UIAlertController(title: "검색 옵션", message: "검색할 항목을 선택하세요.", preferredStyle: .actionSheet)

        // 제목 검색 액션을 정의합니다.
        let titleSearchAction = UIAlertAction(title: "제목으로 검색", style: .default) { [unowned self] _ in
            self.presentSearchAlert(searchType: "제목")
        }

        // 작성자 검색 액션을 정의합니다.
        let writerSearchAction = UIAlertAction(title: "작성자로 검색", style: .default) { [unowned self] _ in
            self.presentSearchAlert(searchType: "작성자")
        }

        // 취소 액션을 정의합니다.
        let cancelAction = UIAlertAction(title: "취소", style: .cancel)

        // 얼럿 컨트롤러에 액션을 추가합니다.
        alertController.addAction(titleSearchAction)
        alertController.addAction(writerSearchAction)
        alertController.addAction(cancelAction)

        // 얼럿 컨트롤러를 표시합니다.
        present(alertController, animated: true)
    }

    // 검색 얼럿을 표시하는 메소드
    func presentSearchAlert(searchType: String) {
        let searchAlertController = UIAlertController(title: "\(searchType) 검색", message: nil, preferredStyle: .alert)
        
        // 검색 텍스트 필드 추가
        searchAlertController.addTextField { textField in
            textField.placeholder = "\(searchType) 입력"
        }
        
        // 검색 액션을 정의합니다.
        let searchAction = UIAlertAction(title: "검색", style: .default) { [unowned self] _ in
            guard let searchText = searchAlertController.textFields?.first?.text, !searchText.isEmpty else { return }
            print("사용자가 입력한 검색어: '\(searchText)'") // 입력한 검색어 출력
            
            if searchType == "제목" {
                    self.filteredPosts = self.posts.filter { post in
                        post.title.lowercased().contains(searchText.lowercased())
                    }
                } else {
                    self.filteredPosts = self.posts.filter { post in
                        post.writer.lowercased().contains(searchText.lowercased())
                    }
                }
                
                print("필터링된 포스트 목록: \(self.filteredPosts.map { $0.title })") // 필터링된 제목 목록 출력

            
            self.isCustomSearchActive = true
            // 필터링 결과를 콘솔에 출력합니다.
            print("필터링된 포스트 수: \(self.filteredPosts.count)")
            
            // 메인 스레드에서 테이블뷰를 리로드합니다.
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
        
        // 취소 액션을 정의합니다.
        let cancelAction = UIAlertAction(title: "취소", style: .cancel) { [unowned self] _ in
            // 사용자 정의 검색 활성화 상태를 false로 설정합니다.
            self.isCustomSearchActive = false
            
            // 메인 스레드에서 테이블뷰를 리로드합니다.
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
        
        // 검색 얼럿 컨트롤러에 액션을 추가합니다.
        searchAlertController.addAction(searchAction)
        searchAlertController.addAction(cancelAction)
        
        // 검색 얼럿 컨트롤러를 표시합니다.
        present(searchAlertController, animated: true)
    }
    
    
    func updateSearchResults(for searchController: UISearchController) {
        // 사용자가 입력한 검색 텍스트를 가져옵니다.
        guard let searchText = searchController.searchBar.text else { return }

        // 검색 텍스트가 비어있지 않은 경우 필터링을 수행합니다.
        if searchText.isEmpty {
            filteredPosts = posts
        } else {
            filteredPosts = posts.filter { post in
                post.title.lowercased().contains(searchText.lowercased()) ||
                post.writer.lowercased().contains(searchText.lowercased())
            }
        }
        print("검색 업데이트 - 검색어: \(searchText)")
        print("검색 업데이트 - 필터링된 포스트 수: \(filteredPosts.count)")

        // 메인 스레드에서 테이블뷰를 리로드합니다.
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }

    func fetchPosts() {
        let ref = Database.database().reference().child("posts")
        ref.queryOrdered(byChild: "timestamp").observe(.value, with: { [weak self] snapshot in
            guard let self = self else { return }
            var adminPosts = [Post]()
            var otherPosts = [Post]()

            for child in snapshot.children.allObjects as! [DataSnapshot] {
                if let postDict = child.value as? [String: Any] {
                    let post = Post(dictionary: postDict)
                    if post.writer == "관리자" || post.writer == "관장님" {
                        adminPosts.append(post)
                    } else {
                        otherPosts.append(post)
                    }
                }
            }

            // 관리자 또는 관장님의 글을 최상단에 두고, 나머지 글들은 최신글이 위로 오도록 정렬합니다.
            otherPosts.sort { $0.timestamp > $1.timestamp }
            
            // 두 그룹을 합칩니다.
            self.posts = adminPosts + otherPosts
            print("전체 포스트 수: \(self.posts.count)")
            self.tableView.reloadData()
        }) { (error) in
            print(error.localizedDescription)
        }
    }

    // TableView DataSource Methods
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // `isCustomSearchActive` 변수를 사용하여 필터링된 결과를 반환합니다.
        return isCustomSearchActive ? filteredPosts.count : posts.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        // 검색이 활성화되었는지 확인하고, 해당하는 데이터 소스를 사용합니다.
        let post = isCustomSearchActive ? filteredPosts[indexPath.row] : posts[indexPath.row]

        // 데이터 모델에서 가져온 데이터를 셀에 할당합니다.
        let date = Date(timeIntervalSince1970: Double(post.timestamp) / 1000)
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
        dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
        let dateString = dateFormatter.string(from: date)

        // 셀에 표시될 텍스트를 설정합니다.
        var titleText = post.title
        if titleText.count > 10 {
            let index = titleText.index(titleText.startIndex, offsetBy: 10)
            titleText = "\(titleText[..<index])..."
        }
        cell.textLabel?.text = "\(titleText) - \(post.writer) - \(dateString)"
        
        // 셀에 할당된 데이터를 로그로 출력하여 확인합니다.
        print("셀에 할당된 데이터: \(cell.textLabel?.text ?? "데이터 없음")")

        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let detailViewController = PostDetailViewController()
        detailViewController.post = posts[indexPath.row] // 선택된 게시글의 데이터를 전달
        navigationController?.pushViewController(detailViewController, animated: true) // 화면 이동
    }
    
}

 

'추가' 버튼 기능 구현

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

import Foundation
import UIKit
import FirebaseDatabase
import FirebaseStorage
import FirebaseAuth
import SDWebImage


class UITextViewWithPlaceholder: UITextView {

    let placeholderLabel: UILabel = {
        let label = UILabel()
        label.textColor = UIColor.lightGray
        label.isHidden = false // isHidden 속성을 false로 변경
        return label
    }()

    var placeholder: String? {
        didSet {
            placeholderLabel.text = placeholder
            placeholderLabel.sizeToFit()
        }
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        self.setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setup()
    }

    func setup() {
        NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)

        placeholderLabel.font = self.font
        placeholderLabel.numberOfLines = 0
        self.addSubview(placeholderLabel)
        self.sendSubviewToBack(placeholderLabel)

        placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
        self.addConstraints([
            NSLayoutConstraint(item: placeholderLabel, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 5),
            NSLayoutConstraint(item: placeholderLabel, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: -5),
            NSLayoutConstraint(item: placeholderLabel, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 8),
        ])

        // 텍스트 컨테이너 인셋 설정
        textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5)
    }

    @objc func textChanged(notification: NSNotification) {
        placeholderLabel.isHidden = !self.text.isEmpty
    }
}



class CreatePostViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    // 이미지 피커 컨트롤러
    let imagePickerController = UIImagePickerController()
    
    // 게시글 내용, 이미지 URL, 제목, 작성자를 입력받는 텍스트 필드
    //var contentTextField: UITextField!
    var imageUrlTextField: UITextField!
    var titleTextField: UITextField!
    var writerTextField: UITextField!
    
    
    // 게시 버튼 UI 요소
    let postButton = UIButton(type: .system)
    
    // 취소 버튼 UI 요소
    let cancelButton = UIButton(type: .system)
    // 이미지 추가 버튼 UI 요소
    let addImageButton = UIButton(type: .system)
    
    var contentTextField: UITextViewWithPlaceholder!
    
    var imageUrl = ""
    
    
    
    // 게시 버튼이 눌렸을 때 호출되는 메소드
    @objc func didTapPostButton() {
        // 텍스트 필드에서 입력받은 내용을 가져옵니다.
        guard let content = contentTextField.text, !content.isEmpty,
              let title = titleTextField.text, !title.isEmpty else {
            // 오류 처리: 필수 필드가 비어 있는 경우
            // 적절한 사용자 피드백을 제공해야 합니다.
            // imageUrl이 비어 있으면 공백 문자열을 사용합니다.
            imageUrl = imageUrlTextField.text ?? ""
            return
        }
        
        //let imageUrl = ""
        // 현재 로그인한 사용자의 UID를 가져옵니다.
        guard let currentUserUid = Auth.auth().currentUser?.uid else {
            // 오류 처리: 사용자가 로그인하지 않았을 경우
            return
        }
        
        // Users 노드에서 현재 사용자의 닉네임을 가져옵니다.
        let usersRef = Database.database().reference().child("Users").child(currentUserUid)
        usersRef.observeSingleEvent(of: .value, with: { snapshot in
            // 닉네임을 가져오거나 기본값을 사용합니다.
            let nickname = (snapshot.value as? [String: Any])?["nickname"] as? String ?? "익명"
            
            // 파이어베이스에 저장하기 위한 참조와 타임스탬프를 생성합니다.
            let postRef = Database.database().reference().child("posts").childByAutoId()
            let timestamp = Int(Date().timeIntervalSince1970 * 1000) // 밀리세컨드 단위로 변환
            let postObject = [
                "content": content,
                "imageUrl": self.imageUrl,
                "postId": postRef.key, // childByAutoId()에서 생성된 고유 ID를 사용
                "title": title,
                "writer": nickname, // 닉네임을 작성자로 설정합니다.
                "timestamp": timestamp
            ] as [String: Any]
            
            // 게시글 객체를 파이어베이스 데이터베이스에 저장합니다.
            postRef.setValue(postObject) { [weak self] error, ref in
                if let error = error {
                    // 오류 처리: 게시글 저장 실패
                    print("Error posting: \(error.localizedDescription)")
                    // 사용자에게 오류 메시지를 표시합니다.
                } else {
                    // 성공 처리: 게시글 저장 성공
                    self?.dismiss(animated: true, completion: nil)  // 이 부분을 이동하였습니다.
                    self?.navigationController?.popViewController(animated: true)
                }
            }
        }) { error in
            // 오류 처리: 사용자 정보 가져오기 실패
        }
        self.dismiss(animated: true, completion: nil)
        self.navigationController?.popViewController(animated: true)
    }
    
    // 이미지 추가 버튼이 눌렸을 때 호출되는 메소드
    @objc func didTapAddImageButton() {
        // 이미지 피커 표시
        imagePickerController.sourceType = .photoLibrary
        present(imagePickerController, animated: true)
    }
    
    // 취소 버튼이 눌렸을 때 호출되는 메소드
    @objc func didTapCancelButton() {
        // 현재 뷰 컨트롤러를 닫고 이전 화면으로 돌아갑니다.
        self.dismiss(animated: true, completion: nil)
        self.navigationController?.popViewController(animated: true)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 이미지 피커 델리게이트 설정
        imagePickerController.delegate = self
        
        // 레이아웃 설정을 위한 UI 설정 메소드 호출
        setupUI()
        
        // '게시' 버튼 액션 설정
        postButton.addTarget(self, action: #selector(didTapPostButton), for: .touchUpInside)
        
        // '취소' 버튼 액션 설정
        cancelButton.addTarget(self, action: #selector(didTapCancelButton), for: .touchUpInside)
        
        // '이미지 추가' 버튼 액션 설정
        addImageButton.addTarget(self, action: #selector(didTapAddImageButton), for: .touchUpInside)
        
        
        imagePickerController.delegate = self
        self.view.addSubview(imageUrlTextField)
    }
    
    func setupUI() {
        // UI 요소들의 배경색을 흰색으로 설정합니다.
        view.backgroundColor = .white
        
        imageUrlTextField = UITextField()
        
        
        // '글 작성하기' 라벨 설정
        let titleLabel = UILabel()
        titleLabel.text = "글 작성하기"
        titleLabel.textAlignment = .center
        titleLabel.font = UIFont.boldSystemFont(ofSize: 20)
        view.addSubview(titleLabel)
        
        // 구분선 설정
        let separatorView = UIView()
        separatorView.backgroundColor = .black
        view.addSubview(separatorView)
        
        
        
        // 텍스트 필드 초기화 및 설정
        titleTextField = UITextField()
        titleTextField.placeholder = "제목"
        titleTextField.borderStyle = .roundedRect
        view.addSubview(titleTextField)
        
        // 본문 텍스트 필드 초기화
        contentTextField = UITextViewWithPlaceholder()
        
        // 본문 텍스트 필드 설정
        contentTextField.placeholder = "게시글 작성 시 다음 사항을 준수해주세요:\n1. 욕설 및 비방글은 금지입니다.\n2. 타인의 사생활을 존중해주세요.\n3. 부적절한 내용의 게시글은 삭제됩니다."
        contentTextField.textColor = .black // 텍스트 색상을 검은색으로 설정
        contentTextField.isEditable = true // 텍스트 뷰를 편집 가능하게 설정
        
        // 테두리 스타일 설정
        contentTextField.layer.cornerRadius = 5
        contentTextField.layer.borderColor = UIColor.gray.cgColor
        contentTextField.layer.borderWidth = 1
        
        // 뷰에 추가
        view.addSubview(contentTextField)
        
        // 버튼들을 뷰에 추가합니다.
        view.addSubview(addImageButton)
        view.addSubview(postButton)
        view.addSubview(cancelButton)
        
        // 버튼들의 오토레이아웃 설정
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        separatorView.translatesAutoresizingMaskIntoConstraints = false
        titleTextField.translatesAutoresizingMaskIntoConstraints = false
        contentTextField.translatesAutoresizingMaskIntoConstraints = false
        postButton.translatesAutoresizingMaskIntoConstraints = false
        addImageButton.translatesAutoresizingMaskIntoConstraints = false
        cancelButton.translatesAutoresizingMaskIntoConstraints = false
        
        
        NSLayoutConstraint.activate([
            // '글 작성하기' 라벨 레이아웃
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            
            // 구분선 레이아웃
            separatorView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
            separatorView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            separatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            separatorView.heightAnchor.constraint(equalToConstant: 1),
            
            // 제목 텍스트필드 레이아웃
            titleTextField.topAnchor.constraint(equalTo: separatorView.bottomAnchor, constant: 20),
            titleTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            // 본문 텍스트필드 레이아웃
            contentTextField.topAnchor.constraint(equalTo: titleTextField.bottomAnchor, constant: 20),
            contentTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            contentTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            contentTextField.heightAnchor.constraint(equalToConstant: 120), // 본문 필드의 높이를 지정합니다.
            
            // '이미지 추가' 버튼 레이아웃
            addImageButton.topAnchor.constraint(equalTo: contentTextField.bottomAnchor, constant: 20),
            addImageButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            addImageButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            addImageButton.heightAnchor.constraint(equalToConstant: 50), // 버튼의 높이를 지정합니다.
            
            // '게시' 버튼 레이아웃
            postButton.topAnchor.constraint(equalTo: addImageButton.bottomAnchor, constant: 20),
            postButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            postButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            postButton.heightAnchor.constraint(equalToConstant: 50), // 버튼의 높이를 지정합니다.
            
            // '취소' 버튼 레이아웃
            cancelButton.topAnchor.constraint(equalTo: postButton.bottomAnchor, constant: 20),
            cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50), // 버튼의 높이를 지정합니다.
        ])
        
        // 버튼 타이틀 설정
        postButton.setTitle("게시", for: .normal)
        addImageButton.setTitle("이미지 추가", for: .normal)
        cancelButton.setTitle("취소", for: .normal)
        
        // 버튼 기본 색상 설정
        postButton.setTitleColor(.blue, for: .normal)
        addImageButton.setTitleColor(.blue, for: .normal)
        cancelButton.setTitleColor(.blue, for: .normal)
    }
    
    // 이미지 피커 컨트롤러에서 이미지를 선택했을 때 호출되는 메소드
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        print("imagePickerCOntroller function has been called")
        
        if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            // 이미지 데이터를 JPEG 형식으로 변환 (압축률 0.8)
            
            if let imageData = pickedImage.jpegData(compressionQuality: 0.8) {
                print("Image data has been created successfully. : \(imageData)")
                
                // Firebase Storage에 업로드할 경로 설정
                let storagePath = "image/\(UUID().uuidString)"
                let storageRef = Storage.storage().reference().child("image/\(UUID().uuidString)")
                print("Storage Path: \(storagePath)")
                print("Storage Ref: \(storageRef)")
                
                // 이미지 데이터 업로드
                storageRef.putData(imageData, metadata: nil) { (metadata, error) in
                    if let error = error{
                        print("Failed to upload image : \(error.localizedDescription)")
                    }
                    else if let metadata = metadata{
                        print("image upload Successfully : \(metadata.path ?? "")")
                    }
                    
                    // 이미지 URL 가져오기
                    storageRef.downloadURL { (url, error) in
                        guard let downloadURL = url else {
                            // URL 가져오기 실패 처리
                            print("Failed to get download URL")
                            return
                        }

                        // 이미지 URL을 textField에 설정
                        if let imageUrlTextField = self.imageUrlTextField {
                            imageUrlTextField.text = downloadURL.absoluteString
                            self.imageUrl = downloadURL.absoluteString // imageUrl 업데이트
                        } else {
                            print("imageUrlTextField이 nil입니다.")
                        }
                    }
                }
                dismiss(animated: true, completion: nil)
            }
            
            // 이미지 피커 컨트롤러에서 취소했을 때 호출되는 메소드
            func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
                dismiss(animated: true, completion: nil)
            }
            
            func setupButtons() {
                // 버튼들을 뷰에 추가합니다.
                view.addSubview(postButton)
                view.addSubview(addImageButton)
                view.addSubview(cancelButton)
                
                postButton.setTitle("게시", for: .normal)
                addImageButton.setTitle("이미지 추가", for: .normal)
                cancelButton.setTitle("취소", for: .normal)
                
                // 버튼 기본 색상 설정
                postButton.setTitleColor(.blue, for: .normal)
                addImageButton.setTitleColor(.blue, for: .normal)
                cancelButton.setTitleColor(.blue, for: .normal)
                
                // postButton 오토레이아웃 설정
                NSLayoutConstraint.activate([
                    postButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                    postButton.topAnchor.constraint(equalTo: contentTextField.bottomAnchor, constant: 20),
                    postButton.widthAnchor.constraint(equalToConstant: 100),
                    postButton.heightAnchor.constraint(equalToConstant: 50)
                ])
            }
        }
    }
}

 

게시글 세부사항 나타내기

 

//
//  PostDetailViewController.swift
//  bodyYgym
//
//  Created by 차재식 on 2024/01/21.
//  Copyright © 2024 차재식. All rights reserved.
//

import UIKit
import FirebaseDatabase
import FirebaseStorage
import FirebaseAuth
import SDWebImage

struct Comment {
    var author: String?
    let text: String
    let timestamp: Int
    let userId: String
    let id: String

    init(dictionary: [String: Any]) {
        self.author = dictionary["author"] as? String ?? "익명"
        self.text = dictionary["text"] as? String ?? ""
        self.timestamp = dictionary["timestamp"] as? Int ?? 0
        self.userId = dictionary["userId"] as? String ?? ""
        self.id = dictionary["id"] as? String ?? ""
    }
}

class PostDetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
    var post: Post?
    var postRef: DatabaseReference!
    var comments: [Comment] = []
    var commentsTableView: UITableView!
    var commentTextField: UITextField!
    let imageView = UIImageView() // 클래스의 속성으로 추가
    let sendButton = UIButton()
    let titleLabel = UILabel()
    let contentTextView = UITextView()
    

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
        tapGesture.cancelsTouchesInView = false
        self.view.addGestureRecognizer(tapGesture)

        postRef = Database.database().reference().child("posts").child(post?.postId ?? "")

        postRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
            guard let self = self else { return }
            if let dictionary = snapshot.value as? [String: AnyObject] {
                let post = Post(dictionary: dictionary)
                self.setupUI(with: post)
            }
        }, withCancel: nil)
        
    
        sendButton.setTitle("저장", for: .normal)
        sendButton.setTitleColor(.black, for: .normal)
        sendButton.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside)
        sendButton.translatesAutoresizingMaskIntoConstraints = false
        sendButton.backgroundColor = .lightGray
        view.addSubview(sendButton)
        
        
        // 댓글 테이블 뷰 설정
            commentsTableView = UITableView()
            commentsTableView.delegate = self
            commentsTableView.dataSource = self
            commentsTableView.register(UITableViewCell.self, forCellReuseIdentifier: "CommentCell")
            commentsTableView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(commentsTableView)

            // 댓글 입력 필드 설정
            commentTextField = UITextField()
            commentTextField.placeholder = "댓글을 입력하세요..."
            commentTextField.delegate = self
            commentTextField.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(commentTextField)

            // 뒤로가기 버튼 추가
            navigationItem.leftBarButtonItem = UIBarButtonItem(title: "뒤로가기", style: .plain, target: self, action: #selector(goBack))
        
        // 로그인한 사용자의 이름을 가져옵니다.
        fetchLoggedInUserName { [weak self] (username) in
            guard let self = self else { return }
            
            print("Logged in user name: \(username ?? "nil")")  // 로그인한 사용자의 이름을 출력합니다.
            print("Post writer: \(self.post?.writer ?? "nil")")  // 게시글 작성자의 이름을 출력합니다.
            
            if self.post?.writer == username || username == "관장님" || username == "관리자" {
                let editButton = UIBarButtonItem(title: "수정", style: .plain, target: self, action: #selector(self.editButtonTapped))
                self.navigationItem.rightBarButtonItem = editButton

                let deleteButton = UIBarButtonItem(title: "삭제", style: .plain, target: self, action: #selector(self.deleteButtonTapped))
                self.navigationItem.leftBarButtonItem = deleteButton
            }
        }
        
        let commentsRef = Database.database().reference().child("comments").child(post?.postId ?? "")
        commentsRef.observe(.childAdded, with: { [weak self] (snapshot) in
            guard let self = self else { return }
            if var dictionary = snapshot.value as? [String: AnyObject] {
                dictionary["id"] = snapshot.key as AnyObject // 여기서 "id"를 추가합니다.
                var comment = Comment(dictionary: dictionary) // comment를 var로 선언합니다.
                
                // userId에 해당하는 사용자의 닉네임을 가져옵니다.
                self.fetchUserName(with: comment.userId) { (username) in
                    comment.author = username // 닉네임을 author 프로퍼티에 저장합니다.
                    print(comment.author)
                    self.comments.append(comment)
                    DispatchQueue.main.async {
                        self.commentsTableView.reloadData()
                    }
                }
            }
        }, withCancel: nil)
        commentsTableView.dataSource = self
        //fetchComments()
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
        // '전송' 버튼의 제약 조건 설정

    }

    func setupUI(with post: Post) {
        
        // 게시글 내용을 표시하는 TextView를 생성하고 설정합니다.
        contentTextView.text = post.content
        contentTextView.font = UIFont.systemFont(ofSize: 20)
        contentTextView.isEditable = false
        contentTextView.translatesAutoresizingMaskIntoConstraints = false
        
        
        // 선을 생성합니다.
        let separatorLine = UIView()
        separatorLine.backgroundColor = .black
        separatorLine.translatesAutoresizingMaskIntoConstraints = false
            
        let text = post.title
        if text.count > 10 {
            let index = text.index(text.startIndex, offsetBy: 8)
            titleLabel.text = String(text[..<index]) + "..."
        } else {
            titleLabel.text = text
        }
        titleLabel.translatesAutoresizingMaskIntoConstraints = false

        let writerAndDateLabel = UILabel()
        writerAndDateLabel.font = UIFont.systemFont(ofSize: 16)
        writerAndDateLabel.translatesAutoresizingMaskIntoConstraints = false
        
        let timestampInt = post.timestamp ?? 0
        let timestamp = Double(timestampInt)
        let date = Date(timeIntervalSince1970: timestamp / 1000) // Firebase timestamp는 밀리초 단위이므로 1000으로 나눕니다.

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MM-dd HH:mm" // 원하는 날짜 형식으로 설정합니다.
        let dateString = dateFormatter.string(from: date)

        writerAndDateLabel.text = "\(post.writer ?? "") (\(dateString))"
        

        
        let editButton = UIButton()
        editButton.setTitle("수정", for: .normal)
        editButton.setTitleColor(.blue, for: .normal)
        editButton.translatesAutoresizingMaskIntoConstraints = false
        editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)

        let deleteButton = UIButton()
        deleteButton.setTitle("삭제", for: .normal)
        deleteButton.setTitleColor(.red, for: .normal)
        deleteButton.translatesAutoresizingMaskIntoConstraints = false
        deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
        
        let reportButton = UIButton()
        reportButton.setTitle("신고", for: .normal)
        reportButton.setTitleColor(.red, for: .normal)
        reportButton.translatesAutoresizingMaskIntoConstraints = false
        reportButton.addTarget(self, action: #selector(reportButtonTapped), for: .touchUpInside)
        
        
        imageView.sd_setImage(with: URL(string: post.imageUrl ?? ""), completed: nil)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.isUserInteractionEnabled = true
        imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageViewTapped)))

        // 댓글 공간을 위한 placeholder
        let commentsPlaceholder = UIView()
        commentsPlaceholder.backgroundColor = .lightGray
        commentsPlaceholder.translatesAutoresizingMaskIntoConstraints = false
        // commentsPlaceholder 뷰에 댓글 테이블 뷰와 댓글 입력 필드를 추가합니다.
        commentsPlaceholder.addSubview(commentsTableView)
        commentsPlaceholder.addSubview(commentTextField)
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        writerAndDateLabel.translatesAutoresizingMaskIntoConstraints = false
        contentTextView.translatesAutoresizingMaskIntoConstraints = false
        imageView.translatesAutoresizingMaskIntoConstraints = false
        commentsPlaceholder.translatesAutoresizingMaskIntoConstraints = false
        separatorLine.translatesAutoresizingMaskIntoConstraints = false
    
    
        
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "뒤로가기", style: .plain, target: self, action: #selector(goBack))

        view.addSubview(titleLabel)
        view.addSubview(writerAndDateLabel)
        view.addSubview(contentTextView) // 여기에 추가
        view.addSubview(imageView)
        view.addSubview(commentsPlaceholder)
        view.addSubview(separatorLine)
        view.addSubview(commentsTableView)
        view.addSubview(editButton)
        view.addSubview(deleteButton)
        view.addSubview(reportButton)
        commentsPlaceholder.addSubview(sendButton)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.heightAnchor.constraint(equalToConstant: 30), // 예시입니다. 적절한 값으로 조절해주세요.
           
            
            separatorLine.topAnchor.constraint(equalTo: writerAndDateLabel.bottomAnchor, constant: 20),
            separatorLine.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            separatorLine.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            separatorLine.heightAnchor.constraint(equalToConstant: 1),

            writerAndDateLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
            writerAndDateLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            writerAndDateLabel.heightAnchor.constraint(equalToConstant: 30), // 예시입니다. 적절한 값으로 조절해주세요.

            editButton.topAnchor.constraint(equalTo: titleLabel.topAnchor),
            editButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 5),
            editButton.widthAnchor.constraint(equalToConstant: 60),
            editButton.heightAnchor.constraint(equalToConstant: 30),

            deleteButton.topAnchor.constraint(equalTo: editButton.topAnchor),
            deleteButton.leadingAnchor.constraint(equalTo: editButton.trailingAnchor, constant: 5),
            deleteButton.widthAnchor.constraint(equalToConstant: 60),
            deleteButton.heightAnchor.constraint(equalToConstant: 30),

            reportButton.topAnchor.constraint(equalTo: titleLabel.topAnchor),
            reportButton.leadingAnchor.constraint(equalTo: deleteButton.trailingAnchor, constant: 5),
            reportButton.widthAnchor.constraint(equalToConstant: 60),
            reportButton.heightAnchor.constraint(equalToConstant: 30),
            
            
            
            contentTextView.topAnchor.constraint(equalTo: writerAndDateLabel.bottomAnchor, constant: 20),
            contentTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            contentTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            contentTextView.heightAnchor.constraint(equalToConstant: 150), // 높이 제약 조건을 변경합니다.
            
            imageView.topAnchor.constraint(equalTo: contentTextView.bottomAnchor, constant: 20), // 여기를 변경합니다.
            imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            imageView.widthAnchor.constraint(equalToConstant: 100),
            imageView.heightAnchor.constraint(equalToConstant: 100),
            

            // commentsPlaceholder의 제약 조건을 설정합니다.
            commentsPlaceholder.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20),
            commentsPlaceholder.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            commentsPlaceholder.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            commentsPlaceholder.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            
            
            commentTextField.leadingAnchor.constraint(equalTo: commentsPlaceholder.leadingAnchor),
            commentTextField.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -8),
            commentTextField.bottomAnchor.constraint(equalTo: commentsPlaceholder.bottomAnchor),
            commentTextField.heightAnchor.constraint(equalToConstant: 50),
            

            // commentsTableView의 top 제약 조건을 commentsPlaceholder.topAnchor로 변경합니다.
            commentsTableView.topAnchor.constraint(equalTo: commentsPlaceholder.topAnchor),
            commentsTableView.leadingAnchor.constraint(equalTo: commentsPlaceholder.leadingAnchor),
            commentsTableView.trailingAnchor.constraint(equalTo: commentsPlaceholder.trailingAnchor),
            commentsTableView.bottomAnchor.constraint(equalTo: commentTextField.topAnchor), // 이 부분을 추가

            commentTextField.leadingAnchor.constraint(equalTo: commentsPlaceholder.leadingAnchor),
            commentTextField.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -8),
            commentTextField.bottomAnchor.constraint(equalTo: commentsPlaceholder.bottomAnchor),
            commentTextField.heightAnchor.constraint(equalToConstant: 50),
            sendButton.trailingAnchor.constraint(equalTo: commentsPlaceholder.trailingAnchor, constant: -16),
            sendButton.bottomAnchor.constraint(equalTo: commentTextField.bottomAnchor), // 추가된 부분
            sendButton.widthAnchor.constraint(equalToConstant: 60),
            sendButton.heightAnchor.constraint(equalTo: commentTextField.heightAnchor),
        ])
    }
    
    func fetchComments() {
        guard let postId = self.post?.postId else { return } // post는 현재 게시물을 나타내는 변수입니다.
        print("Failed to get postId")

        let commentsRef = Database.database().reference().child("comments").child(postId)
        print("Fetching comments from: comments/\(postId)")
        commentsRef.observe(.childAdded, with: { [weak self] (snapshot) in
            guard let self = self else { return }
            guard let dictionary = snapshot.value as? [String: Any] else { return }
            
            let comment = Comment(dictionary: dictionary)
            self.comments.append(comment)
            
            // 댓글 데이터를 콘솔에 출력합니다.
            print("Fetched comment: \(comment.text)")
            
            // UI 업데이트는 메인 스레드에서 실행해야 합니다.
            DispatchQueue.main.async {
                self.commentsTableView.reloadData()
            }
        })
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        guard let commentText = textField.text, !commentText.isEmpty else {
            // 댓글 텍스트가 비어있으면 아무것도 하지 않습니다.
            return true
        }

        guard let userId = Auth.auth().currentUser?.uid else {
            print("No user is signed in.")
            return true
        }

        // userId에 해당하는 사용자의 닉네임을 가져옵니다.
        self.fetchUserName(with: userId) { [weak self] (username) in
            guard let self = self else { return }
            
            let commentsRef = Database.database().reference().child("comments").child(self.post?.postId ?? "")
            let newCommentRef = commentsRef.childByAutoId()
            let timestamp = Int(Date().timeIntervalSince1970 * 1000) // Firebase timestamp는 밀리초 단위입니다.
            let commentId = newCommentRef.key ?? "" // 여기에서 댓글의 ID를 가져옵니다.

            // 'author' 필드에 닉네임을 추가하고, 'id' 필드에 댓글의 ID를 추가합니다.
            let values: [String: Any] = ["text": commentText, "userId": userId, "timestamp": timestamp, "author": username ?? "", "id": commentId]
            
            newCommentRef.updateChildValues(values) { [weak self] (error, ref) in
                guard let self = self else { return }
                
                if let error = error {
                    print("Failed to save comment:", error)
                    return
                }

                // 댓글을 저장한 후에는 텍스트 필드를 비웁니다.
                DispatchQueue.main.async {
                    textField.text = ""
                }

                // 댓글을 comments 배열에 추가하고, commentsTableView를 리로드합니다.
                ref.observeSingleEvent(of: .value, with: { (snapshot) in
                    guard let dictionary = snapshot.value as? [String: Any] else { return }
                    let comment = Comment(dictionary: dictionary)
                    self.comments.append(comment)
                    
                    DispatchQueue.main.async {
                        self.commentsTableView.reloadData()
                    }
                })
            }
        }

        return true
    }

    // MARK: - UITableView Delegate & DataSource

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return comments.count
    }
    
    func fetchUserName(with userId: String, completion: @escaping (String?) -> Void) {
        let usersRef = Database.database().reference().child("Users").child(userId)
        usersRef.observeSingleEvent(of: .value) { (snapshot) in
            let value = snapshot.value as? [String: Any]
            let username = value?["nickname"] as? String // 닉네임 키가 "nickname"이라고 가정했습니다.
            completion(username)
        }
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        let comment = comments[indexPath.row]
        
        // 로그인한 사용자의 이름을 가져옵니다.
        fetchLoggedInUserName { [weak self] (username) in
            guard let self = self else { return }
            
            if comment.author == username {
                // 삭제 대화 상자를 표시합니다.
                let alertController = UIAlertController(title: "댓글 삭제", message: "이 댓글을 삭제하시겠습니까?", preferredStyle: .alert) // 이 부분이 빠져있었습니다.
                let deleteAction = UIAlertAction(title: "삭제", style: .destructive) { _ in
                    // 댓글을 삭제합니다.
                    let postId = self.post?.postId ?? ""
                    let commentId = comment.id // 이 부분을 수정합니다.
                    print("Post ID: \(postId)") // Post ID 확인
                    print("Comment ID: \(commentId)") // Comment ID 확인
                    let commentsRef = Database.database().reference().child("comments").child(postId)
                    commentsRef.child(commentId).removeValue { error, _ in
                        if let error = error {
                            print("Failed to remove comment: \(error)")
                        } else {
                            print("Comment successfully removed!")
                            // 댓글을 배열에서 제거합니다.
                            self.comments = self.comments.filter { $0.id != commentId }
                            
                            // 테이블 뷰를 다시 로드합니다.
                            DispatchQueue.main.async {
                                tableView.reloadData()
                            }
                        }
                    }
                }
                let cancelAction = UIAlertAction(title: "취소", style: .cancel)
                
                alertController.addAction(deleteAction)
                alertController.addAction(cancelAction)
                
                self.present(alertController, animated: true) // 이 부분을 수정합니다.
            }
        }
    }
    
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath)
        let comment = comments[indexPath.row]
        cell.textLabel?.text = "\(comment.author ?? "익명"): \(comment.text)"
        return cell
    }
    
    func fetchLoggedInUserName(completion: @escaping (String?) -> Void) {
        guard let uid = Auth.auth().currentUser?.uid else {
            completion(nil)
            return
        }
        
        Database.database().reference().child("Users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
            if let dictionary = snapshot.value as? [String: AnyObject] {
                let username = dictionary["nickname"] as? String
                completion(username)
            }
        }, withCancel: nil)
    }
    
    
    @objc func sendButtonTapped() {
        guard let commentText = commentTextField.text, !commentText.isEmpty else {
            // 댓글 텍스트가 비어있으면 아무것도 하지 않습니다.
            return
        }

        guard let userId = Auth.auth().currentUser?.uid else {
            print("No user is signed in.")
            return
        }

        // userId에 해당하는 사용자의 닉네임을 가져옵니다.
        self.fetchUserName(with: userId) { [weak self] (username) in
            guard let self = self else { return }
            
            let commentsRef = Database.database().reference().child("comments").child(self.post?.postId ?? "")
            let newCommentRef = commentsRef.childByAutoId()
            let timestamp = Int(Date().timeIntervalSince1970 * 1000) // Firebase timestamp는 밀리초 단위입니다.
            let commentId = newCommentRef.key ?? "" // 여기에서 댓글의 ID를 가져옵니다.

            // 'author' 필드에 닉네임을 추가하고, 'id' 필드에 댓글의 ID를 추가합니다.
            let values: [String: Any] = ["text": commentText, "userId": userId, "timestamp": timestamp, "author": username ?? "", "id": commentId]
            
            newCommentRef.updateChildValues(values) { [weak self] (error, ref) in
                guard let self = self else { return }
                
                if let error = error {
                    print("Failed to save comment:", error)
                    return
                }

                // 댓글을 저장한 후에는 텍스트 필드를 비웁니다.
                DispatchQueue.main.async {
                    self.commentTextField.text = ""
                }

                // 댓글을 comments 배열에 추가하고, commentsTableView를 리로드합니다.
                let comment = Comment(dictionary: values)
                self.comments.append(comment)
                
                DispatchQueue.main.async {
                    self.commentsTableView.reloadData()
                }
            }
        }
    }
    
    
    @objc func goBack() {
        navigationController?.popViewController(animated: true)
    }
    
    @objc func imageViewTapped() {
        let imageViewController = UIViewController()
        imageViewController.view.backgroundColor = .black

        let imageView = UIImageView()
        imageView.frame = imageViewController.view.frame
        imageView.contentMode = .scaleAspectFit
        imageView.image = self.imageView.image // self.imageView는 클릭한 이미지뷰를 의미합니다.
        imageView.isUserInteractionEnabled = true
        imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage)))

        imageViewController.view.addSubview(imageView)
        self.present(imageViewController, animated: true)
    }
    
    @objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
        sender.view?.removeFromSuperview()
        self.dismiss(animated: true)
    }
    
    @objc func editButtonTapped() {
            fetchLoggedInUserName { [weak self] (username) in
                guard let self = self, let post = self.post, post.writer == username || username == "관장님" || username == "관리자"else { return }

                // 로그인한 사용자가 게시글 작성자인 경우 게시글 수정 로직을 수행합니다.
                let alertController = UIAlertController(title: "게시글 수정", message: "제목과 내용을 수정해주세요.", preferredStyle: .alert)

                alertController.addTextField { (textField) in
                    textField.text = post.title
                }
                alertController.addTextField { (textField) in
                    textField.text = post.content
                }

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

                alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: { [weak self] (_) in
                    guard let self = self else { return }
                    let title = alertController.textFields?[0].text ?? ""
                    let content = alertController.textFields?[1].text ?? ""

                    let postRef = Database.database().reference().child("posts").child(self.post?.postId ?? "")
                    let updatedPost = ["title": title, "content": content]
                    postRef.updateChildValues(updatedPost)

                    var updatedPostModel = self.post
                    updatedPostModel?.title = title
                    updatedPostModel?.content = content
                    self.post = updatedPostModel

                    // UI 요소를 업데이트합니다.
                    let text = self.post?.title ?? ""
                    if text.count > 8 {
                        let index = text.index(text.startIndex, offsetBy: 10)
                        self.titleLabel.text = String(text[..<index]) + "..."
                    } else {
                        self.titleLabel.text = text
                    }
                }))

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

        @objc func deleteButtonTapped() {
            fetchLoggedInUserName { [weak self] (username) in
                guard let self = self, let post = self.post, post.writer == username || username == "관장님" || username == "관리자" else { return }

                // 로그인한 사용자가 게시글 작성자인 경우 게시글 삭제 로직을 수행합니다.
                let alertController = UIAlertController(title: "게시글 삭제", message: "정말 삭제하시겠습니까?", preferredStyle: .alert)

                alertController.addAction(UIAlertAction(title: "예", style: .default, handler: { (_) in
                    let postRef = Database.database().reference().child("posts").child(post.postId ?? "")
                    postRef.removeValue()
                    
                    self.navigationController?.popViewController(animated: true)
                }))

                alertController.addAction(UIAlertAction(title: "아니오", style: .cancel, handler: nil))

                self.present(alertController, animated: true, completion: nil)
            }
        }
    
    @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 {
                // reportReason에 신고 사유가 저장됩니다.
                // 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.post?.writer ?? "",
                                  "postTitle": self.post?.title ?? "",
                                  "postContent": self.post?.content ?? "",
                                  "reportTime": reportTime] // 신고 시간을 추가합니다.
                reportRef.child(reportId).setValue(reportData)
            }
        }
        
        let cancelAction = UIAlertAction(title: "취소", style: .cancel)
        
        alertController.addAction(reportAction)
        alertController.addAction(cancelAction)
        
        self.present(alertController, animated: 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 dismissKeyboard() {
        self.view.endEditing(true)
    }
}

 

 

 

결과화면