Skip to content

[Feat] #493 - 신규 홈뷰 - 화면 전환 구현 #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,15 @@ public extension UILabel {
}
}

func setLineSpacingWithChaining(lineSpacing: CGFloat) -> UILabel {
let label = self
func setLineSpacingWithChaining(lineSpacing: CGFloat) {
Copy link
Contributor

Choose a reason for hiding this comment

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

함수명을 보니 해당 함수는 메소드 체이닝을 위해 반환값을 만들어둔 것 같아요. 반환값이 필요없다면 같은 역할을 하는 setLineSpacing 함수를 사용하는 것이 좋을 듯 합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ㅋ ㅋ 원래대로 돌려놓을게요 오 오 ..

if let text = self.text {
let style = NSMutableParagraphStyle()
style.lineSpacing = lineSpacing
let attributes: [NSAttributedString.Key: Any] = [
.paragraphStyle: style
]
label.attributedText = NSAttributedString(string: text, attributes: attributes)
self.attributedText = NSAttributedString(string: text, attributes: attributes)
}
return label
}

/// 자간 설정 메서드
Expand Down Expand Up @@ -83,9 +81,10 @@ public extension UILabel {
/// - defaultFont, defaultColor에는 기본 폰트와 컬러를 넣어주세요
func htmlToString(targetString: String,
defaultFont: UIFont,
boldFont: UIFont,
boldFont: UIFont?,
defaultColor: UIColor) {
let text = targetString
let boldFont = boldFont ?? defaultFont
guard let data = text.data(using: .utf8) else { return }

do {
Expand Down
6 changes: 6 additions & 0 deletions SOPT-iOS/Projects/Core/Sources/Literals/StringLiterals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ public struct I18N {
public struct Home {
public static let viewAll = "전체보기"

public struct PopUp {
public static let login = "로그인"
public static let needToLogin = "로그인이 필요해요"
public static let needToLoginDetail = "앱 서비스는 로그인을 해야만 사용할 수 있어요.\n솝트 회원이라면 로그인해주세요."
}

public struct DashBoard {
public struct UserHistory {
public static let visitor = "비회원"
Expand Down
29 changes: 28 additions & 1 deletion SOPT-iOS/Projects/Data/Sources/Repository/HomeRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,51 @@ public class HomeRepository {

private let homeService: HomeService
private let calendarService: CalendarService
private let userService: UserService

private let cancelBag = CancelBag()

public init(homeService: HomeService,
calendarService: CalendarService
calendarService: CalendarService,
userService: UserService
) {
self.homeService = homeService
self.calendarService = calendarService
self.userService = userService
}
}

extension HomeRepository: HomeRepositoryInterface {

public func getAppServices() -> AnyPublisher<[Domain.HomeAppServicesModel], any Error> {
homeService.getAppServiceAccessStatus()
.map { $0.map { $0.toDomain() } }
.eraseToAnyPublisher()
}

public func getUserInfo() -> AnyPublisher<Domain.UserMainInfoModel?, Domain.MainError> {
userService.getUserMainInfo()
.mapError { error -> MainError in
guard let error = error as? APIError else {
return MainError.networkError(message: "Moya 에러")
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분을 Moya 에러 대신 더 명확하게 표현하는건 어떨까요? 현재는 MoyaError가 아닌 DecodingError, NSError도 이 부분에서 처리되는 것 같아요.

UserMainInfoModel의 CodingKey를 임의로 수정해 디코딩 오류가 발생하도록 테스트해 본 결과, Service 계층의 requestObjectWithNetworkErrorInCombine에서 디코딩 에러로 진입했습니다.

image

그렇기 때문에 APIError가 아닌 모든 에러를 Moya 에러로 처리하는 것은 부정확하다고 생각해요.

Copy link
Contributor

Choose a reason for hiding this comment

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

제가 생각한 대안책은 두 가지가 있습니다.

1️⃣ requestObjectWithNetworkErrorInCombine의 Error를 모두 MoyaError로 감싸기
APIError가 아닌 부분은 모두 MoyaError로 처리할 수 있어 에러 처리가 명확해져요.

// requestObjectWithNetworkErrorInCombine
// AS-IS
promise(.failure(error))

// TO-BE
promise(.failure(MoyaError.objectMapping(error, value)))
// HomeRepository
...
guard let error as? APIError else {
    return MainError.networkError(message: "MoyaError \(error.localizedDescription)")
}

실제 MoyaError로 처리되는지 테스트하기 위해 MoyaError 코드를 수정해보았어요.
image

실행 결과
image

단점은 적절한 MoyaError를 지정해주어야한다는 것인데요, 번거로움을 줄이기 위해 error.localizedDescription자체를 반환하는 MoyaError.underlying으로 모두 감싸는 것이 더 좋아보여요.

2️⃣ error.localizedDescription 를 반환
APIError가 아닌 경우 에러 타입에 관계없이 error.localizedDescription을 반환합니다.
개인적으로는 default 부분에서 error.localizedDescription을 반환하고, 해당 부분에서는 MoyaError로 감싸는 방식이 더 명확하다고 생각해요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

앗 말씀해주신대로 반영하고 다시 리뷰달겠습니다! 감사합니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

2번 방식으로 수정해두었어용

}

switch error {
case .network(let statusCode, _):
if statusCode == 401 {
return MainError.authFailed
}
return MainError.networkError(message: "\(statusCode) 네트워크 에러")
case .tokenReissuanceFailed:
return MainError.authFailed
default:
return MainError.networkError(message: "API 에러 디폴트")
}
}
.map { $0.toDomain() }
.eraseToAnyPublisher()
}

public func getInsightPosts() -> AnyPublisher<[Domain.HomeInsightPostsModel], any Error> {
homeService.getInsightPosts()
.map { $0.map { $0.toDomain() } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ extension AppDelegate {
implement: {
HomeRepository(
homeService: DefaultHomeService(),
calendarService: DefaultCalendarService()
calendarService: DefaultCalendarService(),
userService: DefaultUserService()
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Core

public protocol HomeRepositoryInterface {
func getHomeDescription() -> AnyPublisher<HomeDescriptionModel, Error>
func getUserInfo() -> AnyPublisher<UserMainInfoModel?, MainError>
func getRecentSchedule() -> AnyPublisher<HomeRecentScheduleModel, Error>
func getAppServices() -> AnyPublisher<[HomeAppServicesModel], Error>
func getInsightPosts() -> AnyPublisher<[HomeInsightPostsModel], Error>
Expand Down
8 changes: 8 additions & 0 deletions SOPT-iOS/Projects/Domain/Sources/UseCase/HomeUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Core

public protocol HomeUseCase {
func getHomeDescription() -> AnyPublisher<HomeDescriptionModel, Never>
func getUserInfo() -> AnyPublisher<UserMainInfoModel?, Never>
func getRecentSchedule() -> AnyPublisher<HomeRecentScheduleModel, Never>
func getAppServices() -> AnyPublisher<[HomeAppServicesModel], Never>
func getInsightPosts() -> AnyPublisher<[HomeInsightPostsModel], Never>
Expand All @@ -38,6 +39,13 @@ extension DefaultHomeUseCase: HomeUseCase {
}.eraseToAnyPublisher()
}

public func getUserInfo() -> AnyPublisher<UserMainInfoModel?, Never> {
repository.getUserInfo()
.catch { error in
return Empty<UserMainInfoModel?, Never>()
}.eraseToAnyPublisher()
}

public func getRecentSchedule() -> AnyPublisher<HomeRecentScheduleModel, Never> {
repository.getRecentSchedule()
.catch { error in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import Core
public protocol HomeForMemberViewControllable: ViewControllable { }
public protocol HomeForMemberCoordinatable {
var onDashBoardCellTapped: (() -> Void)? { get set }

var onCalendarCellTapped: (() -> Void)? { get set }
var onAttendanceButtonTapped: (() -> Void)? { get set }
var onMainProductCellTapped: ((String) -> Void)? { get set }
var onAppServiceCellTapped: ((String) -> Void)? { get set }
var onNotificationButtonTapped: (() -> Void)? { get set }
var onSettingButtonTapped: ((UserType) -> Void)? { get set }
}
public typealias HomeForMemberViewModelType = ViewModelType & HomeForMemberCoordinatable
public typealias HomeForMemberPresentable = (vc: HomeForMemberViewControllable, vm: any HomeForMemberViewModelType)
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import Core

public protocol HomeForVisitorViewControllable: ViewControllable { }
public protocol HomeForVisitorCoordinatable {

var onMainProductCellTapped: ((String) -> Void)? { get set }
var onAppServiceCellTapped: (() -> Void)? { get set }
var onSettingButtonTapped: ((UserType) -> Void)? { get set }
}
public typealias HomeForVisitorViewModelType = ViewModelType & HomeForVisitorCoordinatable
public typealias HomeForVisitorPresentable = (vc: HomeForVisitorViewControllable, vm: any HomeForVisitorViewModelType)
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,7 @@ extension AnnouncementCardCVC {
// MARK: - Methods

extension AnnouncementCardCVC {
func configureCell(model: HomePresentationModel.Announcement?) {
guard let model else { return }

func configureCell(model: HomePresentationModel.Announcement) {
self.categoryTagView.setData(with: model.categoryName, isHotTag: false)
self.categoryDetailLabel.text = "꿀팁"
if let profileImage = model.profileImage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ import DSKit

final class CalendarCardCVC: UICollectionViewCell {

// MARK: - Properties

lazy var attendanceButtonTap = attendanceButton.publisher(for: .touchUpInside)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
lazy var attendanceButtonTap = attendanceButton.publisher(for: .touchUpInside)
private(set) lazy var attendanceButtonTap = attendanceButton.publisher(for: .touchUpInside)


// MARK: - UI Components

private let dateLabel = UILabel().then {
$0.text = "10.22"
$0.textColor = DSKitAsset.Colors.gray400.color
$0.font = DSKitFontFamily.Suit.semiBold.font(size: 14)
}

private let scheduleCategoryTagView = HomeSquareTagView()

private let scheduleTitleLabel = UILabel().then {
$0.text = "1차 행사"
$0.textColor = DSKitAsset.Colors.white.color
$0.font = DSKitFontFamily.Suit.semiBold.font(size: 16)
}
Expand Down Expand Up @@ -126,7 +128,7 @@ extension CalendarCardCVC {
extension CalendarCardCVC {
func configureCell(model: HomePresentationModel.RecentSchedule,
userType: UserType) {
self.dateLabel.text = model.date
self.dateLabel.text = setDateFormat(date: model.date, to: "MM.dd")
Copy link
Contributor

Choose a reason for hiding this comment

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

dateFormatter를 매번 생성하는 것이 성능상 좋지 않다고 알고있는데, 이 부분 주석으로 메모해두고 추후 서버 선생님들께 바꿔 내려달라고 하는건 어떠신가요 ? 저희도 dateFormatter를 재활용하도록 추후 리팩토링 해봅시다 ㅋ.ㅋ
참고링크

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아 이거 저번에 dateFormatter 성능 관련해 얘기하면서 ... 서버에 요청하기로 구두로 얘기했던 것 같은데 (그래서 안 고쳐놨었나봐요 ㅋㅋ ) 얘도 서버 쌤들이 바꿔주시고 나면 원래대로 돌려놓겠습니다!

self.scheduleTitleLabel.text = model.title
if let tagType = CalenderCategoryTagType(rawValue: model.type) {
self.scheduleCategoryTagView.setData(title: tagType.text,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,23 @@ extension DashBoardCardCVC {
// MARK: - Methods

extension DashBoardCardCVC {
func configureCell(userType: UserType, description: String? = nil) {
func configureCell(userType: UserType, model: HomePresentationModel.DashBoard? = nil) {
switch userType {
case .visitor:
self.descriptionLabel.font = DSKitFontFamily.Suit.medium.font(size: 18)
self.descriptionLabel.text = I18N.Home.DashBoard.UserHistory.encourage
self.descriptionLabel.setLineSpacing(lineSpacing: 5)
self.rightArrowWithCircleImageView.isHidden = true
case .active, .inactive:
guard let description else { return }
self.descriptionLabel.htmlToString(targetString: description,
guard let model else { return }
self.descriptionLabel.htmlToString(targetString: model.description,
defaultFont: DSKitFontFamily.Suit.medium.font(size: 18),
boldFont: DSKitFontFamily.Suit.bold.font(size: 18),
defaultColor: DSKitAsset.Colors.white100.color)
self.descriptionLabel.setLineSpacingWithChaining(lineSpacing: 5)
self.rightArrowWithCircleImageView.isHidden = false
guard let historyList = model.historyList else { return }
userHistoryView.setData(userType: userType, recentHistory: 35, allHistory: historyList)
}

userHistoryView.setData(userType: userType, recentHistory: 35, allHistory: [35, 34, 33, 32, 31, 30, 29])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ enum SocialLinkCardType: CaseIterable, Identifiable {
case instagram
case youtube

var id: UUID {
return UUID()
}
var id: Self { self }

var image: UIImage {
switch self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension HomeForMemberVC {

static let productItemSpacing: Double = 15
static let appServiceItemSpacing: Double = 16
static let mainProductSectionSpacing: Double = 44
static let announcementWidth: Double = 300
}

Expand Down Expand Up @@ -112,7 +113,7 @@ extension HomeForMemberVC {
let section = NSCollectionLayoutSection(group: productGroup)
section.contentInsets = NSDirectionalEdgeInsets(top: Metric.defaultItemSpacing,
leading: Metric.collectionViewDefaultSideInset,
bottom: 40,
bottom: Metric.mainProductSectionSpacing,
trailing: Metric.collectionViewDefaultSideInset)

return section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extension HomeForVisitorVC {
static let defaultSectionSpacing: Double = 36

static let productItemSpacing: Double = 15
static let mainProductSectionSpacing: Double = 44
static let appServiceItemSpacing: Double = 16
}

Expand Down Expand Up @@ -84,7 +85,7 @@ extension HomeForVisitorVC {
section.boundarySupplementaryItems = [header]
section.contentInsets = NSDirectionalEdgeInsets(top: Metric.defaultItemSpacing,
leading: Metric.collectionViewDefaultSideInset,
bottom: 40,
bottom: Metric.mainProductSectionSpacing,
trailing: Metric.collectionViewDefaultSideInset)

return section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

enum HomeForMemberItem: Hashable {
case description(HomePresentationModel.Description)
case dashBoard(HomePresentationModel.DashBoard)
case recentSchedule(HomePresentationModel.RecentSchedule)
case productService(HomePresentationModel.ProductService)
case appService(HomePresentationModel.AppService)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

enum HomeForVisitorItem: Hashable {
case description(HomePresentationModel.Description)
case dashBoard(HomePresentationModel.DashBoard)
case productService(HomePresentationModel.ProductService)
case appService(HomePresentationModel.AppService)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,75 @@

import UIKit

import Combine

extension HomeForMemberVC {
// cells
func createDashBoardCellRegistration() -> UICollectionView.CellRegistration<DashBoardCardCVC, HomePresentationModel.Description> {
func createDashBoardCellRegistration() -> DashBoardCardCellRegistration {
collectionView.createCellRegistration { [weak self] cell, _, item in
guard let self else { return }
cell.configureCell(userType: self.viewModel.userType, description: item.description)
cell.configureCell(userType: self.viewModel.userType, model: item)
}
}

func createCalendarCellRegistration() -> UICollectionView.CellRegistration<CalendarCardCVC, HomePresentationModel.RecentSchedule> {
func createCalendarCellRegistration() -> CalendarCellRegistration {
collectionView.createCellRegistration { [weak self] cell, _, item in
guard let self else { return }
cell.configureCell(model: item, userType: self.viewModel.userType)

cell.attendanceButtonTap
.withUnretained(self)
.sink { owner, _ in
owner.attendanceButtonTapped.send()
}
.store(in: cancelBag)
}
}

func createProductCellRegistration() -> UICollectionView.CellRegistration<MainProductCardCVC, HomePresentationModel.ProductService> {
func createProductCellRegistration() -> ProductCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(title: item.name, image: item.image)
}
}

func createAppServiceCellRegistration() -> UICollectionView.CellRegistration<AppServiceCardCVC, HomePresentationModel.AppService> {
func createAppServiceCellRegistration() -> AppServiceCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(model: item)
}
}

func createInsightCellRegistration() -> UICollectionView.CellRegistration<InsightCardCVC, HomePresentationModel.InsightPost> {
func createInsightCellRegistration() -> InsightCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(model: item)
}
}

func createGroupCellRegistration() -> UICollectionView.CellRegistration<GroupCardCVC, HomePresentationModel.GroupPost> {
func createGroupCellRegistration() -> GroupCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(model: item)
}
}

func createCoffeeChatCellRegistration() -> UICollectionView.CellRegistration<CoffeeChatCardCVC, HomePresentationModel.CoffeeChat> {
func createCoffeeChatCellRegistration() -> CoffeeChatCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(model: item)
}
}

func createAnnouncementCellRegistration() -> UICollectionView.CellRegistration<AnnouncementCardCVC, HomePresentationModel.Announcement> {
func createAnnouncementCellRegistration() -> AnnouncementCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(model: item)
}
}

func createSocialLinkCellRegistration() -> UICollectionView.CellRegistration<SocialLinkCardCVC, SocialLinkCardType> {
func createSocialLinkCellRegistration() -> SocialLinkCellRegistration {
collectionView.createCellRegistration { cell, _, item in
cell.configureCell(type: item)
}
}

// supplementary views
func createHeaderRegistration() -> UICollectionView.SupplementaryRegistration<HomeDefaultHeaderView> {
func createHeaderRegistration() -> HeaderRegistration {
collectionView.createSupplementaryRegistration(
elementKind: UICollectionView.elementKindSectionHeader
) { headerView, indexPath in
Expand All @@ -76,7 +85,7 @@ extension HomeForMemberVC {
}
}

func createFooterRegistration() -> UICollectionView.SupplementaryRegistration<AnnouncementPageContolFooterView> {
func createFooterRegistration() -> FooterRegistration {
collectionView.createSupplementaryRegistration(
elementKind: UICollectionView.elementKindSectionFooter
) { [weak self] footerView, indexPath in
Expand Down
Loading