diff --git a/iBox/Sources/BoxList/BoxListView.swift b/iBox/Sources/BoxList/BoxListView.swift index 2a99fe6..316c5bf 100644 --- a/iBox/Sources/BoxList/BoxListView.swift +++ b/iBox/Sources/BoxList/BoxListView.swift @@ -194,6 +194,11 @@ extension BoxListView: UITableViewDelegate { guard let viewModel else { return } viewModel.input.send(.folderTapped(section: button.tag)) button.toggleStatus() + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.prepare() + generator.impactOccurred() + } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { @@ -207,6 +212,11 @@ extension BoxListView: UITableViewDelegate { // 액션 정의 let favoriteAction = UIContextualAction(style: .normal, title: "favorite", handler: { [weak self] (action, view, completionHandler) in self?.viewModel?.input.send(.toggleFavorite(indexPath: indexPath)) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } completionHandler(true) }) favoriteAction.backgroundColor = .box2 @@ -219,6 +229,11 @@ extension BoxListView: UITableViewDelegate { let shareAction = UIContextualAction(style: .normal, title: "share", handler: {(action, view, completionHandler) in let cellViewModel = self.viewModel?.viewModel(at: indexPath) self.delegate?.pushViewController(url: cellViewModel?.url) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } completionHandler(true) }) shareAction.backgroundColor = .box3 @@ -226,6 +241,11 @@ extension BoxListView: UITableViewDelegate { let deleteAction = UIContextualAction(style: .normal, title: "delete", handler: {(action, view, completionHandler) in self.viewModel?.input.send(.deleteBookmark(indexPath: indexPath)) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } completionHandler(true) }) deleteAction.backgroundColor = .systemGray @@ -276,6 +296,11 @@ extension BoxListView: UITableViewDelegate { private func makeContextMenu(for indexPath: IndexPath) -> UIMenu { let deleteAction = UIAction(title: "삭제", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] action in self?.viewModel?.input.send(.deleteBookmark(indexPath: indexPath)) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } } let isFavorite = self.viewModel?.isFavoriteBookmark(at: indexPath) ?? false @@ -284,11 +309,22 @@ extension BoxListView: UITableViewDelegate { let favoriteAction = UIAction(title: favoriteActionTitle, image: favoriteActionImage) { [weak self] action in self?.viewModel?.input.send(.toggleFavorite(indexPath: indexPath)) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } } let shareAction = UIAction(title: "공유하기", image: UIImage(systemName: "square.and.arrow.up")) { [weak self] action in guard let self = self, let url = self.viewModel?.boxList[indexPath.section].boxListCellViewModelsWithStatus[indexPath.row].url else { return } + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } + let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) if let viewController = self.delegate as? UIViewController { viewController.present(activityViewController, animated: true, completion: nil) diff --git a/iBox/Sources/BoxList/BoxListViewController.swift b/iBox/Sources/BoxList/BoxListViewController.swift index da1fe42..4feee24 100644 --- a/iBox/Sources/BoxList/BoxListViewController.swift +++ b/iBox/Sources/BoxList/BoxListViewController.swift @@ -86,6 +86,11 @@ extension BoxListViewController: AddBookmarkViewControllerProtocol { func addBookmarkDirect(_ bookmark: Bookmark, at folderIndex: Int) { guard let contentView = contentView as? BoxListView else { return } contentView.viewModel?.addBookmarkDirect(bookmark, at: folderIndex) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } } } @@ -195,10 +200,20 @@ extension BoxListViewController: EditFolderViewControllerDelegate { func deleteFolder(at row: Int) { guard let contentView = contentView as? BoxListView else { return } contentView.viewModel?.deleteFolder(at: row) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } } func addFolder(_ folder: Folder) { guard let contentView = contentView as? BoxListView else { return } contentView.viewModel?.addFolder(folder) + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .soft) + generator.prepare() + generator.impactOccurred() + } } } diff --git a/iBox/Sources/Main/MainTabBarController.swift b/iBox/Sources/Main/MainTabBarController.swift index 625d21d..f1b8329 100644 --- a/iBox/Sources/Main/MainTabBarController.swift +++ b/iBox/Sources/Main/MainTabBarController.swift @@ -52,6 +52,11 @@ class MainTabBarController: UITabBarController { extension MainTabBarController: UITabBarControllerDelegate { func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { + if UserDefaultsManager.isHaptics { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.prepare() + generator.impactOccurred() + } if tabBarController.selectedIndex == 1 && previousTabIndex == 1 { WebViewPreloader.shared.resetFavoriteView() } diff --git a/iBox/Sources/Model/SettingsItem.swift b/iBox/Sources/Model/SettingsItem.swift index d725301..4b0c992 100644 --- a/iBox/Sources/Model/SettingsItem.swift +++ b/iBox/Sources/Model/SettingsItem.swift @@ -10,6 +10,7 @@ import Foundation enum SettingsType { case theme case homeTab + case haptics case preload case reset case guide @@ -19,6 +20,7 @@ enum SettingsType { switch self { case .theme: "테마" case .homeTab: "홈화면" + case .haptics: "진동" case .preload: "즐겨찾기 미리 로드" case .reset: "데이터 초기화" case .guide: "앱 소개" diff --git a/iBox/Sources/Settings/SettingsView.swift b/iBox/Sources/Settings/SettingsView.swift index e1fe94c..4f5745d 100644 --- a/iBox/Sources/Settings/SettingsView.swift +++ b/iBox/Sources/Settings/SettingsView.swift @@ -69,7 +69,12 @@ final class SettingsView: UIView { // MARK: - Action Functions - @objc private func handleSwitchControlTap(_ controlSwitch: UISwitch) { + @objc private func handleHapticsSwitchTap(_ controlSwitch: UISwitch) { + guard let viewModel = viewModel else { return } + viewModel.input.send(.setHaptics(controlSwitch.isOn)) + } + + @objc private func handlePreloadSwitchTap(_ controlSwitch: UISwitch) { guard let viewModel = viewModel else { return } viewModel.input.send(.setPreload(controlSwitch.isOn)) } @@ -100,7 +105,7 @@ extension SettingsView: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let viewModel = viewModel else { return } let settingsItem = viewModel.sectionViewModels[indexPath.section].cellViewModels[indexPath.row].settingsItem - if (settingsItem.type != SettingsType.preload) { + if (settingsItem.type != SettingsType.haptics && settingsItem.type != SettingsType.preload) { delegate?.pushViewController(settingsItem.type) } } @@ -120,9 +125,13 @@ extension SettingsView: UITableViewDataSource { as? SettingsItemCell else { return UITableViewCell() } let cellViewModel = viewModel.sectionViewModels[indexPath.section].cellViewModels[indexPath.row] cell.bindViewModel(cellViewModel) - if cellViewModel.flag != nil { + let settingsType = cellViewModel.settingsItem.type + if settingsType == .haptics { + cell.switchControl.removeTarget(nil, action: nil, for: .valueChanged) + cell.switchControl.addTarget(self, action: #selector(handleHapticsSwitchTap), for: .valueChanged) + } else if settingsType == .preload { cell.switchControl.removeTarget(nil, action: nil, for: .valueChanged) - cell.switchControl.addTarget(self, action: #selector(handleSwitchControlTap), for: .valueChanged) + cell.switchControl.addTarget(self, action: #selector(handlePreloadSwitchTap), for: .valueChanged) } return cell } diff --git a/iBox/Sources/Settings/SettingsViewModel.swift b/iBox/Sources/Settings/SettingsViewModel.swift index a6fbda4..73a1581 100644 --- a/iBox/Sources/Settings/SettingsViewModel.swift +++ b/iBox/Sources/Settings/SettingsViewModel.swift @@ -12,6 +12,7 @@ class SettingsViewModel { enum Input { case viewWillAppear + case setHaptics(_ isOn: Bool) case setPreload(_ isOn: Bool) } @@ -33,6 +34,8 @@ class SettingsViewModel { self?.sectionViewModels.removeAll() self?.updateSectionViewModels() self?.output.send(.updateSectionViewModels) + case let .setHaptics(isOn): + UserDefaultsManager.isHaptics = isOn case let .setPreload(isOn): UserDefaultsManager.isPreload = isOn } @@ -44,6 +47,7 @@ class SettingsViewModel { sectionViewModels.append(SettingsSectionViewModel(cellViewModels: [ SettingsCellViewModel(SettingsItem(type: .theme, description: UserDefaultsManager.theme.toString())), SettingsCellViewModel(SettingsItem(type: .homeTab, description: HomeTabType.allCases[UserDefaultsManager.homeTabIndex].toString())), + SettingsCellViewModel(SettingsItem(type: .haptics, flag: UserDefaultsManager.isHaptics)), SettingsCellViewModel(SettingsItem(type: .preload, flag: UserDefaultsManager.isPreload)) ])) sectionViewModels.append(SettingsSectionViewModel(cellViewModels: [ diff --git a/iBox/Sources/Shared/UserDefaultsManager.swift b/iBox/Sources/Shared/UserDefaultsManager.swift index e8de7dc..556951f 100644 --- a/iBox/Sources/Shared/UserDefaultsManager.swift +++ b/iBox/Sources/Shared/UserDefaultsManager.swift @@ -21,6 +21,9 @@ final class UserDefaultsManager { @UserDefaultsData(key: "isDefaultDataInserted", defaultValue: false) static var isDefaultDataInserted: Bool + @UserDefaultsData(key: "isHaptics", defaultValue: true) + static var isHaptics: Bool + @UserDefaultsData(key: "isPreload", defaultValue: false) static var isPreload: Bool