aligns behavior for user intiaited corner cases.
This commit is contained in:
parent
cf8d0f967e
commit
be196a289e
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModel, FormFieldProtocol {
|
||||
@objcMembers public class CheckboxModel: NSObject, MoleculeModelProtocol, SelectableMoleculeModel, FormFieldProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -15,7 +15,7 @@
|
||||
public static var identifier: String = "checkbox"
|
||||
public var backgroundColor: Color?
|
||||
public var accessibilityIdentifier: String?
|
||||
public var checked: Bool = false
|
||||
public dynamic var checked: Bool = false
|
||||
public var enabled: Bool = true
|
||||
public var animated: Bool = true
|
||||
public var inverted: Bool = false
|
||||
|
||||
@ -24,7 +24,7 @@ public class SelectAllBoxesBehaviorModel: PageBehaviorModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codable
|
||||
//--------------------------------------------------
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case selectAllTitle
|
||||
case deselectAllTitle
|
||||
@ -41,7 +41,7 @@ public class SelectAllBoxesBehaviorModel: PageBehaviorModelProtocol {
|
||||
self.deselectAllTitle = deselectAllTitle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(selectAllTitle, forKey: .selectAllTitle)
|
||||
@ -50,16 +50,23 @@ public class SelectAllBoxesBehaviorModel: PageBehaviorModelProtocol {
|
||||
}
|
||||
|
||||
/// Selects all the control models presented on a page.
|
||||
public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
||||
public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior, PageMoleculeTransformationBehavior {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Status of the select all behavior. Initially false as the action has not been enaged.
|
||||
var selectAllState = false
|
||||
var didSelectAllState = false
|
||||
|
||||
/// Reference to the general PageBehaviorModel.
|
||||
var model: PageBehaviorModelProtocol
|
||||
|
||||
/// Dictionary of KVOs to observing the selected property of each `SelectableMoleculeModel`.
|
||||
private var observers = [String: NSKeyValueObservation?]()
|
||||
|
||||
/// A store representing the values of the `SelectableMoleculeModel`.
|
||||
private var valuesMirror = [String: Bool]()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Delegate
|
||||
//--------------------------------------------------
|
||||
@ -75,6 +82,45 @@ public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
||||
self.delegate = delegateObject
|
||||
}
|
||||
|
||||
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) {
|
||||
|
||||
let selectableModels: [SelectableMoleculeModel] = rootMolecules.allMoleculesOfType()
|
||||
|
||||
guard !selectableModels.isEmpty else { return }
|
||||
|
||||
for model in selectableModels {
|
||||
if let checkboxModel = model as? CheckboxModel, let key = checkboxModel.fieldKey {
|
||||
|
||||
valuesMirror[key] = checkboxModel.checked
|
||||
|
||||
observers[key] = checkboxModel.observe(\.checked, options: [.new]) { [weak self] model, change in
|
||||
guard let self = self,
|
||||
let isChecked = change.newValue,
|
||||
let key = model.fieldKey
|
||||
else { return }
|
||||
|
||||
self.valuesMirror[key] = isChecked
|
||||
|
||||
// If all are models are in the opposite state of the behavior, then realign.
|
||||
if self.selectAllIsMisaligned() {
|
||||
self.realignPageBehavior(asSelectAll: true)
|
||||
|
||||
} else if self.deselectAllIsMisaligned() {
|
||||
self.realignPageBehavior(asSelectAll: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Deinit
|
||||
//--------------------------------------------------
|
||||
|
||||
deinit {
|
||||
observers.values.forEach { $0?.invalidate() }
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Custom Action
|
||||
//--------------------------------------------------
|
||||
@ -91,25 +137,45 @@ public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
||||
// Verify we have the correct action type and necessary values.
|
||||
guard actionType == "selectAllBoxes",
|
||||
let selectableModels: [SelectableMoleculeModel] = delegate?.moleculeDelegate?.getRootMolecules().allMoleculesOfType(),
|
||||
!selectableModels.isEmpty,
|
||||
let model = model as? SelectAllBoxesBehaviorModel
|
||||
!selectableModels.isEmpty
|
||||
else { return false }
|
||||
|
||||
// Flip the selected state of the behavior.
|
||||
selectAllState.toggle()
|
||||
didSelectAllState.toggle()
|
||||
|
||||
// Iterate through selectable molecules.
|
||||
for selectableModel in selectableModels {
|
||||
if toSelect(model: selectableModel) || toDeselect(model: selectableModel) {
|
||||
selectableModel.select(as: selectAllState)
|
||||
selectableModel.select(as: didSelectAllState)
|
||||
}
|
||||
}
|
||||
|
||||
// Get title to update the nav button title.
|
||||
let navButtonTitle: String? = selectAllState ? model.deselectAllTitle : model.selectAllTitle
|
||||
updatePageNavigationUI()
|
||||
return true
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
/// In the event that the user manually selects or deselects all `SelectableMoleculeModel`
|
||||
/// the behavior will need to reflect the inverse of its previously expected action.
|
||||
/// Initiates the navigation and page behavior realignment
|
||||
/// - Parameter asSelectAll: The actual value didSelectAllState ought to be.
|
||||
func realignPageBehavior(asSelectAll: Bool) {
|
||||
didSelectAllState = asSelectAll
|
||||
updatePageNavigationUI()
|
||||
}
|
||||
|
||||
/// Updates the navigation UI to correctly reflect the behavior's state.
|
||||
func updatePageNavigationUI() {
|
||||
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let controller = self.delegate?.moleculeDelegate as? ViewController else { return }
|
||||
guard let model = model as? SelectAllBoxesBehaviorModel else { return }
|
||||
|
||||
let navButtonTitle: String? = didSelectAllState ? model.deselectAllTitle : model.selectAllTitle
|
||||
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
|
||||
guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return }
|
||||
controller.handleNewDataAndUpdateUI()
|
||||
|
||||
if MVMCoreUIUtility.getCurrentVisibleController() == controller {
|
||||
@ -117,21 +183,31 @@ public class SelectAllBoxesBehavior: PageCustomActionHandlerBehavior {
|
||||
controller.manager?.refreshNavigationUI()
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/// Convenience function for readability to confirmt he state of the behavior.
|
||||
/// - Returns: Boolean indicating that the behavior's `didSelectAllState` is false while all model values are true (selected).
|
||||
func selectAllIsMisaligned() -> Bool {
|
||||
!didSelectAllState && valuesMirror.values.allSatisfy { $0 == true }
|
||||
}
|
||||
|
||||
/// Convenience function for readability to confirmt he state of the behavior.
|
||||
/// - Returns: Boolean indicating that the behavior's `didSelectAllState` is true while all model values are false (deselected).
|
||||
func deselectAllIsMisaligned() -> Bool {
|
||||
didSelectAllState && valuesMirror.values.allSatisfy { $0 == false }
|
||||
}
|
||||
|
||||
/// Convenience function making it easier to read if a current selectable model should be acted on.
|
||||
/// - Parameter model: A model object assined to the SelectableModel protocol
|
||||
/// - Returns: Boolean determining if the passed model should be selected.
|
||||
func toSelect(model: SelectableMoleculeModel) -> Bool {
|
||||
selectAllState && !model.selectedValue
|
||||
didSelectAllState && !model.selectedValue
|
||||
}
|
||||
|
||||
/// Convenience function making it easier to read if a current selectable model should be acted on.
|
||||
/// - Parameter model: A model object assined to the SelectableModel protocol
|
||||
/// - Returns: Boolean determining if the passed model should be deselected.
|
||||
func toDeselect(model: SelectableMoleculeModel) -> Bool {
|
||||
!selectAllState && model.selectedValue
|
||||
!didSelectAllState && model.selectedValue
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user