diff --git a/Source/IGListSwiftKit/ListIdentifiable.swift b/Source/IGListSwiftKit/ListIdentifiable.swift new file mode 100644 index 00000000..3a5d0489 --- /dev/null +++ b/Source/IGListSwiftKit/ListIdentifiable.swift @@ -0,0 +1,72 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import IGListDiffKit + +/// The `ListIdentifiable` protocol is a subset of `ListDiffable`'s functionality, +/// for use with Swift value types and `ListValueSectionController`. +/// +/// `ListIdentifiable` is an experimental, under-development API, and may change without warning in the future. +public protocol ListIdentifiable: Equatable { + var diffIdentifier: NSObjectProtocol { get } +} + +public extension ListIdentifiable { + /// Provides an object version of the value that can be passed to Objective-C APIs from + /// `IGListKit` that require objects. + /// + /// The object class is a private implementation detail of `IGListSwiftKit`. Use of this + /// API must be paired with the `ListValueSectionController` class, which unwraps the + /// value for its subclasses. + func diffable() -> ListDiffable { + return ListDiffableValueBox(value: self) + } + + /// Determines whether an arbitrary `Any` value is an object version of the identifiable value. + static func isDiffable(_ value: Any) -> Bool { + return value is ListDiffableValueBox + } + + // TODO(natesm): Should this be a public API? It is for now. + init?(diffable: Any) { + if let value = (diffable as? ListDiffableValueBox)?.value { + self = value + } else { + return nil + } + } +} + +public extension Sequence where Element: ListIdentifiable { + func diffables() -> [ListDiffable] { + return map { $0.diffable() } + } +} + +/// An internal class for boxing Swift values, for use with the `ListValueSectionController` class. +/// +/// The public boxing API is provided by a protocol extension of `ListIdentifiable`. +private final class ListDiffableValueBox: NSObject, ListDiffable { + let value: Value + + init(value: Value) { + self.value = value + } + + // MARK: - ListDiffable + func diffIdentifier() -> NSObjectProtocol { + return value.diffIdentifier + } + + func isEqual(toDiffableObject object: ListDiffable?) -> Bool { + if let other = object as? ListDiffableValueBox { + return value == other.value + } else { + return false + } + } +} diff --git a/Source/IGListSwiftKit/ListValueSectionController.swift b/Source/IGListSwiftKit/ListValueSectionController.swift new file mode 100644 index 00000000..e018c540 --- /dev/null +++ b/Source/IGListSwiftKit/ListValueSectionController.swift @@ -0,0 +1,38 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import IGListKit + +/// A generic section controller that supports any Swift value conforming to `ListIdentifiable`. +/// +/// For a type-safe base class for `ListDiffable` objects, see `ListGenericSectionController` instead. +/// +/// `ListValueSectionController` is an experimental, under-development API, and may change without warning in the future. +open class ListValueSectionController: ListSectionController { + /// The section controller's current value. + /// + /// This value will be `nil` temporarily after initialization, but is exposed as an implicitly-unwrapped + /// optional because it will be set by `didUpdate(to:)` before any other methods, like `cellForItem(at:)`, + /// are invoked. + public private(set) var value: Value! + + /// Subclasses of `ListValueSectionController` may not override `didUpdate(to:)` for objects. + /// Instead, they must use the overload for generic values. + public final override func didUpdate(to object: Any) { + if let value = Value(diffable: object) { + self.value = value + didUpdate(to: value) + } else { + fatalError("Expected object for value section controller to be a boxed \(Value.self), but it was \(object)") + } + } + + /// Updates the section controller to a new value. + /// + /// Subclasses of `ListValueSectionController` may override this method. Calling `super` is not required. + open func didUpdate(to value: Value) {} +}