Skip to content

Fix#67

Merged
gomminjae merged 1 commit intodevelopfrom
feat/error-handling
Mar 5, 2026
Merged

Fix#67
gomminjae merged 1 commit intodevelopfrom
feat/error-handling

Conversation

@gomminjae
Copy link
Copy Markdown
Owner

No description provided.

@gomminjae gomminjae merged commit 5004fa8 into develop Mar 5, 2026
@gomminjae gomminjae deleted the feat/error-handling branch March 5, 2026 12:08
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust and centralized error handling framework to improve the application's stability and user experience. It standardizes how errors are captured, logged, and presented to the user, particularly for network and server-side issues. The changes involve creating new error-related components, refactoring existing network layers, and integrating the new error handling capabilities into key ViewModels and UI elements throughout the application. Additionally, it includes minor UI adjustments for image rendering and article display, along with refinements to the authentication process.

Highlights

  • Centralized Error Handling System: A new, comprehensive error handling system has been implemented, introducing AppError for standardized error types, ErrorContext for detailed error logging, and an ErrorHandling protocol for consistent error management across ViewModels. This system includes a CoreErrorLogger for centralized logging.
  • Server Error Popup Integration: A new ServerErrorPopupView and ServerErrorPopupModifier have been added to the DesignSystem module. These components provide a standardized and user-friendly way to display server-related errors, with options to retry or navigate back.
  • ViewModel Error Handling Refactor: Numerous ViewModels across various feature modules (Auth, Bookmark, Detail, Explore, MyPage, Recovery, Search, Subscribe) have been updated to conform to the new ErrorHandling protocol. This refactoring centralizes error management, allowing ViewModels to automatically log errors and display appropriate UI feedback, such as the new server error popup.
  • Enhanced Network Error Mapping: The ErrorMapper in the Core module has been significantly improved to better parse server error responses and map various URLError and AFError types to specific NetworkError cases. This provides more granular control and clearer error messages for network-related issues.
  • Authentication Flow and Data Management Improvements: The application's authentication flow now includes better synchronization of login states with AppState and more comprehensive clearing of user data (tokens, user info, UserDefaults) upon receiving an unauthorized (401) error, enhancing security and data integrity.
  • Image Quality Enhancement: Image downsampling processors for KFImage have been updated across several UI components to utilize UIScreen.main.scale. This ensures that images are loaded at the appropriate resolution for the device's display, leading to sharper visuals.
  • Article Detail View Refinements: The ArticleDetailView received UI and functional enhancements, including the removal of manual safe area calculations, improved view animation, and a redesigned font size control slider with better visual feedback. Article rows now also display a highlight badge indicating saved highlights.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • Modules/App/Sources/NewdokApp.swift
    • Registered CoreErrorLogger for application-wide error logging.
  • Modules/AppCoordinator/Sources/Flow/AuthFlow/AppFlowCoordinatorView.swift
    • Synchronized login state with AppState.shared.login() when a valid token exists.
    • Expanded user data clearing in UserDefaults upon receiving an unauthorized (401) error.
  • Modules/Core/Core.xcodeproj/project.pbxproj
    • Added ErrorLogger.swift to the project files.
    • Updated build phases to include ErrorLogger.swift.
  • Modules/Core/Sources/API/ArticleAPI.swift
    • Added a new refresh case to ArticleAPI enum.
    • Updated path and task for ArticleAPI to handle the new refresh case.
  • Modules/Core/Sources/Log/ErrorLogger.swift
    • Added CoreErrorLogger struct conforming to ErrorLogging protocol for logging errors using Core.logError.
  • Modules/Core/Sources/Network/Core/Error/ErrorMapper.swift
    • Introduced ServerErrorBody struct to decode server error messages from response data.
    • Enhanced map(response:) to attempt decoding a structured server error body before falling back to raw string mapping.
    • Improved map(moyaError:) to more robustly extract URLError from MoyaError.underlying and AFError.sessionTaskFailed.
    • Added mapping for URLError codes like cannotConnectToHost, cannotFindHost, and dnsLookupFailed to NetworkError.serverError.
  • Modules/Core/Sources/Network/Core/Error/NetworkError.swift
    • Imported Shared module.
    • Extended NetworkError to conform to AppErrorConvertible, providing a mapping to AppError types.
  • Modules/Core/Sources/Network/Core/Plugin/AccessTokenPlugin.swift
    • Refactored didReceive to reliably extract statusCode from both success and failure MoyaError cases.
    • Integrated AppState.shared.logout() into the 401 error handling logic.
  • Modules/Core/Sources/Network/NetworkProvider.swift
    • Replaced TokenPlugin with AuthPlugin in the list of network plugins.
  • Modules/Data/Sources/Repository/ArticleRepositoryImpl.swift
    • Implemented the refresh() method to call the ArticleAPI.refresh endpoint.
  • Modules/DesignSystem/Derived/Sources/TuistAssets+DesignSystem.swift
    • Added pen image asset to DesignSystemAsset.
  • Modules/DesignSystem/DesignSystem.xcodeproj/project.pbxproj
    • Added ServerErrorPopupView.swift and ServerErrorPopupModifier.swift to the project files.
    • Updated build phases to include the new server error popup files.
  • Modules/DesignSystem/Resources/Assets.xcassets/pen.imageset/Contents.json
    • Added a new asset for a pen icon.
  • Modules/DesignSystem/Sources/Component/OverlayRootView.swift
    • Updated checkNetworkStatus() to use a one-shot NWPathMonitor probe for more accurate and immediate network status checks.
  • Modules/DesignSystem/Sources/Popup/System/ServerErrorPopupModifier.swift
    • Added ServerErrorPopupModifier to provide a SwiftUI view modifier for displaying ServerErrorPopupView based on an AppError binding.
  • Modules/DesignSystem/Sources/Popup/System/ServerErrorPopupView.swift
    • Added ServerErrorPopupView to display a generic server error message with optional 'Go Back' and 'Retry' actions.
  • Modules/Domain/Sources/Entity/Article/ArticleDetail.swift
    • Made ArticleDetail conform to the Equatable protocol.
  • Modules/Domain/Sources/Repository/ArticleRepository.swift
    • Added refresh() method to the ArticleRepository protocol.
  • Modules/Domain/Sources/UseCase/Auth/LoginUseCase.swift
    • Imported Shared module.
    • Extended LoginError to conform to AppErrorConvertible, mapping specific login errors to AppError types.
  • Modules/Domain/Sources/UseCase/Home/DefaultHomeBusinessUseCase.swift
    • Added logHomeError helper function for consistent error logging within the Home module.
    • Integrated logHomeError into loadToday(), loadMonthData(), fetchDataDays(), loadArticles(), and prefetchMonth() catch blocks.
    • Added fetchUseCase.refresh() call within refreshToToday() to trigger data refresh.
  • Modules/Domain/Sources/UseCase/Home/FetchHomeDataUseCase.swift
    • Added refresh() method to the FetchHomeDataUseCase protocol.
  • Modules/Domain/Sources/UseCase/Home/FetchHomeDataUseCaseImpl.swift
    • Implemented the refresh() method by calling articleRepo.refresh().
  • Modules/Domain/Sources/UseCase/User/ProfileUseCase.swift
    • Imported Shared module.
    • Extended ProfileError to conform to AppErrorConvertible, mapping userNotFound to an AppError.
  • Modules/Features/Auth/Sources/Login/View/LoginView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
  • Modules/Features/Auth/Sources/Login/View/ViewModel/LoginViewModel.swift
    • Conformed LoginViewModel to ErrorHandling protocol.
    • Added @Published var currentError: AppError? property.
    • Integrated handleError calls into login catch blocks.
    • Removed direct errorMessage assignment for network errors, relying on AppError mapping.
  • Modules/Features/Auth/Sources/Signup/Curation/CurationRow.swift
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Auth/Sources/Signup/SignupView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
  • Modules/Features/Auth/Sources/Signup/SignupViewModel.swift
    • Conformed SignupViewModel to ErrorHandling protocol.
    • Added @Published var currentError: AppError? property.
    • Integrated handleError calls into various catch blocks for verification, ID duplication check, interest submission, and signup.
    • Updated error message assignment to use currentError?.userFacingMessage.
  • Modules/Features/Bookmark/Sources/Bookmark/BookmarkView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with a 'retry' action.
  • Modules/Features/Bookmark/Sources/Bookmark/BookmarkViewModel.swift
    • Conformed BookmarkViewModel to ErrorHandling protocol.
    • Added @Published var currentError: AppError? property.
    • Refactored fetchUserInterests(), fetchUserBookmarks(), and loadInitial() to use the performAsync helper for consistent error handling and loading state management.
  • Modules/Features/Bookmark/Sources/Bookmark/Component/BookmarkCard.swift
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Detail/Detail.xcodeproj/project.pbxproj
    • Removed HighlightModel.swift from the project files and build phases, as it was moved to the Shared module.
  • Modules/Features/Detail/Sources/Article/ArticleDetailView.swift
    • Removed safeAreaTop calculation, relying on system safe areas.
    • Adjusted view animation for isViewReady.
    • Updated background modifier to ignore top safe area.
    • Changed onAppear to onChange(of: viewModel.detail) for setting isViewReady.
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
    • Refactored FontSizeControlView to use BorderedThumbSlider and improved button styling and state.
  • Modules/Features/Detail/Sources/Article/ArticleDetailViewModel.swift
    • Imported Shared module.
    • Conformed ArticleDetailViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Refactored fetch() and bookmark() to use the performAsync helper for consistent error handling and loading state management.
    • Removed debug print statements from loadHighlights().
  • Modules/Features/Detail/Sources/Article/HighlightListView.swift
    • Imported Shared module.
  • Modules/Features/Detail/Sources/Brand/BrandDetailView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Detail/Sources/Brand/BrandDetailViewModel.swift
    • Imported Shared module.
    • Conformed BrandDetailViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Refactored fetch(), guestFetch(), resume(), and pause() to use the performAsync helper for consistent error handling and loading state management.
  • Modules/Features/Explore/Sources/Explore/AllNewsletter/NewsletterDetailRow.swift
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Explore/Sources/Explore/ExploreView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with a 'retry' action.
  • Modules/Features/Explore/Sources/Explore/ExploreViewModel.swift
    • Conformed ExploreViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Refactored fetchRecommendation(), fetchAllNewsletters(), fetchBrandDetail(), and fetchGuestAllNewsletters() to use the performAsync helper for consistent error handling and loading state management.
  • Modules/Features/Explore/Sources/Explore/Recomend/NewletterRow.swift
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Explore/Sources/Explore/Recomend/RecommendedNewsLetterView.swift
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Home/Sources/Home/ArticleRow.swift
    • Imported Shared module.
    • Added @State private var highlightCount: Int to track the number of highlights for an article.
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
    • Displayed a highlightBadge with the count if highlightCount is greater than 0, otherwise showed '읽음'/'안읽음' status.
    • Added a task modifier to asynchronously fetch highlight count from HighlightStorage.
  • Modules/Features/MyPage/Sources/MyPage/MypageView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with a 'retry' action.
  • Modules/Features/MyPage/Sources/MyPage/MypageViewModel.swift
    • Conformed MypageViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Refactored fetchuserInfo(), updateNickname(), updateIndustry(), updateInterests(), updatePhoneNumber(), updatePassword(), and sendVerificationCode() to use the performAsync helper for consistent error handling and loading state management.
  • Modules/Features/MyPage/Sources/MyPage/Update/EditIndustryView.swift
    • Refactored the update action to use await viewModel.updateIndustry(id: selectedId) directly, relying on the ViewModel's ErrorHandling for error management.
  • Modules/Features/MyPage/Sources/MyPage/Update/EditInterestView.swift
    • Refactored the update action to use await viewModel.updateInterests(ids: Array(selectedIds)) directly, relying on the ViewModel's ErrorHandling for error management.
  • Modules/Features/MyPage/Sources/Recovery/RecoveryView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
  • Modules/Features/MyPage/Sources/Recovery/RecoveryViewModel.swift
    • Imported Shared module.
    • Conformed RecoveryViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Integrated handleError calls into findMyIds(), checkIdExists(), sendRecoveryCode(), and resetPassword() catch blocks.
  • Modules/Features/MyPage/Sources/Withdraw/WithdrawView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
  • Modules/Features/MyPage/Sources/Withdraw/WithdrawViewModel.swift
    • Conformed WithdrawViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Refactored fetchUserInfo() and withdraw() to use the performAsync helper for consistent error handling and loading state management.
  • Modules/Features/Search/Sources/Search/SearchView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with 'pop' and 'retry' actions.
  • Modules/Features/Search/Sources/Search/SearchViewModel.swift
    • Imported Shared module.
    • Conformed SearchViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Integrated handleError calls into loadPopularKeywords() and searchNewsletters() catch blocks.
    • Updated error message assignment to use currentError?.userFacingMessage.
  • Modules/Features/Subscribe/Sources/Subscribe/Component/SubscribeRow.swift
    • Updated KFImage processor to use UIScreen.main.scale for improved image downsampling quality.
  • Modules/Features/Subscribe/Sources/Subscribe/SubscribeView.swift
    • Integrated serverErrorPopup modifier to display viewModel.currentError with a 'retry' action.
  • Modules/Features/Subscribe/Sources/Subscribe/SubscribeViewModel.swift
    • Imported Shared module.
    • Conformed SubscribeViewModel to ErrorHandling protocol.
    • Added @Published public var currentError: AppError? property.
    • Refactored loadInitial(), refresh(), pause(), and resume() to use the performAsync helper for consistent error handling and loading state management.
  • Modules/Shared/Shared.xcodeproj/project.pbxproj
    • Added AppError.swift, ErrorContext.swift, and ErrorHandling.swift to the project files.
    • Moved HighlightModel.swift from Detail module to Shared module and updated project references.
    • Organized new error-related files into a dedicated 'Error' group.
  • Modules/Shared/Sources/Error/AppError.swift
    • Added AppError enum to define standardized application-level errors with user-facing messages and UI behavior flags.
    • Added AppErrorConvertible protocol for converting raw errors to AppError.
  • Modules/Shared/Sources/Error/ErrorContext.swift
    • Added ErrorContext struct to encapsulate error details for logging, including underlying error, feature, operation, and metadata.
    • Added ErrorLogging protocol for defining error logging functionality.
    • Added ErrorLoggerRegistry for registering and accessing a shared error logger instance.
  • Modules/Shared/Sources/Error/ErrorHandling.swift
    • Added ErrorHandling protocol for ViewModels to adopt, providing a currentError property.
    • Added an extension to ErrorHandling with handleError function for logging and setting currentError.
    • Added performAsync helper function to wrap asynchronous operations, automatically managing loading states and error handling.
  • Modules/Shared/Sources/Highlight/HighlightModel.swift
    • Moved HighlightModel.swift from Modules/Features/Detail/Sources/Article to Modules/Shared/Sources/Highlight.
Activity
  • The pull request was created by gomminjae.
  • No specific review comments or further activity details were provided in the context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This PR introduces a comprehensive error handling and logging system across the app, improves UI on several screens, and refactors some features, aiming to significantly enhance code stability and maintainability. While the overall structure is sound, two security concerns were identified: unauthorized PII exposure via automatic clipboard copying and potential sensitive data leakage in application logs due to overly verbose error mapping. Additionally, some screens lack retry logic for errors, and the new error handling patterns are not consistently applied throughout the codebase. Addressing these points will improve user privacy, prevent accidental data exposure, and ensure a more robust and consistent error handling implementation.

Comment on lines +114 to +115
onGoBack: { router.pop() },
onRetry: { Task { await viewModel.loadPopularKeywords() } }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

서버 오류 발생 시 재시도 로직이 loadPopularKeywords()만 호출하도록 고정되어 있습니다. 만약 사용자가 검색어를 입력한 후 검색 과정에서 오류가 발생했다면, 재시도 시 인기 검색어를 다시 불러오는 것이 아니라 현재 입력된 검색어로 검색을 재시도해야 합니다. viewModel.searchText의 상태에 따라 분기하여 적절한 재시도 동작을 수행하도록 수정해야 합니다.

            onGoBack: { router.pop() },
            onRetry: { 
                Task {
                    if viewModel.searchText.isEmpty {
                        await viewModel.loadPopularKeywords()
                    } else {
                        await viewModel.searchNewsletters()
                    }
                }
            }

Comment on lines 28 to 29
let message = try? response.mapString()
return .serverError(statusCode: response.statusCode, message: message)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The ErrorMapper.map(response:) function maps the entire response body to a string and includes it in the NetworkError.serverError message if the response does not match the expected ServerErrorBody format. This error message is subsequently logged by the ErrorLogger. If the server response contains sensitive information (such as PII, tokens, or internal system details) in the body during an error condition, this information will be written to the application logs.

Comment on lines +81 to +84
UserDefaults.standard.set(false, forKey: "isLoggedIn")
UserDefaults.standard.set(false, forKey: "isGuest")
UserDefaults.standard.removeObject(forKey: "nickname")
UserDefaults.standard.removeObject(forKey: "email")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

UserDefaults에 사용되는 키를 문자열 리터럴로 직접 사용하는 것은 오타에 취약하고 유지보수를 어렵게 만듭니다. 이러한 키들을 별도의 상수나 열거형으로 관리하여 타입 안정성을 높이고 재사용성을 개선하는 것이 좋습니다.

private enum UserDefaultsKeys {
    static let isLoggedIn = "isLoggedIn"
    static let isGuest = "isGuest"
    static let nickname = "nickname"
    static let email = "email"
}

// ...

UserDefaults.standard.set(false, forKey: UserDefaultsKeys.isLoggedIn)
UserDefaults.standard.set(false, forKey: UserDefaultsKeys.isGuest)
UserDefaults.standard.removeObject(forKey: UserDefaultsKeys.nickname)
UserDefaults.standard.removeObject(forKey: UserDefaultsKeys.email)

.serverErrorPopup(
error: $viewModel.currentError,
onGoBack: { router.pop() },
onRetry: {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

서버 오류 발생 시 사용자가 재시도할 수 있도록 onRetry 클로저를 구현해야 합니다. 현재 비어있어 오류 발생 시 사용자가 아무런 조치를 취할 수 없습니다. 로그인 로직을 다시 호출하도록 수정하는 것을 제안합니다.

            onRetry: { 
                viewModel.login {
                    isLoggedIn = true
                    isGuest = false
                    router.resetTo(.tabbar(selectedTab: .home))
                }
            }

.serverErrorPopup(
error: $viewModel.currentError,
onGoBack: { router.pop() },
onRetry: {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

서버 오류 팝업의 onRetry 핸들러가 비어있습니다. 회원가입 과정에서 네트워크 오류 등이 발생했을 때 사용자가 재시도를 할 수 없어 불편을 겪을 수 있습니다. 현재 단계에 맞는 재시도 로직을 구현해야 합니다. 예를 들어, viewModel에 마지막으로 시도한 작업을 재실행하는 메서드를 추가하는 것을 고려해볼 수 있습니다.

Comment on lines 70 to 77
loadTask = Task { @MainActor in
isLoading = true
async let interests = useCase.fetchBookmarkedInterests()
async let articles = useCase.fetchBookmarkedArticles(interest: interest, sortBy: convertSortOrderToOption(sortOrder))
do {
self.interests = try await interests
self.bookmarks = try await articles
} catch {
await performAsync(feature: "bookmark", operation: "loadInitial", loadingBinding: \.isLoading) {
async let fetchedInterests = useCase.fetchBookmarkedInterests()
async let fetchedArticles = useCase.fetchBookmarkedArticles(interest: interest, sortBy: convertSortOrderToOption(sortOrder))
self.interests = try await fetchedInterests
self.bookmarks = try await fetchedArticles
}
isLoading = false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

performAsync 헬퍼를 사용하여 비동기 작업과 오류 처리를 일관되고 간결하게 잘 리팩터링했습니다. 코드 가독성과 유지보수성이 크게 향상되었습니다.

Comment on lines +168 to 170
.onChange(of: viewModel.detail) { detail in
if detail != nil { isViewReady = true }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

데이터가 로드된 후 뷰를 표시하는 로직을 onAppearasyncAfter 대신 onChange(of: viewModel.detail)로 변경한 점이 좋습니다. 이 방식은 데이터의 상태 변화에 직접 반응하므로 더 안정적이고 선언적입니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant