diff --git a/Sources/UIPredicateEditor/Public/Cells/UIPredicateEditorCellConfiguration.swift b/Sources/UIPredicateEditor/Public/Cells/UIPredicateEditorCellConfiguration.swift index 35dae5c..a2a6381 100644 --- a/Sources/UIPredicateEditor/Public/Cells/UIPredicateEditorCellConfiguration.swift +++ b/Sources/UIPredicateEditor/Public/Cells/UIPredicateEditorCellConfiguration.swift @@ -35,7 +35,7 @@ open class UIPredicateEditorCellConfiguration: UIContentConfiguration, Equatable /// - isEditable: `true` if the fields in this row should be editable, `false` otherwise. **Default**: `true` /// - indentationLevel: the custom indentation level to apply to this row. Irrespective of the value provided here, the `rowTemplate.indentationLevel` is always checked first. /// - delegate: the content refreshing delegate which will be notified when the receiver's field or toggle values change - /// - rowMenuActionsProvider: a menu actions provider speficially used for combination rows + /// - rowMenuActionsProvider: a menu actions provider specifically used for combination rows init(rowTemplate: UIPredicateEditorRowTemplate, traitCollection: UITraitCollection, isEditable: Bool = true, indentationLevel: Int, delegate: (any UIPredicateEditorContentRefreshing)?, rowMenuActionsProvider: (() -> [UIMenuElement])?) { self.rowTemplate = rowTemplate state = UICellConfigurationState(traitCollection: traitCollection) diff --git a/Sources/UIPredicateEditor/Public/PredicateController.swift b/Sources/UIPredicateEditor/Public/PredicateController.swift index f9bde36..2f6758d 100644 --- a/Sources/UIPredicateEditor/Public/PredicateController.swift +++ b/Sources/UIPredicateEditor/Public/PredicateController.swift @@ -13,22 +13,27 @@ public extension Notification.Name { /// Notified when the predicate of the `UIPredicateEditor`will change. /// /// The `object` on the `Notification` will be the editor. + @available(*, deprecated, message: "Utilize `PredicateControllerDelegate`") static let predicateWillChange = Notification.Name(rawValue: "UIPredicateEditor.predicateWillChange") /// Notified when the predicate of the `UIPredicateEditor` changes. /// /// The `object` on the `Notification` will be the editor. + @available(*, deprecated, message: "Utilize `PredicateControllerDelegate`") static let predicateDidChange = Notification.Name(rawValue: "UIPredicateEditor.predicateDidChange") } /// Concrete final class that manages predicates, row templates, updating and notifying of predicate changes to its managing view. /// -/// The `UIPRedicateEditorViewController` class uses it internally for its predicate operations. +/// The `UIPredicateEditorViewController` class uses it internally for its predicate operations. /// /// You may choose to write your own view and use the `PredicateController` as its driving model. @MainActor @Observable public final class PredicateController { + /// The delegate which will receive updates as the controller modified the `predicate`. + public var delegate: PredicateControllerDelegate? + /// contains the predicate evaluated by the editor. /// /// If one or more parts cannot be queried from the row templates, the property evaluates to `nil`. @@ -68,7 +73,7 @@ public final class PredicateController { /// Each row will have its own predicate which is used to form the predicate on the `UIPredicateEditor`. public var requiredRowTemplates: [UIPredicateEditorRowTemplate] = [] - /// Created on-demand everytime as the formatting dictionary may change during runtime + /// Created on-demand every time as the formatting dictionary may change during runtime var formattingHelper: FormattingDictionaryHelper { FormattingDictionaryHelper(formattingDictionary: formattingDictionary ?? [:]) } @@ -144,17 +149,13 @@ public final class PredicateController { /// The receiver internally updates the derived predicate and notifies subscribers. /// - Parameter logicalType: logical type of the predicate. public func updatePredicate(for _: NSCompoundPredicate.LogicalType) { - Task { @MainActor in - notifyPredicateWillChange() - } + notifyPredicateWillChange() // We assume the first row is the root container if available. // If we have no rows, we have no predicate. - guard !requiredRowTemplates.isEmpty else { + guard !requiredRowTemplates.isEmpty, !rowTemplates.isEmpty else { predicate = nil - Task { @MainActor in - notifyPredicateDidChange() - } + notifyPredicateDidChange() return } @@ -163,11 +164,8 @@ public final class PredicateController { buildRecursivePredicate(from: $0) } - predicate = NSCompoundPredicate(type: self.rowTemplates[0].logicalType, subpredicates: predicates) - - Task { @MainActor in - notifyPredicateDidChange() - } + predicate = NSCompoundPredicate(type: rowTemplates[0].logicalType, subpredicates: predicates) + notifyPredicateDidChange() } /// Add a new row template on the receiver for the given LHS expression title. @@ -291,13 +289,13 @@ public final class PredicateController { // MARK: Notify - @MainActor func notifyPredicateWillChange() { - // @TODO: Refactor to call delegate + func notifyPredicateWillChange() { + delegate?.predicateWillChangeForPredicateController(self) NotificationCenter.default.post(name: .predicateWillChange, object: self) } - @MainActor func notifyPredicateDidChange() { - // @TODO: Refactor to call delegate + func notifyPredicateDidChange() { + delegate?.predicateDidChangeForPredicateController(self) NotificationCenter.default.post(name: .predicateDidChange, object: self) } diff --git a/Sources/UIPredicateEditor/Public/PredicateControllerDelegate.swift b/Sources/UIPredicateEditor/Public/PredicateControllerDelegate.swift new file mode 100644 index 0000000..d029404 --- /dev/null +++ b/Sources/UIPredicateEditor/Public/PredicateControllerDelegate.swift @@ -0,0 +1,22 @@ +#if os(iOS) +import Foundation + +/// Optional methods implemented by parties interested in updates to a `PredicateController` +/// +/// - note: Not marked `@objc`, so conforming types do not need to also conform to `NSObjectProtocol`. +/// The default implementation makes it so the implementer is only required to respond to 'didChange' +/// events. +@MainActor public protocol PredicateControllerDelegate { + /// Informs the receiver that a predicate is about to change. + func predicateWillChangeForPredicateController(_ predicateController: PredicateController) + + /// Informs the receiver that a predicate has been changed. + func predicateDidChangeForPredicateController(_ predicateController: PredicateController) +} + +// MARK: - Default Implementation + +public extension PredicateControllerDelegate { + func predicateWillChangeForPredicateController(_: PredicateController) {} +} +#endif diff --git a/Sources/UIPredicateEditor/Public/UIPredicateEditorRowTemplate.swift b/Sources/UIPredicateEditor/Public/UIPredicateEditorRowTemplate.swift index 268302f..ab50f88 100644 --- a/Sources/UIPredicateEditor/Public/UIPredicateEditorRowTemplate.swift +++ b/Sources/UIPredicateEditor/Public/UIPredicateEditorRowTemplate.swift @@ -14,7 +14,7 @@ import CoreData /// A template that describes available predicates and how to display them. /// -/// By default, a noncompound row template has three views: a popup (or static text field) on the left, a popup or static text field for operators, and either a popup or other view on the right. You can subclass `UIPredicateEditorRowTemplate` to create a row template with different numbers or types of views. +/// By default, a non-compound row template has three views: a popup (or static text field) on the left, a popup or static text field for operators, and either a popup or other view on the right. You can subclass `UIPredicateEditorRowTemplate` to create a row template with different numbers or types of views. @MainActor open class UIPredicateEditorRowTemplate: NSObject { /// An array of ``NSExpression`` objects that represent the left side of a predicate. @@ -77,7 +77,7 @@ import CoreData /// For values greater than zero, the row should be indented in the presenting view. Values lower than 0 should be treated as 0. public var indentationLevel: Int = 0 - /// wehn the row template is setup as a sub-predicate of a ``NSCompoundPredicate``, this ID will match the value of the parent template row. + /// when the row template is setup as a sub-predicate of a ``NSCompoundPredicate``, this ID will match the value of the parent template row. /// /// The ID will be common across all siblings. The parent will always point to an ``NSComparisonPredicate``. public var parentTemplateID: UUID? diff --git a/Sources/UIPredicateEditor/Public/UIPredicateEditorViewController.swift b/Sources/UIPredicateEditor/Public/UIPredicateEditorViewController.swift index 403d0a4..5da6b25 100644 --- a/Sources/UIPredicateEditor/Public/UIPredicateEditorViewController.swift +++ b/Sources/UIPredicateEditor/Public/UIPredicateEditorViewController.swift @@ -15,7 +15,7 @@ import UIKit /// Concrete view controller for managing and editing predicates in a user interface. /// -/// It's `open` by default, encourgaging subclassing, but can be used as is. +/// It's `open` by default, encouraging subclassing, but can be used as is. /// /// Similar to its `NSPredicateEditor` counterpart, it directly queries rows to be displayed based on its `objectValue`, an instance of `NSPredicate`, and matching rows from `rowTemplates`, and array of `UIPredicateEditorRowTemplate`. open class UIPredicateEditorViewController: UICollectionViewController { @@ -79,7 +79,7 @@ open class UIPredicateEditorViewController: UICollectionViewController { /// set to `false` once the view appears and the initial predicate is setup. private var isLoading: Bool = true - /// this is created on-demand everytime as the formatting dictionary may change during runtime + /// this is created on-demand every time as the formatting dictionary may change during runtime var formattingHelper: FormattingDictionaryHelper { FormattingDictionaryHelper(formattingDictionary: formattingDictionary ?? [:]) } @@ -87,7 +87,7 @@ open class UIPredicateEditorViewController: UICollectionViewController { // MARK: Init public init(predicate: NSPredicate, rowTemplates: [UIPredicateEditorRowTemplate], layout: UICollectionViewLayout) { - assert(!rowTemplates.isEmpty, "Initialize the UIPredicateEditor with atleast one row template") + assert(!rowTemplates.isEmpty, "Initialize the UIPredicateEditor with at least one row template") let compoundTypeRow = rowTemplates.first(where: { !$0.compoundTypes.isEmpty }) @@ -382,7 +382,8 @@ extension UIPredicateEditorViewController { private func addRowTemplate(for leftExpressionTitle: String, parentRowTemplate: UIPredicateEditorRowTemplate? = nil) { if predicateController.addRowTemplate(for: leftExpressionTitle, for: parentRowTemplate), - let logicalType = (parentRowTemplate ?? rowTemplates.first)?.logicalType { + let logicalType = (parentRowTemplate ?? rowTemplates.first)?.logicalType + { predicateController.updatePredicate(for: logicalType) } }