Skip to content

Conversation

@JinUng41
Copy link
Collaborator

@JinUng41 JinUng41 commented Mar 23, 2025

👻 PULL REQUEST

📄 작업 내용

  • 소식 탭의 각 화면들을 구현하였습니다.

💻 주요 코드 설명

뷰컨트롤러 하나로 또는 분리해서,,,

  • 비교적 간단한 화면은 뷰컨트롤러 하나로 화면을 구현하였습니다. (컬렉션뷰 등)
  • 복잡한 화면에 대해서만 뷰컨트롤러와 루트뷰를 분리하여 구현하였습니다. (AnnouncementDetail에 해당)
  • 루트뷰의 컴포넌트에 접근할 때, 코드를 비교적 간단하게 표현하기 위해 계산 속성을 활용하여 보았습니다.
AnnouncementDetailViewController.swift
import UIKit
import SafariServices

import Kingfisher

final class AnnouncementDetailViewController: UIViewController {
    
    // MARK: - AnnouncementType
    
    enum AnnouncementType {
        case news
        case notice
        
        fileprivate var navigationTitle: String {
            switch self {
            case .news:
                return "뉴스"
            case .notice:
                return "공지사항"
            }
        }
    }
    
    // MARK: - Property
    
    private let rootView = AnnouncementDetailView()
    
    // MARK: - Life Cycle
    
    override func loadView() {
        view = rootView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupAction()
        setupDelegate()
    }
    
    func configure(
        type: AnnouncementType,
        title: String,
        time: String,
        imageURL: URL?,
        bodyText: String
    ) {
        navigationTitleLabel.text = type.navigationTitle
        titleLabel.text = title
        timeLabel.text = time
        bodyTextView.text = bodyText
        
        submitButtonContainerView.isHidden = !(type == .notice)
        
        guard let url = imageURL else { return }
        imageView.isHidden = false
        imageView.kf.setImage(with: url)
    }
}

// MARK: - UIGestureRecognizerDelegate

extension AnnouncementDetailViewController: UIGestureRecognizerDelegate {
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return navigationController?.viewControllers.count ?? 0 > 1
    }
}

// MARK: - Setup Method

private extension AnnouncementDetailViewController {
    func setupAction() {
        navigationBackButton.addTarget(self, action: #selector(backButtonDidTap), for: .touchUpInside)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewDidTap))
        imageView.addGestureRecognizer(tapGesture)
        
        submitButton.addTarget(self, action: #selector(submitButtonDidTap), for: .touchUpInside)
    }
    
    func setupDelegate() {
        navigationController?.interactivePopGestureRecognizer?.delegate = self
    }
}

// MARK: - Action Method

private extension AnnouncementDetailViewController {
    @objc func backButtonDidTap() {
        navigationController?.popViewController(animated: true)
    }
    
    @objc func imageViewDidTap() {
        guard let image = imageView.image else { return }
        
        let photoDetailViewController = PhotoDetailViewController(image: image)
        present(photoDetailViewController, animated: true)
    }
    
    @objc func submitButtonDidTap() {
        guard let url = URL(string: Constant.googleFormURLText) else { return }
        
        let safariController = SFSafariViewController(url: url)
        present(safariController, animated: true)
    }
}

// MARK: - Computed Property

private extension AnnouncementDetailViewController {
    var navigationBackButton: UIButton { rootView.navigationBackButton }
    var navigationTitleLabel: UILabel { rootView.navigationTitleLabel }
    var titleLabel: UILabel { rootView.titleLabel }
    var timeLabel: UILabel { rootView.timeLabel }
    var imageView: UIImageView { rootView.imageView }
    var bodyTextView: UITextView { rootView.bodyTextView }
    var submitButtonContainerView: UIView { rootView.submitButtonContainerView }
    var submitButton: UIButton { rootView.submitButton }
}

// MARK: - Constant

private extension AnnouncementDetailViewController {
    enum Constant {
        static let googleFormURLText: String = "https://docs.google.com/forms/d/e/1FAIpQLSf3JlBkVRPaPFSreQHaEv-u5pqZWZzk7Y4Qll9lRP0htBZs-Q/viewform"
    }
}

🔗 연결된 이슈

@JinUng41 JinUng41 added ✨ feat 기능 또는 객체 구현 🍻 진웅 술 한잔 가온나~ labels Mar 23, 2025
@JinUng41 JinUng41 added this to the 리팩토링 마감 milestone Mar 23, 2025
@JinUng41 JinUng41 requested a review from youz2me March 23, 2025 13:08
@JinUng41 JinUng41 self-assigned this Mar 23, 2025
@JinUng41 JinUng41 moved this to Todo in Wable-iOS Mar 23, 2025
@JinUng41 JinUng41 moved this from Todo to In Review in Wable-iOS Mar 23, 2025
Copy link
Member

@youz2me youz2me left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 ~~! 몇가지 코멘트만 확인 부탁드려요 ^_^

setupConstraint()
}

@available(*, unavailable)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

외부 뷰컨에서 호출이 불가능하도록 해두셨군요! 굿

Comment on lines 27 to 30
typealias DataSource = UICollectionViewDiffableDataSource<Section, Item>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Item>
typealias CellRegistration = UICollectionView.CellRegistration
typealias SupplementaryRegistration = UICollectionView.SupplementaryRegistration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 프로퍼티들은 다른 곳에서도 사용하니 따로 모아둬도 좋을 것 같아요!

Comment on lines +148 to +151
// let gameDateFormatter = DateFormatter().then {
// $0.dateFormat = "MM.dd (E)"
// $0.locale = Locale(identifier: "ko_KR")
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 필요 없으면 삭제 부탁드려요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 로직을 위해 남겨두었습니다.
제가 Todo 주석을 깜빡했습니다.

Comment on lines +193 to +194
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 프로퍼티들도 개행한다면 여기도 개행해도 좋을 것 같은 ... 느낌이네요 반영은 안하셔도 괜춘합니다

Comment on lines +189 to +214
var gameTypeSection: NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)

let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(40)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitems: [item]
)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 24, leading: 16, bottom: 20, trailing: 16)

return section
}

var gameScheduleSection: NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itemSize처럼 중복되는 프로퍼티는 합쳐서 사용해도 좋을 것 같아용


private let labelStackView: UIStackView = .init(axis: .vertical).then {
$0.alignment = .leading
$0.distribution = .fillProportionally
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이런 것도 있군요 !!

Comment on lines 25 to 32
override init(frame: CGRect) {
super.init(frame: frame)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 안써도 될 것 같아요 !!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 setup~ 메서드를 호출하는 것을 깜빡했네요.
알려주셔서 감사합니다!

Comment on lines 98 to 127
func elapsedString(from date: Date?) -> String {
guard let date else {
return "지금"
}

let now = Date()
let calendar = Calendar.current

let components = calendar.dateComponents(
[.minute, .hour, .day, .weekOfMonth, .month, .year],
from: date,
to: now
)

if let years = components.year, years > 0 {
return "\(years)년 전"
} else if let months = components.month, months > 0 {
return "\(months)달 전"
} else if let weeks = components.weekOfMonth, weeks > 0 {
return "\(weeks)주 전"
} else if let days = components.day, days > 0 {
return "\(days)일 전"
} else if let hours = components.hour, hours > 0 {
return "\(hours)시간 전"
} else if let minutes = components.minute, minutes > 0 {
return "\(minutes)분 전"
} else {
return "지금"
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 홈 화면에서도 사용하는데 따로 분리해도 괜찮을 것 같습니닷

그리고 switch문으로 처리해도 좋을 것 같네요 ~~!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 플젝의 코드를 그대로 차용하다 보니 이렇게 작성되었네요.
저도 Date의 익스텐션으로 빼도 좋을 것 같습니다.

Comment on lines +110 to +116
// MARK: - backgroundColor Initializer

convenience init(backgroundColor: UIColor) {
self.init()

self.backgroundColor = backgroundColor
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캬 감사합니다 ~~! 더 간편해졌네요

- Date 객체로붙터 얼마나 지났는지 string을 반환하는 계산 속성 구현
@JinUng41 JinUng41 merged commit ad52dca into develop Mar 23, 2025
@github-project-automation github-project-automation bot moved this from In Review to Done in Wable-iOS Mar 23, 2025
@JinUng41 JinUng41 deleted the feat/#130-overview branch March 23, 2025 16:55
youz2me pushed a commit that referenced this pull request Oct 26, 2025
[Feat] 소식 탭 화면 구현
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feat 기능 또는 객체 구현 🍻 진웅 술 한잔 가온나~

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Feat] 소식 탭 화면 구현

3 participants