fix: setting item reuse issue

This commit is contained in:
CMK 2021-07-22 13:47:56 +08:00
parent 681f78856a
commit a8c29789f5
5 changed files with 199 additions and 165 deletions

View File

@ -8,12 +8,10 @@
import UIKit
import CoreData
enum SettingsItem: Hashable {
enum SettingsItem {
case appearance(settingObjectID: NSManagedObjectID)
case notification(settingObjectID: NSManagedObjectID, switchMode: NotificationSwitchMode)
case preferenceDarkMode(settingObjectID: NSManagedObjectID)
case preferenceDisableAvatarAnimation(settingObjectID: NSManagedObjectID)
case preferenceUsingDefaultBrowser(settingObjectID: NSManagedObjectID)
case preference(settingObjectID: NSManagedObjectID, preferenceType: PreferenceType)
case boringZone(item: Link)
case spicyZone(item: Link)
}
@ -26,7 +24,7 @@ extension SettingsItem {
case dark
}
enum NotificationSwitchMode: CaseIterable {
enum NotificationSwitchMode: CaseIterable, Hashable {
case favorite
case follow
case reblog
@ -41,8 +39,22 @@ extension SettingsItem {
}
}
}
enum PreferenceType: CaseIterable {
case darkMode
case disableAvatarAnimation
case useDefaultBrowser
var title: String {
switch self {
case .darkMode: return L10n.Scene.Settings.Section.AppearanceSettings.trueBlackDarkMode
case .disableAvatarAnimation: return L10n.Scene.Settings.Section.AppearanceSettings.disableAvatarAnimation
case .useDefaultBrowser: return L10n.Scene.Settings.Section.Preference.usingDefaultBrowser
}
}
}
enum Link: CaseIterable {
enum Link: CaseIterable, Hashable {
case accountSettings
case termsOfService
case privacyPolicy
@ -71,3 +83,27 @@ extension SettingsItem {
}
}
extension SettingsItem: Hashable {
func hash(into hasher: inout Hasher) {
switch self {
case .appearance(let settingObjectID):
hasher.combine(String(describing: SettingsItem.AppearanceMode.self))
hasher.combine(settingObjectID)
case .notification(let settingObjectID, let switchMode):
hasher.combine(String(describing: SettingsItem.notification.self))
hasher.combine(settingObjectID)
hasher.combine(switchMode)
case .preference(let settingObjectID, let preferenceType):
hasher.combine(String(describing: SettingsItem.preference.self))
hasher.combine(settingObjectID)
hasher.combine(preferenceType)
case .boringZone(let link):
hasher.combine(String(describing: SettingsItem.boringZone.self))
hasher.combine(link)
case .spicyZone(let link):
hasher.combine(String(describing: SettingsItem.spicyZone.self))
hasher.combine(link)
}
}
}

View File

@ -5,7 +5,9 @@
// Created by MainasuK Cirno on 2021-4-25.
//
import Foundation
import UIKit
import CoreData
import CoreDataStack
enum SettingsSection: Hashable {
case appearance
@ -24,3 +26,125 @@ enum SettingsSection: Hashable {
}
}
}
extension SettingsSection {
static func tableViewDiffableDataSource(
for tableView: UITableView,
managedObjectContext: NSManagedObjectContext,
settingsAppearanceTableViewCellDelegate: SettingsAppearanceTableViewCellDelegate,
settingsToggleCellDelegate: SettingsToggleCellDelegate
) -> UITableViewDiffableDataSource<SettingsSection, SettingsItem> {
UITableViewDiffableDataSource(tableView: tableView) { [
weak settingsAppearanceTableViewCellDelegate,
weak settingsToggleCellDelegate
] tableView, indexPath, item -> UITableViewCell? in
switch item {
case .appearance(let objectID):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsAppearanceTableViewCell.self), for: indexPath) as! SettingsAppearanceTableViewCell
managedObjectContext.performAndWait {
let setting = managedObjectContext.object(with: objectID) as! Setting
cell.update(with: setting.appearance)
ManagedObjectObserver.observe(object: setting)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
cell.update(with: setting.appearance)
})
.store(in: &cell.disposeBag)
}
cell.delegate = settingsAppearanceTableViewCellDelegate
return cell
case .notification(let objectID, let switchMode):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
managedObjectContext.performAndWait {
let setting = managedObjectContext.object(with: objectID) as! Setting
if let subscription = setting.activeSubscription {
SettingsSection.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
}
ManagedObjectObserver.observe(object: setting)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
guard let subscription = setting.activeSubscription else { return }
SettingsSection.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
})
.store(in: &cell.disposeBag)
}
cell.delegate = settingsToggleCellDelegate
return cell
case .preference(let objectID, _):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
cell.delegate = settingsToggleCellDelegate
managedObjectContext.performAndWait {
let setting = managedObjectContext.object(with: objectID) as! Setting
SettingsSection.configureSettingToggle(cell: cell, item: item, setting: setting)
ManagedObjectObserver.observe(object: setting)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
SettingsSection.configureSettingToggle(cell: cell, item: item, setting: setting)
})
.store(in: &cell.disposeBag)
}
return cell
case .boringZone(let item),
.spicyZone(let item):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsLinkTableViewCell.self), for: indexPath) as! SettingsLinkTableViewCell
cell.update(with: item)
return cell
}
}
}
}
extension SettingsSection {
static func configureSettingToggle(
cell: SettingsToggleTableViewCell,
item: SettingsItem,
setting: Setting
) {
guard case let .preference(_, preferenceType) = item else { return }
cell.textLabel?.text = preferenceType.title
switch preferenceType {
case .darkMode:
cell.switchButton.isOn = setting.preferredTrueBlackDarkMode
case .disableAvatarAnimation:
cell.switchButton.isOn = setting.preferredStaticAvatar
case .useDefaultBrowser:
cell.switchButton.isOn = setting.preferredUsingDefaultBrowser
}
}
static func configureSettingToggle(
cell: SettingsToggleTableViewCell,
switchMode: SettingsItem.NotificationSwitchMode,
subscription: NotificationSubscription
) {
cell.textLabel?.text = switchMode.title
let enabled: Bool?
switch switchMode {
case .favorite: enabled = subscription.alert.favourite
case .follow: enabled = subscription.alert.follow
case .reblog: enabled = subscription.alert.reblog
case .mention: enabled = subscription.alert.mention
}
cell.update(enabled: enabled)
}
}

View File

@ -358,13 +358,10 @@ extension SettingsViewController: UITableViewDelegate {
case .appearance:
// do nothing
break
case .preferenceDarkMode, .preferenceDisableAvatarAnimation:
// do nothing
break
case .notification:
// do nothing
break
case .preferenceUsingDefaultBrowser:
case .preference:
// do nothing
break
case .boringZone(let link), .spicyZone(let link):
@ -476,48 +473,30 @@ extension SettingsViewController: SettingsToggleCellDelegate {
// do nothing
}
.store(in: &disposeBag)
case .preferenceDarkMode(let settingObjectID):
case .preference(let settingObjectID, let preferenceType):
let managedObjectContext = context.backgroundManagedObjectContext
managedObjectContext.performChanges {
let setting = managedObjectContext.object(with: settingObjectID) as! Setting
setting.update(preferredTrueBlackDarkMode: isOn)
}
.sink { result in
switch result {
case .success:
ThemeService.shared.set(themeName: isOn ? .system : .mastodon)
case .failure(let error):
assertionFailure(error.localizedDescription)
break
switch preferenceType {
case .darkMode:
setting.update(preferredTrueBlackDarkMode: isOn)
case .disableAvatarAnimation:
setting.update(preferredStaticAvatar: isOn)
case .useDefaultBrowser:
setting.update(preferredUsingDefaultBrowser: isOn)
}
}
.store(in: &disposeBag)
case .preferenceDisableAvatarAnimation(let settingObjectID):
let managedObjectContext = context.backgroundManagedObjectContext
managedObjectContext.performChanges {
let setting = managedObjectContext.object(with: settingObjectID) as! Setting
setting.update(preferredStaticAvatar: isOn)
}
.sink { result in
switch result {
case .success:
UserDefaults.shared.preferredStaticAvatar = isOn
case .failure(let error):
assertionFailure(error.localizedDescription)
break
}
}
.store(in: &disposeBag)
case .preferenceUsingDefaultBrowser(let settingObjectID):
let managedObjectContext = context.backgroundManagedObjectContext
managedObjectContext.performChanges {
let setting = managedObjectContext.object(with: settingObjectID) as! Setting
setting.update(preferredUsingDefaultBrowser: isOn)
}
.sink { result in
switch result {
case .success:
UserDefaults.shared.preferredUsingDefaultBrowser = isOn
switch preferenceType {
case .darkMode:
ThemeService.shared.set(themeName: isOn ? .system : .mastodon)
case .disableAvatarAnimation:
UserDefaults.shared.preferredStaticAvatar = isOn
case .useDefaultBrowser:
UserDefaults.shared.preferredUsingDefaultBrowser = isOn
}
case .failure(let error):
assertionFailure(error.localizedDescription)
break

View File

@ -122,9 +122,9 @@ extension SettingsViewModel {
// preference
snapshot.appendSections([.preference])
let preferenceItems: [SettingsItem] = [
.preferenceDarkMode(settingObjectID: setting.objectID),
.preferenceDisableAvatarAnimation(settingObjectID: setting.objectID),
.preferenceUsingDefaultBrowser(settingObjectID: setting.objectID),
.preference(settingObjectID: setting.objectID, preferenceType: .darkMode),
.preference(settingObjectID: setting.objectID, preferenceType: .disableAvatarAnimation),
.preference(settingObjectID: setting.objectID, preferenceType: .useDefaultBrowser),
]
snapshot.appendItems(preferenceItems,toSection: .preference)
@ -163,123 +163,12 @@ extension SettingsViewModel {
settingsAppearanceTableViewCellDelegate: SettingsAppearanceTableViewCellDelegate,
settingsToggleCellDelegate: SettingsToggleCellDelegate
) {
dataSource = UITableViewDiffableDataSource(tableView: tableView) { [
weak self,
weak settingsAppearanceTableViewCellDelegate,
weak settingsToggleCellDelegate
] tableView, indexPath, item -> UITableViewCell? in
guard let self = self else { return nil }
switch item {
case .appearance(let objectID):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsAppearanceTableViewCell.self), for: indexPath) as! SettingsAppearanceTableViewCell
self.context.managedObjectContext.performAndWait {
let setting = self.context.managedObjectContext.object(with: objectID) as! Setting
cell.update(with: setting.appearance)
ManagedObjectObserver.observe(object: setting)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
cell.update(with: setting.appearance)
})
.store(in: &cell.disposeBag)
}
cell.delegate = settingsAppearanceTableViewCellDelegate
return cell
case .preferenceDarkMode(let objectID),
.preferenceDisableAvatarAnimation(let objectID),
.preferenceUsingDefaultBrowser(let objectID):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
cell.delegate = settingsToggleCellDelegate
self.context.managedObjectContext.performAndWait {
let setting = self.context.managedObjectContext.object(with: objectID) as! Setting
SettingsViewModel.configureSettingToggle(cell: cell, item: item, setting: setting)
ManagedObjectObserver.observe(object: setting)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
SettingsViewModel.configureSettingToggle(cell: cell, item: item, setting: setting)
})
.store(in: &cell.disposeBag)
}
return cell
case .notification(let objectID, let switchMode):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
self.context.managedObjectContext.performAndWait {
let setting = self.context.managedObjectContext.object(with: objectID) as! Setting
if let subscription = setting.activeSubscription {
SettingsViewModel.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
}
ManagedObjectObserver.observe(object: setting)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
guard let subscription = setting.activeSubscription else { return }
SettingsViewModel.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
})
.store(in: &cell.disposeBag)
}
cell.delegate = settingsToggleCellDelegate
return cell
case .boringZone(let item), .spicyZone(let item):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsLinkTableViewCell.self), for: indexPath) as! SettingsLinkTableViewCell
cell.update(with: item)
return cell
}
}
dataSource = SettingsSection.tableViewDiffableDataSource(
for: tableView,
managedObjectContext: context.managedObjectContext,
settingsAppearanceTableViewCellDelegate: settingsAppearanceTableViewCellDelegate,
settingsToggleCellDelegate: settingsToggleCellDelegate
)
processDataSource(self.setting.value)
}
}
extension SettingsViewModel {
static func configureSettingToggle(
cell: SettingsToggleTableViewCell,
item: SettingsItem,
setting: Setting
) {
switch item {
case .preferenceDarkMode:
cell.textLabel?.text = L10n.Scene.Settings.Section.AppearanceSettings.trueBlackDarkMode
cell.switchButton.isOn = setting.preferredTrueBlackDarkMode
case .preferenceDisableAvatarAnimation:
cell.textLabel?.text = L10n.Scene.Settings.Section.AppearanceSettings.disableAvatarAnimation
cell.switchButton.isOn = setting.preferredStaticAvatar
case .preferenceUsingDefaultBrowser:
cell.textLabel?.text = L10n.Scene.Settings.Section.Preference.usingDefaultBrowser
cell.switchButton.isOn = setting.preferredUsingDefaultBrowser
default:
assertionFailure()
}
}
static func configureSettingToggle(
cell: SettingsToggleTableViewCell,
switchMode: SettingsItem.NotificationSwitchMode,
subscription: NotificationSubscription
) {
cell.textLabel?.text = switchMode.title
let enabled: Bool?
switch switchMode {
case .favorite: enabled = subscription.alert.favourite
case .follow: enabled = subscription.alert.follow
case .reblog: enabled = subscription.alert.reblog
case .mention: enabled = subscription.alert.mention
}
cell.update(enabled: enabled)
}
}

View File

@ -22,6 +22,12 @@ class SettingsToggleTableViewCell: UITableViewCell {
}()
weak var delegate: SettingsToggleCellDelegate?
override func prepareForReuse() {
super.prepareForReuse()
disposeBag.removeAll()
}
// MARK: - Methods
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {