Skip to content

Commit

Permalink
feat: AlertBuilder 추가, Error Localization 처리
Browse files Browse the repository at this point in the history
  • Loading branch information
maxhyunm committed Sep 17, 2023
1 parent 2b7e4a2 commit 0f372e1
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 161 deletions.
24 changes: 10 additions & 14 deletions Diary.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
632F74F22AB14D8D003E1B97 /* WeatherResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632F74F12AB14D8D003E1B97 /* WeatherResult.swift */; };
632F74F42AB14DBC003E1B97 /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632F74F32AB14DBC003E1B97 /* APIError.swift */; };
636B19AC2AA6C5E900B5242D /* AlertDisplayble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 636B19AB2AA6C5E900B5242D /* AlertDisplayble.swift */; };
63B12BAE2AAD9C9000D614A6 /* AlertNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B12BAD2AAD9C9000D614A6 /* AlertNamespace.swift */; };
63B12BB02AAD9D3400D614A6 /* ButtonNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B12BAF2AAD9D3400D614A6 /* ButtonNamespace.swift */; };
63BB62822A9F109400524DCB /* DecodingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63BB62812A9F109400524DCB /* DecodingManager.swift */; };
63BB62B22AA181BE00524DCB /* Diary+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63BB62B02AA181BE00524DCB /* Diary+CoreDataClass.swift */; };
63BB62B32AA181BE00524DCB /* Diary+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63BB62B12AA181BE00524DCB /* Diary+CoreDataProperties.swift */; };
Expand All @@ -29,6 +27,7 @@
BAECB2D92AB18611006B4A46 /* DiaryV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BAECB2D82AB18611006B4A46 /* DiaryV2.xcmappingmodel */; };
BAECB2DD2AB187D6006B4A46 /* ImageCachingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAECB2DC2AB187D6006B4A46 /* ImageCachingManager.swift */; };
BAECB2E12AB568A0006B4A46 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BAECB2E32AB568A0006B4A46 /* Localizable.strings */; };
BAECB2E82AB72869006B4A46 /* AlertBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAECB2E72AB72869006B4A46 /* AlertBuilder.swift */; };
C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; };
C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; };
C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; };
Expand All @@ -43,8 +42,6 @@
632F74F32AB14DBC003E1B97 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = "<group>"; };
632F74F72AB17E05003E1B97 /* Diary V2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Diary V2.xcdatamodel"; sourceTree = "<group>"; };
636B19AB2AA6C5E900B5242D /* AlertDisplayble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDisplayble.swift; sourceTree = "<group>"; };
63B12BAD2AAD9C9000D614A6 /* AlertNamespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertNamespace.swift; sourceTree = "<group>"; };
63B12BAF2AAD9D3400D614A6 /* ButtonNamespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonNamespace.swift; sourceTree = "<group>"; };
63BB62812A9F109400524DCB /* DecodingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingManager.swift; sourceTree = "<group>"; };
63BB62B02AA181BE00524DCB /* Diary+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Diary+CoreDataClass.swift"; path = "Diary/Model/CoreData/Diary+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
63BB62B12AA181BE00524DCB /* Diary+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Diary+CoreDataProperties.swift"; path = "Diary/Model/CoreData/Diary+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
Expand All @@ -63,6 +60,7 @@
BAECB2E22AB568A0006B4A46 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
BAECB2E42AB568D2006B4A46 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
BAECB2E52AB568D2006B4A46 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
BAECB2E72AB72869006B4A46 /* AlertBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBuilder.swift; sourceTree = "<group>"; };
C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; };
C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -146,7 +144,6 @@
children = (
BAECB2DA2AB1877C006B4A46 /* DTO */,
63BB62B62AA185E700524DCB /* CoreData */,
BAECB2DB2AB187AB006B4A46 /* Namespace */,
BAECB2DE2AB18A17006B4A46 /* ImageCache */,
);
path = Model;
Expand Down Expand Up @@ -189,21 +186,20 @@
path = DTO;
sourceTree = "<group>";
};
BAECB2DB2AB187AB006B4A46 /* Namespace */ = {
BAECB2DE2AB18A17006B4A46 /* ImageCache */ = {
isa = PBXGroup;
children = (
63B12BAD2AAD9C9000D614A6 /* AlertNamespace.swift */,
63B12BAF2AAD9D3400D614A6 /* ButtonNamespace.swift */,
BAECB2DC2AB187D6006B4A46 /* ImageCachingManager.swift */,
);
path = Namespace;
path = ImageCache;
sourceTree = "<group>";
};
BAECB2DE2AB18A17006B4A46 /* ImageCache */ = {
BAECB2E62AB72855006B4A46 /* Builder */ = {
isa = PBXGroup;
children = (
BAECB2DC2AB187D6006B4A46 /* ImageCachingManager.swift */,
BAECB2E72AB72869006B4A46 /* AlertBuilder.swift */,
);
path = ImageCache;
path = Builder;
sourceTree = "<group>";
};
C739AE18284DF28600741E8F = {
Expand All @@ -229,6 +225,7 @@
C739AE23284DF28600741E8F /* Diary */ = {
isa = PBXGroup;
children = (
BAECB2E62AB72855006B4A46 /* Builder */,
632F74EE2AB14CF3003E1B97 /* Network */,
636B19AA2AA6C5C200B5242D /* Protocol */,
63E5273D2A9ECD660000FBA6 /* Model */,
Expand Down Expand Up @@ -372,6 +369,7 @@
63BB62822A9F109400524DCB /* DecodingManager.swift in Sources */,
632F74F22AB14D8D003E1B97 /* WeatherResult.swift in Sources */,
63E527372A9D87660000FBA6 /* DiaryListTableViewCell.swift in Sources */,
BAECB2E82AB72869006B4A46 /* AlertBuilder.swift in Sources */,
C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */,
BAECB2D12AB157CB006B4A46 /* NetworkConfiguration.swift in Sources */,
63BB62B52AA182AA00524DCB /* CoreDataManager.swift in Sources */,
Expand All @@ -381,9 +379,7 @@
BABBDAE52A9F13A200D8D50B /* DecodingError.swift in Sources */,
BABBDB362AAD904100D8D50B /* CoreDataError.swift in Sources */,
BABBDB342AA6D05A00D8D50B /* ShareDisplayable.swift in Sources */,
63B12BB02AAD9D3400D614A6 /* ButtonNamespace.swift in Sources */,
63BB62B32AA181BE00524DCB /* Diary+CoreDataProperties.swift in Sources */,
63B12BAE2AAD9C9000D614A6 /* AlertNamespace.swift in Sources */,
63BB62B22AA181BE00524DCB /* Diary+CoreDataClass.swift in Sources */,
632F74F42AB14DBC003E1B97 /* APIError.swift in Sources */,
);
Expand Down
2 changes: 1 addition & 1 deletion Diary.xcodeproj/xcshareddata/xcschemes/Diary.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = "ko"
language = "en"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
110 changes: 110 additions & 0 deletions Diary/Builder/AlertBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// AlertBuilder.swift
// Diary
//
// Created by Min Hyun on 2023/09/17.
//

import UIKit

final class AlertBuilder {
private let viewController: UIViewController
private let alertController: UIAlertController

private var type: AlertType?
private var alertActions: [UIAlertAction] = []

init(viewController: UIViewController, prefferedStyle: UIAlertController.Style) {
self.viewController = viewController
self.alertController = UIAlertController(title: nil, message: nil, preferredStyle: prefferedStyle)
}

func setType(_ type: AlertType) {
self.type = type
}

func addAction(_ actionType: AlertActionType, action: ((UIAlertAction) -> Void)? = nil) {
let action = UIAlertAction(title: actionType.title, style: actionType.style, handler: action)
alertActions.append(action)
}

@discardableResult
func show() -> Self {
alertController.title = type?.title
alertController.message = type?.message
alertActions.forEach { alertController.addAction($0) }

viewController.present(alertController, animated: true)

return self
}
}

extension AlertBuilder {
enum AlertType {
case decodingError(error: DecodingError)
case apiError(error: APIError)
case coreDataError(error: CoreDataError)
case delete
case actionSheet

var title: String? {
switch self {
case .decodingError, .apiError:
return NSLocalizedString("networkError", comment: "")
case .coreDataError(let error):
return error.alertTitle
case .delete:
return NSLocalizedString("deleteTitle", comment: "")
case .actionSheet:
return nil
}
}

var message: String? {
switch self {
case .decodingError(let error):
return error.message
case .apiError(let error):
return error.message
case .coreDataError(let error):
return error.message
case .delete:
return NSLocalizedString("deleteMessage", comment: "")
case .actionSheet:
return nil
}
}
}

enum AlertActionType {
case confirm
case cancel
case share
case delete

var title: String {
switch self {
case .confirm:
return NSLocalizedString("confirm", comment: "")
case .cancel:
return NSLocalizedString("cancel", comment: "")
case .share:
return NSLocalizedString("share", comment: "")
case .delete:
return NSLocalizedString("delete", comment: "")
}
}

var style: UIAlertAction.Style {
switch self {
case .cancel:
return .cancel
case .delete:
return .destructive
default:
return .default
}
}
}
}
91 changes: 35 additions & 56 deletions Diary/Controller/DiaryDetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,33 +128,28 @@ final class DiaryDetailViewController: UIViewController, AlertDisplayable, Share
}

private func showDeleteAlert() {
let cancelAction = UIAlertAction(title: NSLocalizedString("cancelOption", comment: ""),
style: .cancel)
let deleteAction = UIAlertAction(title: NSLocalizedString("deleteOption", comment: ""),
style: .destructive) { [weak self] _ in
let alertBuilder = AlertBuilder(viewController: self, prefferedStyle: .alert)
alertBuilder.setType(.delete)
alertBuilder.addAction(.cancel)
alertBuilder.addAction(.delete) { [weak self] _ in
guard let self else { return }
do {
try CoreDataManager.shared.deleteDiary(self.diary)
self.navigationController?.popViewController(animated: true)
} catch CoreDataError.deleteFailure {
let cancelAction = UIAlertAction(title: NSLocalizedString("confirm", comment: ""), style: .cancel)
self.showAlert(title: CoreDataError.deleteFailure.alertTitle,
message: CoreDataError.deleteFailure.message,
actions: [cancelAction],
preferredStyle: .alert)
let additionalAlertBuilder = AlertBuilder(viewController: self, prefferedStyle: .alert)
additionalAlertBuilder.setType(.coreDataError(error: .deleteFailure))
additionalAlertBuilder.addAction(.confirm)
additionalAlertBuilder.show()
} catch {
let cancelAction = UIAlertAction(title: NSLocalizedString("confirm", comment: ""), style: .cancel)
self.showAlert(title: CoreDataError.deleteFailure.alertTitle,
message: CoreDataError.unknown.message,
actions: [cancelAction],
preferredStyle: .alert)
let additionalAlertBuilder = AlertBuilder(viewController: self, prefferedStyle: .alert)
additionalAlertBuilder.setType(.coreDataError(error: .unknown))
additionalAlertBuilder.addAction(.confirm)
additionalAlertBuilder.show()
}
}

showAlert(title: AlertNamespace.deleteTitle,
message: AlertNamespace.deleteMessage,
actions: [cancelAction, deleteAction],
preferredStyle: .alert)
alertBuilder.show()
}

deinit {
Expand All @@ -171,28 +166,18 @@ extension DiaryDetailViewController {
}

@objc private func showMoreOptions() {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)

let deleteAction = UIAlertAction(title: NSLocalizedString("deleteOption", comment: ""),
style: .destructive) { [weak self] _ in
let alertBuilder = AlertBuilder(viewController: self, prefferedStyle: .actionSheet)
alertBuilder.setType(.actionSheet)
alertBuilder.addAction(.delete) { [weak self] _ in
guard let self else { return }
self.showDeleteAlert()
}

let shareAction = UIAlertAction(title: NSLocalizedString("shareOption", comment: ""),
style: .default) { [weak self] _ in
alertBuilder.addAction(.share) { [weak self] _ in
guard let self else { return }
self.shareDiary(self.diary)
}

let cancelAction = UIAlertAction(title: NSLocalizedString("cancelOption", comment: ""),
style: .cancel)

alertController.addAction(shareAction)
alertController.addAction(deleteAction)
alertController.addAction(cancelAction)

present(alertController, animated: true)
alertBuilder.addAction(.cancel)
alertBuilder.show()
}

@objc private func keyboardWillHide(_ notification: Notification) {
Expand All @@ -208,17 +193,15 @@ extension DiaryDetailViewController: UITextViewDelegate {
do {
try CoreDataManager.shared.saveContext()
} catch CoreDataError.saveFailure {
let cancelAction = UIAlertAction(title: NSLocalizedString("confirm", comment: ""), style: .cancel)
self.showAlert(title: CoreDataError.saveFailure.alertTitle,
message: CoreDataError.saveFailure.message,
actions: [cancelAction],
preferredStyle: .alert)
let alertBulder = AlertBuilder(viewController: self, prefferedStyle: .alert)
alertBulder.setType(.coreDataError(error: .saveFailure))
alertBulder.addAction(.confirm)
alertBulder.show()
} catch {
let cancelAction = UIAlertAction(title: NSLocalizedString("confirm", comment: ""), style: .cancel)
self.showAlert(title: CoreDataError.saveFailure.alertTitle,
message: CoreDataError.unknown.message,
actions: [cancelAction],
preferredStyle: .alert)
let alertBulder = AlertBuilder(viewController: self, prefferedStyle: .alert)
alertBulder.setType(.coreDataError(error: .unknown))
alertBulder.addAction(.confirm)
alertBulder.show()
}
}

Expand Down Expand Up @@ -253,22 +236,18 @@ extension DiaryDetailViewController {
self.diary.weatherIcon = weatherIcon
} catch {
DispatchQueue.main.async {
let confirmAction = UIAlertAction(title: NSLocalizedString("confirm", comment: ""),
style: .default)
self.showAlert(title: AlertNamespace.networkErrorTitle,
message: nil,
actions: [confirmAction],
preferredStyle: .alert)
let alertBulder = AlertBuilder(viewController: self, prefferedStyle: .alert)
alertBulder.setType(.decodingError(error: .decodingFailure))
alertBulder.addAction(.confirm)
alertBulder.show()
}
}
case .failure:
DispatchQueue.main.async {
let confirmAction = UIAlertAction(title: NSLocalizedString("confirm", comment: ""),
style: .default)
self.showAlert(title: AlertNamespace.networkErrorTitle,
message: nil,
actions: [confirmAction],
preferredStyle: .alert)
let alertBulder = AlertBuilder(viewController: self, prefferedStyle: .alert)
alertBulder.setType(.apiError(error: .requestFailure))
alertBulder.addAction(.confirm)
alertBulder.show()
}
}
}
Expand Down
Loading

0 comments on commit 0f372e1

Please sign in to comment.