diff --git a/Components/Sources/Collection/CollectionSpacerCell.swift b/Components/Sources/Collection/CollectionSpacerCell.swift deleted file mode 100644 index a53ab8aaf..000000000 --- a/Components/Sources/Collection/CollectionSpacerCell.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// CollectionSpacerCell.swift -// ReactiveDataDisplayManager -// -// Created by Никита Коробейников on 26.09.2022. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Empty collection cell with `SpacerView` -public final class CollectionSpacerCell: UICollectionViewCell, SpacerWrapper { - - public typealias Model = SpacerView.Model - - // MARK: - Properties - - public private(set) var spacer: SpacerView = .init(frame: .zero) - - // MARK: - Initialization - - public override init(frame: CGRect) { - super.init(frame: frame) - configureViews() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Components/Sources/Collection/CollectionStack/CollectionStack.swift b/Components/Sources/Collection/CollectionStack/CollectionStack.swift deleted file mode 100644 index 7b3a26cfc..000000000 --- a/Components/Sources/Collection/CollectionStack/CollectionStack.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// CollectionStack.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 30.06.2023. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Base class for collection stack (generator) -open class CollectionStack: UIView, ConfigurableItem, SelectableItem { - - // MARK: - Public properties - - public var isNeedDeselect = true - public var didSelectEvent = EmptyEvent() - public var didDeselectEvent = EmptyEvent() - - public var views: [UIView] = [] - - // MARK: - Private properties - - private var cell: StackCollectionCell? - private let stackView = UIStackView() - - // MARK: - Initialization - - /// - Parameters: - /// - space: Space between items - /// - insets: Insets for stack - /// - axis: Axis for stack - /// - items: Items for stack - public init(space: CGFloat, - insets: UIEdgeInsets, - axis: NSLayoutConstraint.Axis, - @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - - self.views = items().compactMap { item in - return (item as? UICollectionViewCell)?.contentView - ?? (item as? UITableViewCell)?.contentView - ?? item - } - super.init(frame: .zero) - setupStackView(with: space, insets: insets, axis: axis) - configure(with: views) - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Public methods - - /// Update items in stack - /// - Parameter items: new items - public func updateItems(_ items: [any ConfigurableItem]) { - self.views = items.compactMap { item in - return (item as? UICollectionViewCell)?.contentView ?? (item as? UITableViewCell)?.contentView ?? item - } - configure(with: views) - } - - /// Remove item from stack - /// - Parameter item: item for remove - public func removeItem(_ item: any ConfigurableItem) { - let view = (item as? UICollectionViewCell)?.contentView ?? (item as? UITableViewCell)?.contentView ?? item - guard let index = views.firstIndex(where: { $0 === view }) else { - return - } - views.remove(at: index) - configure(with: views) - } - - /// Update size after update content - public func updateSizeIfNeaded() { - updateFrameByContent() - cell?.configure(with: self) - } - - // MARK: - ConfigurableItem - - public func configure(with model: [UIView]) { - views = model - - stackView.removeAllArrangedSubviews() - model.forEach { view in - stackView.addArrangedSubview(view) - } - updateFrameByContent() - cell?.configure(with: self) - } - - // MARK: - Private methods - - private func setupStackView(with spacing: CGFloat, insets: UIEdgeInsets, axis: NSLayoutConstraint.Axis) { - stackView.axis = axis - stackView.alignment = .fill - stackView.spacing = spacing - stackView.attach( - to: self, - topOffset: insets.top, - bottomOffset: insets.bottom, - leftOffset: insets.left, - rightOffset: insets.right - ) - } - -} - -// MARK: - CollectionCellGenerator - -extension CollectionStack: CollectionCellGenerator { - - public func generate(collectionView: UICollectionView, for indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? StackCollectionCell else { - return UICollectionViewCell() - } - self.cell = cell - configure(with: views) - return cell - } - - public func registerCell(in collectionView: UICollectionView) { - collectionView.register(StackCollectionCell.self, forCellWithReuseIdentifier: identifier) - } - - public var identifier: String { - return String(describing: StackCollectionCell.self) - } - - public static func bundle() -> Bundle? { - return .main - } - -} - -// MARK: - Events - -public extension CollectionStack { - - func didSelectEvent(_ closure: @escaping () -> Void) -> Self { - didSelectEvent.addListner(closure) - return self - } - - func didDeselectEvent(_ closure: @escaping () -> Void) -> Self { - didDeselectEvent.addListner(closure) - return self - } - -} diff --git a/Components/Sources/Collection/CollectionStack/HorizontalCollectionStack.swift b/Components/Sources/Collection/CollectionStack/HorizontalCollectionStack.swift deleted file mode 100644 index 71a68ea26..000000000 --- a/Components/Sources/Collection/CollectionStack/HorizontalCollectionStack.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// HorizontalCollectionStack.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 12.07.2023. -// - -import ReactiveDataDisplayManager -import UIKit - -/// Base class for horizontal collection stack (generator) -open class HorizontalCollectionStack: CollectionStack { - - // MARK: - Initialization - - public init(space: CGFloat, insets: UIEdgeInsets = .zero, @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: space, insets: insets, axis: .horizontal, items: items) - } - - public init(@ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: 0, insets: .zero, axis: .horizontal, items: items) - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Components/Sources/Collection/CollectionStack/StackCollectionCell.swift b/Components/Sources/Collection/CollectionStack/StackCollectionCell.swift deleted file mode 100644 index c6746a78b..000000000 --- a/Components/Sources/Collection/CollectionStack/StackCollectionCell.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// StackCollectionCell.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 29.06.2023. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Cell - container with horizontal layout. Accepts generators of other cells, creates a single press state for them -open class StackCollectionCell: UICollectionViewCell, ConfigurableItem, ExpandableItem { - - // MARK: - ExpandableItem - - public var onHeightChanged = Event() - public var animatedExpandable = true - - // MARK: - Public methods - - public func configure(with model: UIView) { - guard !contentView.contains(model) else { - if contentView.frame != model.frame { - onHeightChanged.invoke(with: nil) - } - return - } - model.attach(to: contentView) - onHeightChanged.invoke(with: nil) - } - -} diff --git a/Components/Sources/Collection/CollectionStack/VerticalCollectionStack.swift b/Components/Sources/Collection/CollectionStack/VerticalCollectionStack.swift deleted file mode 100644 index 1b8c67cf1..000000000 --- a/Components/Sources/Collection/CollectionStack/VerticalCollectionStack.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// VerticalCollectionStack.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 12.07.2023. -// - -import ReactiveDataDisplayManager -import UIKit - -/// Base class for vertical collection stack (generator) -open class VerticalCollectionStack: CollectionStack { - - // MARK: - Initialization - - /// - Parameters: - /// - space: Space between items - /// - insets: Insets for stack - /// - items: Items for stack - public init(space: CGFloat, insets: UIEdgeInsets = .zero, @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: space, insets: insets, axis: .vertical, items: items) - } - - /// - Parameters: - /// - items: Items for stack - public init(@ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: .zero, insets: .zero, axis: .vertical, items: items) - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Components/Sources/Collection/DecorationProviders/CollectionDecorationProvider.swift b/Components/Sources/Collection/DecorationProviders/CollectionDecorationProvider.swift index 6d64094aa..31cb37f25 100644 --- a/Components/Sources/Collection/DecorationProviders/CollectionDecorationProvider.swift +++ b/Components/Sources/Collection/DecorationProviders/CollectionDecorationProvider.swift @@ -10,7 +10,7 @@ import ReactiveDataDisplayManager final class CollectionDecorationProvider: DecorationProvider where Cell.Model: Equatable { - typealias GeneratorType = DiffableCollectionCellGenerator + typealias GeneratorType = DiffableCellGenerator // MARK: - Properties diff --git a/Components/Sources/Common/Extensions/BaseCollectionManager+Extension.swift b/Components/Sources/Collection/Extensions/BaseCollectionManager+Extension.swift similarity index 100% rename from Components/Sources/Common/Extensions/BaseCollectionManager+Extension.swift rename to Components/Sources/Collection/Extensions/BaseCollectionManager+Extension.swift diff --git a/Components/Sources/Collection/Extensions/CollectionContext+StackView.swift b/Components/Sources/Collection/Extensions/CollectionContext+StackView.swift new file mode 100644 index 000000000..36fa11455 --- /dev/null +++ b/Components/Sources/Collection/Extensions/CollectionContext+StackView.swift @@ -0,0 +1,18 @@ +// +// StackView+CollectionContext.swift +// ReactiveChat_iOS +// +// Created by Никита Коробейников on 28.07.2023. +// + +import ReactiveDataDisplayManager + +public extension CollectionContext { + + /// Used to place `StackView` into other `UICollectionView` as cell + /// - Parameter model: model which contains styles and children + static func stack(model: StackView.Model) -> BaseCellGenerator> { + StackView.rddm.collectionGenerator(with: model) + } + +} diff --git a/Components/Sources/Common/Extensions/ConfigurableItem+Builder.swift b/Components/Sources/Common/Extensions/ConfigurableItem+Builder.swift deleted file mode 100644 index f3e39ad5e..000000000 --- a/Components/Sources/Common/Extensions/ConfigurableItem+Builder.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// ConfigurableItem+Builder.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 06.07.2023. -// - -import ReactiveDataDisplayManager - -public extension ConfigurableItem { - - static func buildView(with model: Self.Model, and type: CellRegisterType = .nib) -> Self { - let cell: Self? - switch type { - case .nib: - cell = Self.loadFromNib(bundle: Self.bundle() ?? .main) as? Self - case .class: - cell = Self() - } - guard let cell = cell else { - fatalError("Can't load cell \(Self.self)") - } - cell.configure(with: model) - return cell - } - -} - -@resultBuilder -public enum ConfigurableItemBuilder { - - public static func buildExpression(_ expression: any ConfigurableItem) -> [any ConfigurableItem] { - return [expression] - } - - public static func buildExpression(_ expressions: [any ConfigurableItem]) -> [any ConfigurableItem] { - return expressions - } - - public static func buildExpression(_ expression: ()) -> [any ConfigurableItem] { - return [] - } - - public static func buildBlock(_ components: [any ConfigurableItem]...) -> [any ConfigurableItem] { - return components.flatMap { $0 } - } - - public static func buildArray(_ components: [[any ConfigurableItem]]) -> [any ConfigurableItem] { - Array(components.joined()) - } - -} diff --git a/Components/Sources/Common/Extensions/ConfigurableItem+BuilderContext.swift b/Components/Sources/Common/Extensions/ConfigurableItem+BuilderContext.swift new file mode 100644 index 000000000..97e4457c6 --- /dev/null +++ b/Components/Sources/Common/Extensions/ConfigurableItem+BuilderContext.swift @@ -0,0 +1,53 @@ +// +// ConfigurableItem+BuilderContext.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 26.07.2023. +// + +import UIKit +import ReactiveDataDisplayManager + +public extension ConfigurableItem where Self: RegistrationTypeProvider { + + /// This method is used to build generator for some `UIView` which can be used as child for `StackView` + /// - Parameters: + /// - ctx: may be `ViewContext.self` + /// - model: configurable model for `UIView` + static func build(in ctx: ViewContext.Type, with model: Model) -> BaseViewGenerator { + ctx.gen(Self.self, model: model) + } + + /// This method is used to build generator for some `UITableViewCell` to place it in `UITableView` + /// - Parameters: + /// - ctx: may be `TableContext.self` + /// - model: configurable model for `UITableViewCell` + static func build(in ctx: TableContext.Type, with model: Model) -> BaseCellGenerator where Self: UITableViewCell { + ctx.gen(Self.self, model: model) + } + + /// This method is used to build generator for some `UIView` wrapped in `TableWrappedCell` to place it in `UITableView` + /// - Parameters: + /// - ctx: may be `TableContext.self` + /// - model: configurable model for `UIView` + static func build(in ctx: TableContext.Type, with model: Model) -> BaseCellGenerator> { + ctx.gen(Self.self, model: model) + } + + /// This method is used to build generator for some `UICollectionViewCell` to place it in `UICollectionView` + /// - Parameters: + /// - ctx: may be `CollectionContext.self` + /// - model: configurable model for `UIView` + static func build(in ctx: CollectionContext.Type, with model: Model) -> BaseCellGenerator where Self: UICollectionViewCell { + ctx.gen(Self.self, model: model) + } + + /// This method is used to build generator for some `UIView` wrapped in `CollectionWrappedCell` to place it in `UICollectionView` + /// - Parameters: + /// - ctx: may be `CollectionContext.self` + /// - model: configurable model for `UIView` + static func build(in ctx: CollectionContext.Type, with model: Model) -> BaseCellGenerator> { + ctx.gen(Self.self, model: model) + } + +} diff --git a/Components/Sources/Common/Extensions/ViewContext+Stack.swift b/Components/Sources/Common/Extensions/ViewContext+Stack.swift new file mode 100644 index 000000000..da0862d9f --- /dev/null +++ b/Components/Sources/Common/Extensions/ViewContext+Stack.swift @@ -0,0 +1,20 @@ +// +// ViewContext+Stack.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 21.07.2023. +// + +import UIKit +import Foundation +import ReactiveDataDisplayManager + +public extension ViewContext { + + /// Used to place `StackView` into other `StackView` + /// - Parameter model: model which contains styles and children + static func stack(model: StackView.Model) -> BaseViewGenerator { + StackView.rddm.viewGenerator(with: model, and: .class) + } + +} diff --git a/Components/Sources/Common/Models/Styles/BakgroundStyle.swift b/Components/Sources/Common/Models/Styles/BakgroundStyle.swift index 3109f5575..c284cc6d0 100644 --- a/Components/Sources/Common/Models/Styles/BakgroundStyle.swift +++ b/Components/Sources/Common/Models/Styles/BakgroundStyle.swift @@ -15,3 +15,28 @@ public enum BackgroundStyle: Equatable { // TODO: - gradient, image, bezierPath, bordered } + +// MARK: - Defaults + +public extension BackgroundStyle { + + func apply(in view: UIView) { + switch self { + case .solid(let color): + view.backgroundColor = color + } + } + +} + +public extension Optional { + + func apply(in view: UIView) { + if let self { + self.apply(in: view) + } else { + view.backgroundColor = nil + } + } + +} diff --git a/Components/Sources/Common/Models/Styles/BorderStyle.swift b/Components/Sources/Common/Models/Styles/BorderStyle.swift index a0c2ded3e..a592c6d79 100644 --- a/Components/Sources/Common/Models/Styles/BorderStyle.swift +++ b/Components/Sources/Common/Models/Styles/BorderStyle.swift @@ -25,3 +25,31 @@ public struct BorderStyle: Equatable { } } + +// MARK: - Defaults + +public extension BorderStyle { + + func apply(in view: UIView) { + view.layer.cornerRadius = cornerRadius + view.layer.borderColor = borderColor + view.layer.borderWidth = borderWidth + view.layer.maskedCorners = maskedCorners + } + +} + +public extension Optional { + + func apply(in view: UIView) { + if let self { + self.apply(in: view) + } else { + view.layer.cornerRadius = 0 + view.layer.borderColor = UIColor.clear.cgColor + view.layer.borderWidth = 0 + view.layer.maskedCorners = .init() + } + } + +} diff --git a/Components/Sources/Common/Models/Styles/StackStyle.swift b/Components/Sources/Common/Models/Styles/StackStyle.swift new file mode 100644 index 000000000..34ab9ec37 --- /dev/null +++ b/Components/Sources/Common/Models/Styles/StackStyle.swift @@ -0,0 +1,27 @@ +// +// StackStyle.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 19.07.2023. +// + +import UIKit + +public struct StackStyle: Equatable { + + public let axis: NSLayoutConstraint.Axis + public let spacing: CGFloat + public let alignment: UIStackView.Alignment + public let distribution: UIStackView.Distribution + + public init(axis: NSLayoutConstraint.Axis, + spacing: CGFloat, + alignment: UIStackView.Alignment, + distribution: UIStackView.Distribution) { + self.axis = axis + self.spacing = spacing + self.alignment = alignment + self.distribution = distribution + } + +} diff --git a/Components/Sources/Common/Models/Styles/TextValue.swift b/Components/Sources/Common/Models/Styles/TextValue.swift index 181ef0d2a..32b4d41ee 100644 --- a/Components/Sources/Common/Models/Styles/TextValue.swift +++ b/Components/Sources/Common/Models/Styles/TextValue.swift @@ -12,3 +12,27 @@ public enum TextValue: Equatable { /// Keep in mind that attributed string may re-configure other model's properties. case attributedString(NSAttributedString) } + +// MARK: - Defaults + +public extension TextValue { + + func apply(in view: UILabel) { + switch self { + case .string(let string): + view.text = string + case .attributedString(let attributedString): + view.attributedText = attributedString + } + } + + func apply(in view: UITextView) { + switch self { + case .string(let string): + view.text = string + case .attributedString(let attributedString): + view.attributedText = attributedString + } + } + +} diff --git a/Components/Sources/Common/Utils/Decoration+Provider.swift b/Components/Sources/Common/Utils/Decoration+Provider.swift index 7e0ad764d..47c6fb874 100644 --- a/Components/Sources/Common/Utils/Decoration+Provider.swift +++ b/Components/Sources/Common/Utils/Decoration+Provider.swift @@ -13,18 +13,18 @@ extension Decoration { var tableProvider: any DecorationProvider { switch self { case .space(let model): - return TableDecorationProvider(model: model, - hash: "space", - hashCombiner: CommonHashCombiner()) + return TableDecorationProvider>(model: model, + hash: "space", + hashCombiner: CommonHashCombiner()) } } var collectionProvider: any DecorationProvider { switch self { case .space(let model): - return CollectionDecorationProvider(model: model, - hash: "space", - hashCombiner: CommonHashCombiner()) + return CollectionDecorationProvider>(model: model, + hash: "space", + hashCombiner: CommonHashCombiner()) } } diff --git a/Components/Sources/Common/Views/LabelView.swift b/Components/Sources/Common/Views/LabelView.swift index d8947e32f..30dbdfbe6 100644 --- a/Components/Sources/Common/Views/LabelView.swift +++ b/Components/Sources/Common/Views/LabelView.swift @@ -135,7 +135,7 @@ extension LabelView: ConfigurableItem { label.lineBreakMode = model.layout.lineBreakMode label.numberOfLines = model.layout.numberOfLines - configureText(with: model.text) + model.text.apply(in: label) setNeedsLayout() } @@ -222,3 +222,11 @@ extension LabelView.Model { } } + +// MARK: - RegistrationTypeProvider + +extension LabelView: RegistrationTypeProvider { + + public static var prefferedRegistration: RegistrationType { .class } + +} diff --git a/Components/Sources/Common/Views/MessageView.swift b/Components/Sources/Common/Views/MessageView.swift index 4f66a0002..9762f890b 100644 --- a/Components/Sources/Common/Views/MessageView.swift +++ b/Components/Sources/Common/Views/MessageView.swift @@ -112,7 +112,7 @@ extension MessageView: ConfigurableItem { return model }) } - + /// To set it to **false**, dataDetection and tapHandler must be nil public static func selectable(_ selectable: Bool) -> Property { .init(closure: { model in @@ -203,8 +203,8 @@ extension MessageView: ConfigurableItem { wrap(subview: textView, with: model.internalEdgeInsets) - applyBackground(style: model.backgroundStyle) - applyBorder(style: model.borderStyle) + model.backgroundStyle.apply(in: self) + model.borderStyle?.apply(in: self) layoutIfNeeded() } @@ -244,12 +244,7 @@ extension MessageView: CalculatableWidthItem { private extension MessageView { func configureTextView(_ textView: UITextView, with model: Model) { - switch model.text { - case .string(let string): - textView.text = string - case .attributedString(let attributedString): - textView.attributedText = attributedString - } + model.text.apply(in: textView) textView.dataDetectorTypes = model.dataDetection?.dataDetectorTypes ?? [] textView.linkTextAttributes = model.dataDetection?.linkTextAttributes @@ -257,23 +252,6 @@ private extension MessageView { dataDetectionHandler = model.dataDetection?.handler } - func applyBackground(style: BackgroundStyle) { - switch style { - case .solid(let color): - backgroundColor = color - } - } - - func applyBorder(style: BorderStyle?) { - guard let borderStyle = style else { - return - } - layer.cornerRadius = borderStyle.cornerRadius - layer.borderColor = borderStyle.borderColor - layer.borderWidth = borderStyle.borderWidth - layer.maskedCorners = borderStyle.maskedCorners - } - func handleDataDetection(_ data: URL) { dataDetectionHandler?(data) } @@ -292,7 +270,10 @@ private extension MessageView { extension MessageView: UITextViewDelegate { - public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + public func textView(_ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction) -> Bool { handleDataDetection(URL) return false } @@ -331,5 +312,13 @@ extension MessageView.Model { } } +} + +// MARK: - RegistrationTypeProvider + +extension MessageView: RegistrationTypeProvider { + + public static var prefferedRegistration: RegistrationType { .class } + } #endif diff --git a/Components/Sources/Common/Views/SeparatorView.swift b/Components/Sources/Common/Views/SeparatorView.swift index 132c9424d..93cb3c2d4 100644 --- a/Components/Sources/Common/Views/SeparatorView.swift +++ b/Components/Sources/Common/Views/SeparatorView.swift @@ -89,3 +89,11 @@ private extension SeparatorView { } } + +// MARK: - RegistrationTypeProvider + +extension SeparatorView: RegistrationTypeProvider { + + public static var prefferedRegistration: RegistrationType { .class } + +} diff --git a/Components/Sources/Common/Views/SpacerView.swift b/Components/Sources/Common/Views/SpacerView.swift index d1070d38c..a16b66384 100644 --- a/Components/Sources/Common/Views/SpacerView.swift +++ b/Components/Sources/Common/Views/SpacerView.swift @@ -109,3 +109,11 @@ public extension SpacerWrapper where Model == SpacerView.Model { } } + +// MARK: - RegistrationTypeProvider + +extension SpacerView: RegistrationTypeProvider { + + public static var prefferedRegistration: RegistrationType { .class } + +} diff --git a/Components/Sources/Common/Views/StackView.swift b/Components/Sources/Common/Views/StackView.swift new file mode 100644 index 000000000..e281ef523 --- /dev/null +++ b/Components/Sources/Common/Views/StackView.swift @@ -0,0 +1,166 @@ +// +// StackView.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 19.07.2023. +// + +import UIKit +import ReactiveDataDisplayManager + +public final class StackView: UIView { + + // MARK: - Private Properties + + private let stackView = UIStackView() + private lazy var adapter = stackView.rddm.baseBuilder.build() + +} + +// MARK: - ConfigurableItem + +extension StackView: ConfigurableItem { + + // MARK: - Model + + public struct Model: AlignmentProvider { + + // MARK: - Editor + + public struct Property: Editor { + public typealias Model = StackView.Model + + private let closure: (Model) -> Model + + public init(closure: @escaping (Model) -> Model) { + self.closure = closure + } + + public func edit(_ model: Model) -> Model { + return closure(model) + } + + public static func children(_ value: [ViewGenerator]) -> Property { + .init(closure: { model in + var model = model + model.set(children: value) + return model + }) + } + + /// Only for stack. Cannot be included in common macros. + public static func children(@GeneratorsBuilder_ content: @escaping ViewContext.CellsBuilder) -> Property { + .init(closure: { model in + var model = model + model.set(children: content(ViewContext.self)) + return model + }) + } + + public static func style(_ value: StackStyle) -> Property { + .init(closure: { model in + var model = model + model.set(style: value) + return model + }) + } + + public static func background(_ value: BackgroundStyle?) -> Property { + .init(closure: { model in + var model = model + model.set(background: value) + return model + }) + } + + public static func alignment(_ value: Alignment) -> Property { + .init(closure: { model in + var model = model + model.set(alignment: value) + return model + }) + } + + } + + // MARK: - Public properties + + private(set) public var children: [ViewGenerator] = [] + private(set) public var style: StackStyle = .init(axis: .horizontal, + spacing: 0, + alignment: .fill, + distribution: .fill) + private(set) public var background: BackgroundStyle? + private(set) public var alignment: Alignment = .all(.zero) + + // MARK: - Mutation + + mutating func set(children: [ViewGenerator]) { + self.children = children + } + + mutating func set(style: StackStyle) { + self.style = style + } + + mutating func set(background: BackgroundStyle?) { + self.background = background + } + + mutating func set(alignment: Alignment) { + self.alignment = alignment + } + + // MARK: - Builder + + public static func build(@EditorBuilder content: (Property.Type) -> [Property]) -> Self { + return content(Property.self).reduce(.init(), { model, editor in + editor.edit(model) + }) + } + + public static func copy(of original: Model, @EditorBuilder content: (Property.Type) -> [Property]) -> Self { + return content(Property.self).reduce(original, { model, editor in + editor.edit(model) + }) + } + } + + // MARK: - Methods + + public func configure(with model: Model) { + configureConstraints() + apply(style: model.style) + model.background.apply(in: stackView) + + adapter -= .all + adapter += model.children + adapter => .reload + } + +} + +// MARK: - Private + +private extension StackView { + + func configureConstraints() { + wrap(subview: stackView, with: .zero) + } + + func apply(style: StackStyle) { + stackView.axis = style.axis + stackView.spacing = style.spacing + stackView.alignment = style.alignment + stackView.distribution = style.distribution + } + +} + +// MARK: - RegistrationTypeProvider + +extension StackView: RegistrationTypeProvider { + + public static var prefferedRegistration: RegistrationType { .class } + +} diff --git a/Components/Sources/Table/Extensions/TableContext+StackView.swift b/Components/Sources/Table/Extensions/TableContext+StackView.swift new file mode 100644 index 000000000..4965b7695 --- /dev/null +++ b/Components/Sources/Table/Extensions/TableContext+StackView.swift @@ -0,0 +1,18 @@ +// +// StackView+TableContext.swift +// ReactiveChat_iOS +// +// Created by Никита Коробейников on 28.07.2023. +// + +import ReactiveDataDisplayManager + +public extension TableContext { + + /// Used to place `StackView` into other `UITableView` as cell + /// - Parameter model: model which contains styles and children + static func stack(model: StackView.Model) -> BaseCellGenerator> { + StackView.rddm.tableGenerator(with: model) + } + +} diff --git a/Components/Sources/Table/TableSpacerCell.swift b/Components/Sources/Table/TableSpacerCell.swift deleted file mode 100644 index f1f1a9bf4..000000000 --- a/Components/Sources/Table/TableSpacerCell.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// TableSpacerCell.swift -// ReactiveDataDisplayManager -// -// Created by Никита Коробейников on 26.09.2022. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Empty table cell with `SpacerView` -public final class TableSpacerCell: UITableViewCell, SpacerWrapper { - - public typealias Model = SpacerView.Model - - // MARK: - Properties - - public private(set) var spacer: SpacerView = .init(frame: .zero) - - // MARK: - Initialization - - public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - configureViews() - selectionStyle = .none - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Components/Sources/Table/TableStack/HorizontalTableStack.swift b/Components/Sources/Table/TableStack/HorizontalTableStack.swift deleted file mode 100644 index 8b8343079..000000000 --- a/Components/Sources/Table/TableStack/HorizontalTableStack.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// HorizontalTableStack.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 12.07.2023. -// - -import ReactiveDataDisplayManager -import UIKit - -/// Base class for horizontal table stack (generator) -open class HorizontalTableStack: TableStack { - - // MARK: - Initialization - - /// - Parameters: - /// - space: Space between items - /// - insets: Insets for stack - /// - items: Items for stack - public init(space: CGFloat, insets: UIEdgeInsets = .zero, @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: space, insets: insets, axis: .horizontal, items: items) - } - - /// - Parameters: - /// - items: Items for stack - public init(@ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: .zero, insets: .zero, axis: .horizontal, items: items) - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Components/Sources/Table/TableStack/StackTableCell.swift b/Components/Sources/Table/TableStack/StackTableCell.swift deleted file mode 100644 index 9791f8338..000000000 --- a/Components/Sources/Table/TableStack/StackTableCell.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// StackTableCell.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 29.06.2023. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Cell - container with horizontal layout. Accepts generators of other cells, creates a single press state for them -open class StackTableCell: UITableViewCell, ConfigurableItem, ExpandableItem { - - // MARK: - ExpandableItem - - public var onHeightChanged = Event() - public var animatedExpandable = true - - // MARK: - Public methods - - public func configure(with model: UIView) { - guard !contentView.contains(model) else { - if contentView.frame != model.frame { - onHeightChanged.invoke(with: nil) - } - return - } - model.attach(to: contentView) - onHeightChanged.invoke(with: nil) - selectionStyle = .none - } - -} diff --git a/Components/Sources/Table/TableStack/TableStack.swift b/Components/Sources/Table/TableStack/TableStack.swift deleted file mode 100644 index 911fdb08b..000000000 --- a/Components/Sources/Table/TableStack/TableStack.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// TableStack.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 30.06.2023. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Base class for table stack (generator) -open class TableStack: UIView, ConfigurableItem, SelectableItem { - - // MARK: - Public properties - - public var isNeedDeselect = true - public var didSelectEvent = EmptyEvent() - public var didDeselectEvent = EmptyEvent() - - public var cell: StackTableCell? - - // MARK: - Private properties - - private var views: [UIView] = [] - private let stackView = UIStackView() - - // MARK: - Initialization - - /// - Parameters: - /// - space: Space between items - /// - insets: Insets for stack - /// - axis: Axis for stack - /// - items: Items for stack - public init(space: CGFloat, - insets: UIEdgeInsets, - axis: NSLayoutConstraint.Axis, - @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - - self.views = items().compactMap { item in - return (item as? UICollectionViewCell)?.contentView - ?? (item as? UITableViewCell)?.contentView - ?? item - } - super.init(frame: .zero) - setupStackView(with: space, insets: insets, axis: axis) - configure(with: views) - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Public methods - - /// Update items in stack - /// - Parameter items: new items - public func updateItems(_ items: [any ConfigurableItem]) { - self.views = items.compactMap { item in - return (item as? UICollectionViewCell)?.contentView ?? (item as? UITableViewCell)?.contentView ?? item - } - configure(with: views) - } - - /// Remove item from stack - /// - Parameter item: item for remove - public func removeItem(_ item: any ConfigurableItem) { - let view = (item as? UICollectionViewCell)?.contentView ?? (item as? UITableViewCell)?.contentView ?? item - guard let index = views.firstIndex(where: { $0 === view }) else { - return - } - views.remove(at: index) - configure(with: views) - } - - /// Update size after update content - public func updateSizeIfNeaded() { - updateFrameByContent() - cell?.configure(with: self) - } - - // MARK: - ConfigurableItem - - public func configure(with model: [UIView]) { - views = model - - stackView.removeAllArrangedSubviews() - model.forEach { view in - stackView.addArrangedSubview(view) - } - updateFrameByContent() - cell?.configure(with: self) - } - - // MARK: - Private methods - - private func setupStackView(with spacing: CGFloat, insets: UIEdgeInsets, axis: NSLayoutConstraint.Axis) { - stackView.axis = axis - stackView.alignment = .fill - stackView.spacing = spacing - stackView.attach( - to: self, - topOffset: insets.top, - bottomOffset: insets.bottom, - leftOffset: insets.left, - rightOffset: insets.right - ) - } - -} - -// MARK: - TableCellGenerator - -extension TableStack: TableCellGenerator { - - public var identifier: String { - String(describing: StackTableCell.self) - } - - public func generate(tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? StackTableCell else { - return UITableViewCell() - } - self.cell = cell - configure(with: views) - return cell - } - - public func registerCell(in tableView: UITableView) { - tableView.register(StackTableCell.self, forCellReuseIdentifier: identifier) - } - - public static func bundle() -> Bundle? { - return .main - } - -} - -// MARK: - Events - -public extension TableStack { - - func didSelectEvent(_ closure: @escaping () -> Void) -> Self { - didSelectEvent.addListner(closure) - return self - } - - func didDeselectEvent(_ closure: @escaping () -> Void) -> Self { - didDeselectEvent.addListner(closure) - return self - } - -} diff --git a/Components/Sources/Table/TableStack/VerticalTableStack.swift b/Components/Sources/Table/TableStack/VerticalTableStack.swift deleted file mode 100644 index 4e9d5be45..000000000 --- a/Components/Sources/Table/TableStack/VerticalTableStack.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// VerticalTableStack.swift -// ReactiveDataDisplayManager -// -// Created by Konstantin Porokhov on 12.07.2023. -// - -import UIKit -import ReactiveDataDisplayManager - -/// Base class for vertical table stack (generator) -open class VerticalTableStack: TableStack { - - // MARK: - Initialization - - /// - Parameters: - /// - space: Space between items - /// - insets: Insets for stack - /// - items: Items for stack - public init(space: CGFloat, insets: UIEdgeInsets = .zero, @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: space, insets: insets, axis: .vertical, items: items) - } - - /// - Parameters: - /// - items: Items for stack - public init(@ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: .zero, insets: .zero, axis: .vertical, items: items) - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Components/Sources/Table/TableWrappedCell+RDDM.swift b/Components/Sources/Table/TableWrappedCell+RDDM.swift deleted file mode 100644 index 429137581..000000000 --- a/Components/Sources/Table/TableWrappedCell+RDDM.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// TableWrappedCell+RDDM.swift -// ReactiveDataDisplayManager -// -// Created by Никита Коробейников on 05.06.2023. -// - -import ReactiveDataDisplayManager -import UIKit - -public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem { - - func tableGenerator(with model: Base.Model, - and registerType: CellRegisterType = .nib) -> BaseCellGenerator> { - TableWrappedCell.rddm.baseGenerator(with: model, - and: registerType) - } - -} - -// MARK: - CalculatableHeightItem - -public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem & CalculatableHeightItem { - - func tableCalculatableHightGenerator( - with model: Base.Model, - and registerType: CellRegisterType = .nib - ) -> CalculatableHeightCellGenerator> { - - CalculatableHeightCellGenerator>(with: model, - registerType: registerType) - } - -} diff --git a/Example/ReactiveDataDisplayManager/Collection/CollectionViewController/CollectionViewController.swift b/Example/ReactiveDataDisplayManager/Collection/CollectionViewController/CollectionViewController.swift index bbc6b5e7a..050e47d98 100644 --- a/Example/ReactiveDataDisplayManager/Collection/CollectionViewController/CollectionViewController.swift +++ b/Example/ReactiveDataDisplayManager/Collection/CollectionViewController/CollectionViewController.swift @@ -64,14 +64,13 @@ private extension CollectionViewController { CollectionGenerators { titles.map { title -> CollectionCellGenerator in // Create generator - let generator = TitleCollectionViewCell.rddm.calculatableHeightGenerator(with: title, referencedWidth: 100) - generator.didSelectEvent += { - debugPrint("\(title) selected") - } - generator.didDeselectEvent += { - debugPrint("\(title) deselected") - } - return generator + return TitleCollectionViewCell.rddm.calculatableHeightGenerator(with: title, referencedWidth: 100) + .didSelectEvent { + debugPrint("\(title) selected") + } + .didDeselectEvent { + debugPrint("\(title) deselected") + } } } } diff --git a/Example/ReactiveDataDisplayManager/Collection/DiffableCollectionViewController/DiffableCollectionViewController.swift b/Example/ReactiveDataDisplayManager/Collection/DiffableCollectionViewController/DiffableCollectionViewController.swift index f4ec6bab8..65cbe2b82 100644 --- a/Example/ReactiveDataDisplayManager/Collection/DiffableCollectionViewController/DiffableCollectionViewController.swift +++ b/Example/ReactiveDataDisplayManager/Collection/DiffableCollectionViewController/DiffableCollectionViewController.swift @@ -11,7 +11,7 @@ import ReactiveDataComponents @available(iOS 13.0, *) final class DiffableCollectionViewController: UIViewController { - typealias DiffableGenerator = DiffableCollectionCellGenerator + typealias DiffableGenerator = DiffableCellGenerator // MARK: - Constants diff --git a/Example/ReactiveDataDisplayManager/Collection/DifferenceCollectionViewController/DifferenceCollectionViewController.swift b/Example/ReactiveDataDisplayManager/Collection/DifferenceCollectionViewController/DifferenceCollectionViewController.swift index 0de1af2b4..de753ae65 100644 --- a/Example/ReactiveDataDisplayManager/Collection/DifferenceCollectionViewController/DifferenceCollectionViewController.swift +++ b/Example/ReactiveDataDisplayManager/Collection/DifferenceCollectionViewController/DifferenceCollectionViewController.swift @@ -9,7 +9,7 @@ import ReactiveDataDisplayManager final class DifferenceCollectionViewController: UIViewController { - typealias DiffableGenerator = DiffableCollectionCellGenerator + typealias DiffableGenerator = DiffableCellGenerator // MARK: - Constants diff --git a/Example/ReactiveDataDisplayManager/Collection/FoldableCollectionViewController/FoldableCollectionViewController.swift b/Example/ReactiveDataDisplayManager/Collection/FoldableCollectionViewController/FoldableCollectionViewController.swift index af329e46b..2550fb485 100644 --- a/Example/ReactiveDataDisplayManager/Collection/FoldableCollectionViewController/FoldableCollectionViewController.swift +++ b/Example/ReactiveDataDisplayManager/Collection/FoldableCollectionViewController/FoldableCollectionViewController.swift @@ -79,14 +79,14 @@ private extension FoldableCollectionViewController { let child3 = makeRegularCellWithTitlesGenerators(count: 2) // Create foldable generators - let folder1 = makeFoldableCellGenerator(color: .lightGray, expanded: false) - let folder2 = makeFoldableCellGenerator(color: .lightGray, expanded: false) - let folder3 = makeFoldableCellGenerator(color: .lightGray, expanded: true) + var folder1 = makeFoldableCellGenerator(color: .lightGray, expanded: false) + var folder2 = makeFoldableCellGenerator(color: .lightGray, expanded: false) + var folder3 = makeFoldableCellGenerator(color: .lightGray, expanded: true) // Configure relationship - folder3.childGenerators = child3 - folder2.childGenerators = child2 + [folder3] - folder1.childGenerators = child1 + [folder2] + folder3.children = child3 + folder2.children = child2 + [folder3] + folder1.children = child1 + [folder2] // Add foldable cell generators to adapter let visibleGenerators = getVisibleGenerators(for: folder1) @@ -96,7 +96,7 @@ private extension FoldableCollectionViewController { adapter => .reload } - func makeFoldableCellGenerator(color: UIColor, expanded: Bool) -> CollectionCellGenerator & CollectionFoldableItem { + func makeFoldableCellGenerator(color: UIColor, expanded: Bool) -> CollectionCellGenerator & CollectionChildrenHolder { // Create foldable generator let generator = FoldableCollectionViewCell.rddm.foldableGenerator(with: .init(color: color)) @@ -130,8 +130,8 @@ private extension FoldableCollectionViewController { } func getVisibleGenerators(for generator: CollectionCellGenerator) -> [CollectionCellGenerator] { - if let foldableItem = generator as? CollectionFoldableItem, foldableItem.isExpanded { - return foldableItem.childGenerators + if let foldableItem = generator as? FoldableItem & CollectionChildrenHolder, foldableItem.isExpanded { + return foldableItem.children .map { getVisibleGenerators(for: $0) } .reduce([generator], +) } else { diff --git a/Example/ReactiveDataDisplayManager/Collection/StackCellExampleCollectionViewController/StackCellExampleCollectionViewController.swift b/Example/ReactiveDataDisplayManager/Collection/StackCellExampleCollectionViewController/StackCellExampleCollectionViewController.swift index 3d7aa094b..abdcabc95 100644 --- a/Example/ReactiveDataDisplayManager/Collection/StackCellExampleCollectionViewController/StackCellExampleCollectionViewController.swift +++ b/Example/ReactiveDataDisplayManager/Collection/StackCellExampleCollectionViewController/StackCellExampleCollectionViewController.swift @@ -23,7 +23,6 @@ final class StackCellExampleCollectionViewController: UIViewController { // MARK: - IBOutlet @IBOutlet private weak var collectionView: UICollectionView! - private lazy var titleCell = TitleTableViewCell.buildView(with: "Title") // MARK: - Private Properties @@ -35,20 +34,6 @@ final class StackCellExampleCollectionViewController: UIViewController { .add(plugin: .accessibility()) .build() - lazy var horizontalNestedStackCell = HorizontalCollectionStack { - TitleTableViewCell.buildView(with: "Text 3") - TitleTableViewCell.buildView(with: "Text 4") - } - - lazy var verticalStackCell = VerticalCollectionStack(space: 8) { - titleCell - .set(font: UIFont.boldSystemFont(ofSize: 20)) - TitleTableViewCell.buildView(with: "Text 1") - TitleTableViewCell.buildView(with: "Text 2") - horizontalNestedStackCell - SeparatorView.buildView(with: .init(size: .height(1), color: .lightGray), and: .class) - } - // MARK: - UIViewController override func viewDidLoad() { @@ -80,27 +65,47 @@ private extension StackCellExampleCollectionViewController { /// This method is used to fill adapter func fillAdapter() { - // Add stack generators into adapter - adapter += verticalStackCell - .didSelectEvent { [weak self] in - self?.cellBaseState.toggle() - self?.titleCell.configure(with: (self?.cellBaseState ?? true) ? "Title" : "Very very very long title") - self?.verticalStackCell.updateSizeIfNeaded() - } - - adapter += VerticalCollectionStack { - SeparatorView.buildView(with: .init(size: .height(1), color: .black), and: .class) - - HorizontalCollectionStack { - SeparatorView.buildView(with: .init(size: .width(1), color: .black), and: .class) - SpacerView.buildView(with: .init(size: .width(64)), and: .class) - - TitleTableViewCell.buildView(with: "Some text") - - SeparatorView.buildView(with: .init(size: .width(1), color: .black), and: .class) - } - - SeparatorView.buildView(with: .init(size: .height(1), color: .black), and: .class) + adapter += CollectionSection.create(header: EmptyCollectionHeaderGenerator(), footer: EmptyCollectionFooterGenerator()) { ctx in + StackView.build(in: ctx, with: .build { hStack in + hStack.style(.init(axis: .horizontal, + spacing: 8, + alignment: .fill, + distribution: .fill)) + hStack.background(.solid(.gray)) + hStack.children { ctx in + SeparatorView.build(in: ctx, with: .init(size: .width(1), color: .black)) + LabelView.build(in: ctx, with: .build { label in + label.text(.string("Some title")) + label.textAlignment(.left) + label.style(.init(color: .black, font: .preferredFont(forTextStyle: .headline))) + }) + SpacerView.build(in: ctx, with: .init(size: .width(16))) + StackView.build(in: ctx, with: .build { vStack in + vStack.style(.init(axis: .vertical, + spacing: 2, + alignment: .center, + distribution: .fill)) + vStack.background(.solid(.rddm)) + vStack.children { ctx in + LabelView.build(in: ctx, with: .build { label in + label.text(.string("Body 1")) + label.textAlignment(.right) + label.style(.init(color: .black, font: .preferredFont(forTextStyle: .body))) + }) + LabelView.build(in: ctx, with: .build { label in + label.text(.string("Body 2")) + label.textAlignment(.right) + label.style(.init(color: .black, font: .preferredFont(forTextStyle: .body))) + }) + } + }) + SeparatorView.build(in: ctx, with: .init(size: .width(1), color: .black)) + } + }) + TitleCollectionViewCell.build(in: ctx, with: "Some text outside of stack") + .didSelectEvent { + print("Did select cell outside of stack") + } } // Tell adapter that we've changed generators diff --git a/Example/ReactiveDataDisplayManager/Collection/TwoDirectionPaginatableCollectionViewController/TwoDirectionPaginatableCollectionViewController.swift b/Example/ReactiveDataDisplayManager/Collection/TwoDirectionPaginatableCollectionViewController/TwoDirectionPaginatableCollectionViewController.swift index f65514bb1..e95bb9af8 100644 --- a/Example/ReactiveDataDisplayManager/Collection/TwoDirectionPaginatableCollectionViewController/TwoDirectionPaginatableCollectionViewController.swift +++ b/Example/ReactiveDataDisplayManager/Collection/TwoDirectionPaginatableCollectionViewController/TwoDirectionPaginatableCollectionViewController.swift @@ -57,7 +57,7 @@ final class TwoDirectionPaginatableCollectionViewController: UIViewController { private var currentTopPage = 0 private var currentBottomPage = 0 - private lazy var emptyCell = CollectionSpacerCell.rddm.baseGenerator(with: CollectionSpacerCell.Model(size: .height(0)), and: .class) + private lazy var emptyCell = SpacerView.rddm.collectionGenerator(with: .init(size: .height(0))) // MARK: - UIViewController diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/DynamicHeightViewCell/DynamicHeightTableViewCell.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/DynamicHeightViewCell/DynamicHeightTableViewCell.swift index aa3116ce6..4cd9ae38f 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/DynamicHeightViewCell/DynamicHeightTableViewCell.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/DynamicHeightViewCell/DynamicHeightTableViewCell.swift @@ -59,7 +59,7 @@ private extension DynamicHeightTableViewCell { } func makeDynamicHeightCellGenerators() -> [CollectionCellGenerator] { - return makeModels().map { BaseCollectionCellGenerator(with: $0) } + return makeModels().map { BaseCellGenerator(with: $0) } } func makeModels() -> [UIColor] { diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/ImageCollectionCellGenerator.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/ImageCollectionCellGenerator.swift index c7c1d593c..f763248fc 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/ImageCollectionCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/ImageCollectionCellGenerator.swift @@ -9,7 +9,7 @@ import Foundation import ReactiveDataDisplayManager -final class ImageCollectionCellGenerator: BaseCollectionCellGenerator, PrefetcherableItem { +final class ImageCollectionCellGenerator: BaseCellGenerator, PrefetcherableItem { // MARK: - PrefetcherableFlow diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/MovableCollectionCellGenerator.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/MovableCollectionCellGenerator.swift index 53f6bb950..0481fa0b4 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/MovableCollectionCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/MovableCollectionCellGenerator.swift @@ -8,7 +8,7 @@ import Foundation import ReactiveDataDisplayManager -final class MovableCollectionCellGenerator: BaseCollectionCellGenerator, MovableItem { +final class MovableCollectionCellGenerator: BaseCellGenerator, MovableItem { // MARK: - Properties diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/SwipeableCollectionGenerator.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/SwipeableCollectionGenerator.swift index 4e0603711..ceeb12ee2 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/SwipeableCollectionGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/SwipeableCollectionGenerator.swift @@ -8,7 +8,7 @@ import ReactiveDataDisplayManager -final class SwipeableCollectionGenerator: BaseCollectionCellGenerator, SwipeableItem { +final class SwipeableCollectionGenerator: BaseCellGenerator, SwipeableItem { // MARK: - SwipeableItem diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/TitleCollectionGenerator.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/TitleCollectionGenerator.swift index 31ef5bfa6..34cb57c84 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/TitleCollectionGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/TitleCollectionGenerator.swift @@ -9,7 +9,7 @@ import UIKit import ReactiveDataDisplayManager -final class TitleCollectionGenerator: BaseCollectionCellGenerator, IndexTitleDisplaybleItem { +final class TitleCollectionGenerator: BaseCellGenerator, IndexTitleDisplaybleItem { // MARK: - IndexTitleDisplayble diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/VerticalSizableTextGenerator.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/VerticalSizableTextGenerator.swift index 160b013ec..db97b6a4e 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/VerticalSizableTextGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/Generators/VerticalSizableTextGenerator.swift @@ -9,7 +9,7 @@ import UIKit import ReactiveDataDisplayManager -final class VerticalSizableTextGenerator: BaseCollectionCellGenerator { +final class VerticalSizableTextGenerator: BaseCellGenerator { // MARK: - Private Properties diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/HighlitableStackCells/StackCollectionCell+HighlitableItem.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/HighlitableStackCells/StackCollectionCell+HighlitableItem.swift deleted file mode 100644 index 4279f6c34..000000000 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/HighlitableStackCells/StackCollectionCell+HighlitableItem.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// StackCollectionCell+HighlitableItem.swift -// ReactiveDataDisplayManagerExample_iOS -// -// Created by Konstantin Porokhov on 29.06.2023. -// - -import ReactiveDataComponents -import ReactiveDataDisplayManager -import UIKit - -extension StackCollectionCell: HighlightableItem { - - // MARK: - HighlightableItem - - public func applyUnhighlightedStyle() { - UIView.animate(withDuration: 0.2) { - self.contentView.backgroundColor = .white - } - } - - public func applyHighlightedStyle() { - UIView.animate(withDuration: 0.2) { - self.contentView.backgroundColor = .lightGray.withAlphaComponent(0.3) - } - } - -} diff --git a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/TitleCollectionViewCell/TitleCollectionViewCell.swift b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/TitleCollectionViewCell/TitleCollectionViewCell.swift index 8256c69c1..b9976ecf3 100644 --- a/Example/ReactiveDataDisplayManager/Collection/Views/Cells/TitleCollectionViewCell/TitleCollectionViewCell.swift +++ b/Example/ReactiveDataDisplayManager/Collection/Views/Cells/TitleCollectionViewCell/TitleCollectionViewCell.swift @@ -99,3 +99,13 @@ private extension TitleCollectionViewCell { accessibilityInvalidator?.invalidateParameters() } } + +// MARK: - RegistrationTypeProvider + +extension TitleCollectionViewCell: RegistrationTypeProvider { + + static var prefferedRegistration: RegistrationType { + .nib + } + +} diff --git a/Example/ReactiveDataDisplayManager/Stack/Landing/LandingStackViewController.swift b/Example/ReactiveDataDisplayManager/Stack/Landing/LandingStackViewController.swift index c02e92bf2..81c67b37e 100644 --- a/Example/ReactiveDataDisplayManager/Stack/Landing/LandingStackViewController.swift +++ b/Example/ReactiveDataDisplayManager/Stack/Landing/LandingStackViewController.swift @@ -80,7 +80,7 @@ private extension LandingStackViewController { alignment: .center, distribution: .fillEqually, spacing: 32), - childGenerators: [mainButton, secondaryButton]) + children: [mainButton, secondaryButton]) // Add generators to adapter adapter += title diff --git a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/ButtonStackCellGenerator.swift b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/ButtonStackCellGenerator.swift index ee168850e..e00f08e71 100644 --- a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/ButtonStackCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/ButtonStackCellGenerator.swift @@ -8,7 +8,7 @@ import ReactiveDataDisplayManager import UIKit -final class ButtonStackCellGenerator: StackCellGenerator { +final class ButtonStackCellGenerator: ViewGenerator { // MARK: - Nested diff --git a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/InnerStackCellGenerator.swift b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/InnerStackCellGenerator.swift index f172b80a7..745205de7 100644 --- a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/InnerStackCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/InnerStackCellGenerator.swift @@ -8,7 +8,7 @@ import UIKit import ReactiveDataDisplayManager -final class InnerStackCellGenerator: StackCellGenerator { +final class InnerStackCellGenerator: ViewGenerator { // MARK: - Model @@ -22,13 +22,13 @@ final class InnerStackCellGenerator: StackCellGenerator { // MARK: - Properties private let model: Model - private let childGenerators: [StackCellGenerator] + private let children: [ViewGenerator] // MARK: - Initialization - init(model: Model, childGenerators: [StackCellGenerator]) { + init(model: Model, children: [ViewGenerator]) { self.model = model - self.childGenerators = childGenerators + self.children = children } } @@ -47,7 +47,7 @@ extension InnerStackCellGenerator: ViewBuilder { let adapter = view.rddm.baseBuilder .build() - adapter += childGenerators + adapter += children adapter => .reload } diff --git a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TextStackCellGenerator.swift b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TextStackCellGenerator.swift index 5bbbdb131..9a99cb6a4 100644 --- a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TextStackCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TextStackCellGenerator.swift @@ -8,7 +8,7 @@ import UIKit import ReactiveDataDisplayManager -final class TextStackCellGenerator: StackCellGenerator { +final class TextStackCellGenerator: ViewGenerator { // MARK: - Model diff --git a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TitleStackCellGenerator.swift b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TitleStackCellGenerator.swift index c93ab1c58..d263e9480 100644 --- a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TitleStackCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/TitleStackCellGenerator.swift @@ -9,7 +9,7 @@ import ReactiveDataDisplayManager import UIKit -final class TitleStackCellGenerator: StackCellGenerator { +final class TitleStackCellGenerator: ViewGenerator { // MARK: - Properties diff --git a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/UnrollStackCellGenerator.swift b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/UnrollStackCellGenerator.swift index a3f6055c9..1feea7f08 100644 --- a/Example/ReactiveDataDisplayManager/Stack/Views/Generators/UnrollStackCellGenerator.swift +++ b/Example/ReactiveDataDisplayManager/Stack/Views/Generators/UnrollStackCellGenerator.swift @@ -7,7 +7,7 @@ import ReactiveDataDisplayManager -final class UnrollStackCellGenerator: StackCellGenerator { +final class UnrollStackCellGenerator: ViewGenerator { // MARK: - Properties diff --git a/Example/ReactiveDataDisplayManager/Table/AllPluginsTableViewController/AllPluginsTableViewController.swift b/Example/ReactiveDataDisplayManager/Table/AllPluginsTableViewController/AllPluginsTableViewController.swift index a1634e756..f07136e9e 100644 --- a/Example/ReactiveDataDisplayManager/Table/AllPluginsTableViewController/AllPluginsTableViewController.swift +++ b/Example/ReactiveDataDisplayManager/Table/AllPluginsTableViewController/AllPluginsTableViewController.swift @@ -199,7 +199,7 @@ private extension AllPluginsTableViewController { let generator = FoldableTableViewCell.rddm.foldableGenerator(with: .init(title: "", isExpanded: false)) // Create and add child generators - generator.childGenerators = Constants.titles.map { TitleTableViewCell.rddm.baseGenerator(with: $0) } + generator.children = Constants.titles.map { TitleTableViewCell.rddm.baseGenerator(with: $0) } // Add generator to adapter adapter += generator diff --git a/Example/ReactiveDataDisplayManager/Table/ComponentsOverviewTableViewController/ComponentsOverviewTableViewController.swift b/Example/ReactiveDataDisplayManager/Table/ComponentsOverviewTableViewController/ComponentsOverviewTableViewController.swift index ff15d74d5..6d9770c13 100644 --- a/Example/ReactiveDataDisplayManager/Table/ComponentsOverviewTableViewController/ComponentsOverviewTableViewController.swift +++ b/Example/ReactiveDataDisplayManager/Table/ComponentsOverviewTableViewController/ComponentsOverviewTableViewController.swift @@ -17,8 +17,33 @@ final class ComponentsOverviewTableViewController: UIViewController { // MARK: - Constants - private enum Constants { - static let titleForRegularCell = "Lorem ipsum dolor sit amet" + private enum Messages: String, CaseIterable { + case hi + case hello + case question = "Are you here?" + case answer = "Yes, I'm here" + case bye + case long = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed nunc id nisl aliquet ultrices. Integer auctor, nisl euismod efficitur maximus, urna diam aliquam tellus, sed lacinia velit nibh eget sem. Nullam sed nisi eget nisl tincidunt malesuada. Donec euismod, turpis et tincidunt luctus, nisl nunc ultricies nunc, non aliquam velit magna id urna. Nullam vel nunc auctor, luctus urna in, aliquet libero. Donec euismod, turpis et tincidunt luctus, nisl nunc ultricies nunc, non aliquam velit magna id urna. Nullam vel nunc auctor, luctus urna in, aliquet libero." + case link = "Check out link: https://stackoverflow.com" + + var preparedForSent: Bool { + switch self { + case .link, .hi, .answer: + return false + default: + return true + } + } + + var preparedForReceived: Bool { + switch self { + case .hello, .question: + return false + default: + return true + } + } + } // MARK: - IBOutlet @@ -45,7 +70,7 @@ final class ComponentsOverviewTableViewController: UIViewController { right: 0))) } - private lazy var recievedMessageTimeGenerator = LabelView.rddm.tableGenerator(with: recievedMessageTimeModel, and: .class) + private lazy var recievedMessageTimeGenerator = LabelView.rddm.tableGenerator(with: recievedMessageTimeModel) // Sent message time private let sentTimeMessageStyle = TextStyle(color: .gray, font: .preferredFont(forTextStyle: .caption1)) @@ -61,7 +86,7 @@ final class ComponentsOverviewTableViewController: UIViewController { right: 16))) } - private lazy var sentMessageTimeGenerator = LabelView.rddm.tableGenerator(with: sentTimeMessageModel, and: .class) + private lazy var sentMessageTimeGenerator = LabelView.rddm.tableGenerator(with: sentTimeMessageModel) // Date private let dateStyle = TextStyle(color: .black, font: .preferredFont(forTextStyle: .caption1)) @@ -77,32 +102,13 @@ final class ComponentsOverviewTableViewController: UIViewController { right: 0))) } - private lazy var dateGenerator = LabelView.rddm.tableGenerator(with: dateModel, and: .class) + private lazy var dateGenerator = LabelView.rddm.tableGenerator(with: dateModel) // Sent message private let sentMessageStyle = TextStyle(color: .white, font: .preferredFont(forTextStyle: .body)) private let sentMessageBorderStyle = BorderStyle(cornerRadius: 9, maskedCorners: [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMinYCorner]) - private lazy var sentMessageModel: MessageView.Model = .build { property in - let backgorundColor: UIColor? = Bool.random() ? .systemBlue : .rddm - property.background(.solid(backgorundColor)) - property.border(sentMessageBorderStyle) - property.style(sentMessageStyle) - property.textAlignment(.right) - property.alignment(.trailing(UIEdgeInsets(top: 12, - left: UIScreen.main.bounds.width / 2, - bottom: 12, - right: 16) - )) - property.insets(UIEdgeInsets(top: 3, - left: 5, - bottom: 3, - right: 5)) - property.text(.string("Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet")) - } - - private lazy var sentMessageGenerator = MessageView.rddm.tableGenerator(with: sentMessageModel, and: .class) // Recieved message private let recievedMessageStyle = TextStyle(color: .black, font: .preferredFont(forTextStyle: .body)) @@ -121,30 +127,13 @@ final class ComponentsOverviewTableViewController: UIViewController { linkTextAttributes: [.foregroundColor: UIColor.blue], handler: dataDetectionHandler, dataDetectorTypes: [.link]) - private lazy var recievedMessageModel: MessageView.Model = .build { property in - property.border(recievedMessageBorderStyle) - property.style(recievedMessageStyle) - property.alignment(.leading(UIEdgeInsets(top: 12, - left: 16, - bottom: 12, - right: UIScreen.main.bounds.width / 2))) - property.insets(UIEdgeInsets(top: 3, - left: 5, - bottom: 3, - right: 5)) - property.text(.string("Check out link: https://stackoverflow.com/")) - property.dataDetection(dataDetection) - property.selectable(true) - } - - private lazy var recievedMessageGenerator = MessageView.rddm.tableGenerator(with: recievedMessageModel, and: .class) // Separator private let separatorModel = SeparatorModel(size: .height(1), color: .lightGray, edgeInsets: UIEdgeInsets(top: 0, left: 32, bottom: 0, right: 32)) - private lazy var separatorGenerator = TableWrappedCell.rddm.baseGenerator(with: separatorModel, and: .class) + private lazy var separatorGenerator = SeparatorView.rddm.tableGenerator(with: separatorModel) // MARK: - UIViewController @@ -179,19 +168,50 @@ private extension ComponentsOverviewTableViewController { } func generateSentMessages() -> [TableCellGenerator] { - var generators = [TableCellGenerator]() - for _ in 0..<20 { - generators.append(sentMessageGenerator) - } - return generators + Messages.allCases + .filter { $0.preparedForSent } + .map { $0.rawValue } + .map { message in + MessageView.rddm.tableGenerator(with: .build { property in + property.background(.solid(.rddm)) + property.border(sentMessageBorderStyle) + property.style(sentMessageStyle) + property.textAlignment(.right) + property.alignment(.trailing(UIEdgeInsets(top: 12, + left: UIScreen.main.bounds.width / 2, + bottom: 12, + right: 16) + )) + property.insets(UIEdgeInsets(top: 3, + left: 5, + bottom: 3, + right: 5)) + property.text(.string(message)) + }) + } } func generateRecievedMessages() -> [TableCellGenerator] { - var generators = [TableCellGenerator]() - for _ in 0..<30 { - generators.append(recievedMessageGenerator) - } - return generators + Messages.allCases + .filter { $0.preparedForReceived } + .map { $0.rawValue } + .map { message in + MessageView.rddm.tableGenerator(with: .build { property in + property.border(recievedMessageBorderStyle) + property.style(recievedMessageStyle) + property.alignment(.leading(UIEdgeInsets(top: 12, + left: 16, + bottom: 12, + right: UIScreen.main.bounds.width / 2))) + property.insets(UIEdgeInsets(top: 3, + left: 5, + bottom: 3, + right: 5)) + property.dataDetection(dataDetection) + property.selectable(true) + property.text(.string(message)) + }) + } } } diff --git a/Example/ReactiveDataDisplayManager/Table/FoldableTableViewController/FoldableTableViewController.swift b/Example/ReactiveDataDisplayManager/Table/FoldableTableViewController/FoldableTableViewController.swift index 7e1e289d8..cae1e720c 100644 --- a/Example/ReactiveDataDisplayManager/Table/FoldableTableViewController/FoldableTableViewController.swift +++ b/Example/ReactiveDataDisplayManager/Table/FoldableTableViewController/FoldableTableViewController.swift @@ -89,7 +89,7 @@ private extension FoldableTableViewController { let generator = FoldableTableViewCell.rddm.foldableGenerator(with: .init(title: id, isExpanded: false)) // Create and add child generators - generator.childGenerators = Constants.titleForSubcells.map { TitleTableViewCell.rddm.baseGenerator(with: $0) } + generator.children = Constants.titleForSubcells.map { TitleTableViewCell.rddm.baseGenerator(with: $0) } return generator } diff --git a/Example/ReactiveDataDisplayManager/Table/GravityTableViewController/GravityTableViewController.swift b/Example/ReactiveDataDisplayManager/Table/GravityTableViewController/GravityTableViewController.swift index dd04faac4..4f6452781 100644 --- a/Example/ReactiveDataDisplayManager/Table/GravityTableViewController/GravityTableViewController.swift +++ b/Example/ReactiveDataDisplayManager/Table/GravityTableViewController/GravityTableViewController.swift @@ -72,7 +72,7 @@ private extension GravityTableViewController { func makeGravityFoldableCellGenerator(with heaviness: Int) -> GravityFoldableCellGenerator { let generator = GravityFoldableCellGenerator(heaviness: heaviness) - generator.childGenerators = [ + generator.children = [ makeGravityCellGenerator(with: 1), makeGravityCellGenerator(with: 2) ] diff --git a/Example/ReactiveDataDisplayManager/Table/StackCellExampleViewController/StackCellExampleViewController.swift b/Example/ReactiveDataDisplayManager/Table/StackCellExampleViewController/StackCellExampleViewController.swift index 8ea2c7be6..da0c2b002 100644 --- a/Example/ReactiveDataDisplayManager/Table/StackCellExampleViewController/StackCellExampleViewController.swift +++ b/Example/ReactiveDataDisplayManager/Table/StackCellExampleViewController/StackCellExampleViewController.swift @@ -26,6 +26,7 @@ final class StackCellExampleViewController: UIViewController { private lazy var adapter = tableView.rddm.manualBuilder .add(plugin: .selectable()) + .add(plugin: .foldable()) .add(plugin: .highlightable()) .add(plugin: .accessibility()) .build() @@ -48,20 +49,81 @@ private extension StackCellExampleViewController { /// This method is used to fill adapter func fillAdapter() { - // Add section into adapter - adapter += Section(header: EmptyTableHeaderGenerator(), footer: EmptyTableFooterGenerator()) { - TableVStack { - TitleTableViewCell.buildView(with: "Текст 1") - TitleTableViewCell.buildView(with: "Текст 2") - TableHStack { - TitleTableViewCell.buildView(with: "Текст 4") - TitleTableViewCell.buildView(with: "Текст 5") + // Note that using `UITableViewCell` or `UICollectionViewCell` inside stack is not recommended, but it possible + adapter += TableSection.create(header: TitleHeaderGenerator(model: "StackView based cells"), + footer: EmptyTableFooterGenerator()) { ctx in + StackView.build(in: ctx, with: .build { vStack in + vStack.background(.solid(.rddm)) + vStack.style(.init(axis: .vertical, + spacing: 8, + alignment: .fill, + distribution: .fill)) + vStack.children { ctx in + TitleTableViewCell.build(in: ctx, with: "1") + TitleTableViewCell.build(in: ctx, with: "2") + StackView.build(in: ctx, with: .build { hStack in + hStack.background(.solid(.systemBlue)) + hStack.style(.init(axis: .horizontal, + spacing: 4, + alignment: .fill, + distribution: .fillEqually)) + hStack.children { ctx in + TitleTableViewCell.build(in: ctx, with: "4") + TitleTableViewCell.build(in: ctx, with: "5") + } + }) + TitleTableViewCell.build(in: ctx, with: "3") } - TitleTableViewCell.buildView(with: "Текст 3") - } - .didSelectEvent { - print("VerticalTableStack did select event") + }).didSelectEvent { + print("StackView did select") } + LabelView.build(in: ctx, with: .build { label in + label.textAlignment(.center) + label.text(.string("Wrapped LabelView")) + label.style(.init(color: .systemBlue, font: .preferredFont(forTextStyle: .body))) + }) + TitleTableViewCell.build(in: ctx, with: "Cell outside from stack") + .didSelectEvent { + print("Cell outside from stack did select") + } + StackView.build(in: ctx, with: .build { hStack in + hStack.background(.solid(.systemGreen)) + hStack.style(.init(axis: .horizontal, + spacing: 0, + alignment: .fill, + distribution: .fillEqually)) + hStack.children { ctx in + TitleTableViewCell.build(in: ctx, with: "6") + StackView.build(in: ctx, with: .build { vStack in + vStack.background(.solid(.systemPink)) + vStack.style(.init(axis: .vertical, + spacing: 20, + alignment: .fill, + distribution: .fillEqually)) + vStack.children { ctx in + TitleTableViewCell.build(in: ctx, with: "7") + TitleTableViewCell.build(in: ctx, with: "8") + TitleTableViewCell.build(in: ctx, with: "9") + TitleTableViewCell.build(in: ctx, with: "10") + } + }) + } + }) + FoldableTableViewCell.build(in: ctx, with: .init(title: "example", isExpanded: false)) + .asFoldable { ctx in + TitleTableViewCell.build(in: ctx, with: "first child") + .didSelectEvent { + print("first child did select") + } + TitleTableViewCell.build(in: ctx, with: "second child") + .didSelectEvent { + print("second child did select") + } + } + .animation((.bottom, .top)) + .didFoldEvent { + print("Foldable cell is \($0 ? "expanded" : "collapsed")") + } } // Tell adapter that we've changed generators diff --git a/Example/ReactiveDataDisplayManager/Table/Views/Cells/FoldableTableViewCell/FoldableTableViewCell.swift b/Example/ReactiveDataDisplayManager/Table/Views/Cells/FoldableTableViewCell/FoldableTableViewCell.swift index 6117b9d86..a400f8cc4 100644 --- a/Example/ReactiveDataDisplayManager/Table/Views/Cells/FoldableTableViewCell/FoldableTableViewCell.swift +++ b/Example/ReactiveDataDisplayManager/Table/Views/Cells/FoldableTableViewCell/FoldableTableViewCell.swift @@ -80,6 +80,14 @@ extension FoldableTableViewCell: ConfigurableItem { } +// MARK: - RegistrationTypeProvider + +extension FoldableTableViewCell: RegistrationTypeProvider { + + static var prefferedRegistration: RegistrationType { .nib } + +} + // MARK: - Configuration private extension FoldableTableViewCell { diff --git a/Example/ReactiveDataDisplayManager/Table/Views/Cells/HighlitableStackCells/StackTableCell+HighlitableItem.swift b/Example/ReactiveDataDisplayManager/Table/Views/Cells/HighlitableStackCells/StackTableCell+HighlitableItem.swift deleted file mode 100644 index 75230791b..000000000 --- a/Example/ReactiveDataDisplayManager/Table/Views/Cells/HighlitableStackCells/StackTableCell+HighlitableItem.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// StackTableCell+HighlitableItem.swift -// ReactiveDataDisplayManagerExample_iOS -// -// Created by Konstantin Porokhov on 29.06.2023. -// - -import ReactiveDataComponents -import ReactiveDataDisplayManager -import UIKit - -// internal project custom stack -final class TableVStack: TableStack, HighlightableItem { - - public init(space: CGFloat, insets: UIEdgeInsets = .zero, @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: space, insets: insets, axis: .vertical, items: items) - } - - public init(@ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: .zero, insets: .zero, axis: .vertical, items: items) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - HighlightableItem - - public func applyUnhighlightedStyle() { - UIView.animate(withDuration: 0.2) { - self.cell?.contentView.backgroundColor = .white - } - } - - public func applyHighlightedStyle() { - UIView.animate(withDuration: 0.2) { - self.cell?.contentView.backgroundColor = .lightGray.withAlphaComponent(0.3) - } - } - -} - -// internal project custom stack -final class TableHStack: HorizontalTableStack, HighlightableItem { - - public override init(space: CGFloat, insets: UIEdgeInsets = .zero, @ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: space, items: items) - } - - public override init(@ConfigurableItemBuilder items: () -> [any ConfigurableItem]) { - super.init(space: .zero, items: items) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - HighlightableItem - - public func applyUnhighlightedStyle() { - UIView.animate(withDuration: 0.2) { - self.cell?.contentView.backgroundColor = .white - } - } - - public func applyHighlightedStyle() { - UIView.animate(withDuration: 0.2) { - self.cell?.contentView.backgroundColor = .lightGray.withAlphaComponent(0.3) - } - } - -} diff --git a/Example/ReactiveDataDisplayManager/Table/Views/Cells/TitleTableViewCell/TitleTableViewCell.swift b/Example/ReactiveDataDisplayManager/Table/Views/Cells/TitleTableViewCell/TitleTableViewCell.swift index 471e87735..dd57f3b7c 100644 --- a/Example/ReactiveDataDisplayManager/Table/Views/Cells/TitleTableViewCell/TitleTableViewCell.swift +++ b/Example/ReactiveDataDisplayManager/Table/Views/Cells/TitleTableViewCell/TitleTableViewCell.swift @@ -26,6 +26,12 @@ class TitleTableViewCell: UITableViewCell, CalculatableHeightItem { return self } + override var intrinsicContentSize: CGSize { + CGSize(width: UIView.noIntrinsicMetric, + height: Self.getHeight(forWidth: UIScreen.main.bounds.width, + with: titleLabel.text ?? "")) + } + // MARK: - CalculatableHeightItem static func getHeight(forWidth width: CGFloat, with model: String) -> CGFloat { @@ -58,3 +64,11 @@ extension TitleTableViewCell: ConfigurableItem { } } + +// MARK: - RegistrationTypeProvider + +extension TitleTableViewCell: RegistrationTypeProvider { + + static var prefferedRegistration: RegistrationType { .nib } + +} diff --git a/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageCollectionViewCell/ImageCollectionViewGenerator.swift b/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageCollectionViewCell/ImageCollectionViewGenerator.swift index 0d77c772c..3d22f9432 100644 --- a/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageCollectionViewCell/ImageCollectionViewGenerator.swift +++ b/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageCollectionViewCell/ImageCollectionViewGenerator.swift @@ -7,4 +7,4 @@ import ReactiveDataDisplayManager -final class ImageCollectionViewGenerator: BaseCollectionCellGenerator, FocusableItem { } +final class ImageCollectionViewGenerator: BaseCellGenerator, FocusableItem { } diff --git a/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageDefaultBehavoirCell/ImageDefaultBehavoirCollectionViewGenerator.swift b/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageDefaultBehavoirCell/ImageDefaultBehavoirCollectionViewGenerator.swift index 4306de547..ee57576b5 100644 --- a/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageDefaultBehavoirCell/ImageDefaultBehavoirCollectionViewGenerator.swift +++ b/Example/ReactiveDataDisplayManagerExampleTv/Views/Cells/ImageDefaultBehavoirCell/ImageDefaultBehavoirCollectionViewGenerator.swift @@ -7,4 +7,4 @@ import ReactiveDataDisplayManager -final class ImageDefaultBehavoirCollectionViewGenerator: BaseCollectionCellGenerator, FocusableItem { } +final class ImageDefaultBehavoirCollectionViewGenerator: BaseCellGenerator, FocusableItem { } diff --git a/Makefile b/Makefile index 450c05cd6..5d60fd3d6 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ test_lib_iOS: ## Count failures in xcodebuild.log and `exit 0` if no failures or `exit 1` otherwise check_test_log: echo "Test results: xcodebuild.log" - $(eval failures_count := $(shell cat xcodebuild.log | grep -Eo "[0-9]+ failures" | grep -Eo "[0-9]+")) + $(eval failures_count := $(shell cat xcodebuild.log | grep -Eo "[0-9]+ (failure|failures)" | grep -Eo "[0-9]+")) echo "Failures count: $(failures_count)" @if [ $(failures_count) == "0" ]; then\ echo "Tests passed";\ diff --git a/ReactiveDataDisplayManagerTests/Builder/Collection/Mocks/PrefetchableCollectionCellGeneratorMock.swift b/ReactiveDataDisplayManagerTests/Builder/Collection/Mocks/PrefetchableCollectionCellGeneratorMock.swift index c35248229..1f4dc072c 100644 --- a/ReactiveDataDisplayManagerTests/Builder/Collection/Mocks/PrefetchableCollectionCellGeneratorMock.swift +++ b/ReactiveDataDisplayManagerTests/Builder/Collection/Mocks/PrefetchableCollectionCellGeneratorMock.swift @@ -9,13 +9,13 @@ import UIKit @testable import ReactiveDataDisplayManager -final class PrefetchableCollectionCellGeneratorMock: BaseCollectionCellGenerator, PrefetcherableItem { +final class PrefetchableCollectionCellGeneratorMock: BaseCellGenerator, PrefetcherableItem { // MARK: - PrefetcherableFlow var requestId: Int? - // MARK: - BaseCollectionCellGenerator + // MARK: - BaseCellGenerator init(with model: Bool, requestId: Int) { self.requestId = requestId diff --git a/ReactiveDataDisplayManagerTests/Collection/Mock/StubCollectionCellGenerator.swift b/ReactiveDataDisplayManagerTests/Collection/Mock/StubCollectionCellGenerator.swift index b16aa53ea..72178299d 100644 --- a/ReactiveDataDisplayManagerTests/Collection/Mock/StubCollectionCellGenerator.swift +++ b/ReactiveDataDisplayManagerTests/Collection/Mock/StubCollectionCellGenerator.swift @@ -9,7 +9,7 @@ import UIKit @testable import ReactiveDataDisplayManager -class StubCollectionCellGenerator: BaseCollectionCellGenerator { +class StubCollectionCellGenerator: BaseCellGenerator { init(model: String) { super.init(with: model, registerType: .class) diff --git a/ReactiveDataDisplayManagerTests/Mocks/AutoMockable.generated.swift b/ReactiveDataDisplayManagerTests/Mocks/AutoMockable.generated.swift index 2d56a85ae..494e9ade9 100644 --- a/ReactiveDataDisplayManagerTests/Mocks/AutoMockable.generated.swift +++ b/ReactiveDataDisplayManagerTests/Mocks/AutoMockable.generated.swift @@ -91,40 +91,7 @@ class CollectionCellGeneratorMock: CollectionCellGenerator { } } -class CollectionFoldableItemMock: CollectionFoldableItem { - var didFoldEvent: Event { - get { return underlyingDidFoldEvent } - set(value) { underlyingDidFoldEvent = value } - } - var underlyingDidFoldEvent: Event! - var isExpanded: Bool { - get { return underlyingIsExpanded } - set(value) { underlyingIsExpanded = value } - } - var underlyingIsExpanded: Bool! - var childGenerators: [CollectionCellGenerator] = [] - var labelStrategy: AccessibilityStringStrategy { - get { return underlyingLabelStrategy } - set(value) { underlyingLabelStrategy = value } - } - var underlyingLabelStrategy: AccessibilityStringStrategy! - var valueStrategy: AccessibilityStringStrategy { - get { return underlyingValueStrategy } - set(value) { underlyingValueStrategy = value } - } - var underlyingValueStrategy: AccessibilityStringStrategy! - var traitsStrategy: AccessibilityTraitsStrategy { - get { return underlyingTraitsStrategy } - set(value) { underlyingTraitsStrategy = value } - } - var underlyingTraitsStrategy: AccessibilityTraitsStrategy! - var isAccessibilityIgnored: Bool { - get { return underlyingIsAccessibilityIgnored } - set(value) { underlyingIsAccessibilityIgnored = value } - } - var underlyingIsAccessibilityIgnored: Bool! -} class CollectionFooterGeneratorMock: CollectionFooterGenerator { var identifier: UICollectionReusableView.Type { get { return underlyingIdentifier } diff --git a/ReactiveDataDisplayManagerTests/Stack/Manager/BaseStackManagerTests.swift b/ReactiveDataDisplayManagerTests/Stack/Manager/BaseStackManagerTests.swift index e20843926..50bf86489 100644 --- a/ReactiveDataDisplayManagerTests/Stack/Manager/BaseStackManagerTests.swift +++ b/ReactiveDataDisplayManagerTests/Stack/Manager/BaseStackManagerTests.swift @@ -164,7 +164,7 @@ final class BaseStackManagerTests: XCTestCase { // MARK: - Mocks -fileprivate final class MockStackCellGenerator: StackCellGenerator { +fileprivate final class MockStackCellGenerator: ViewGenerator { var title: String var view: UIView? diff --git a/ReactiveDataDisplayManagerTests/Table/Manager/GravityFoldingHeaderGenerator.swift b/ReactiveDataDisplayManagerTests/Table/Manager/GravityFoldingHeaderGenerator.swift index f6a48ee84..784ad4595 100644 --- a/ReactiveDataDisplayManagerTests/Table/Manager/GravityFoldingHeaderGenerator.swift +++ b/ReactiveDataDisplayManagerTests/Table/Manager/GravityFoldingHeaderGenerator.swift @@ -16,7 +16,7 @@ final class GravityFoldingHeaderGenerator: GravityTableCellGenerator, FoldableIt var didFoldEvent = Event() var isExpanded = true - var childGenerators = [TableCellGenerator]() + var children = [TableCellGenerator]() // MARK: - GravityTableCellGenerator @@ -93,7 +93,7 @@ final class GravityFoldingTableDataDisplayManagerTests: XCTestCase { let childGenerator3 = GravityCellGenerator() let header = GravityFoldingHeaderGenerator() - header.childGenerators = [childGenerator1, childGenerator2, childGenerator3] + header.children = [childGenerator1, childGenerator2, childGenerator3] header.isExpanded = true ddm.addCellGenerators([header, childGenerator1, childGenerator2, childGenerator3]) @@ -116,7 +116,7 @@ final class GravityFoldingTableDataDisplayManagerTests: XCTestCase { let childGenerator3 = GravityCellGenerator() let header = GravityFoldingHeaderGenerator() - header.childGenerators = [childGenerator1, childGenerator2, childGenerator3] + header.children = [childGenerator1, childGenerator2, childGenerator3] header.isExpanded = false ddm.addCellGenerator(header) diff --git a/Source/Collection/CollectionCell+RDDM.swift b/Source/Collection/CollectionCell+RDDM.swift index dc0c74296..9bc553893 100644 --- a/Source/Collection/CollectionCell+RDDM.swift +++ b/Source/Collection/CollectionCell+RDDM.swift @@ -10,7 +10,7 @@ import UIKit public extension StaticDataDisplayWrapper where Base: UICollectionViewCell & ConfigurableItem { - func baseGenerator(with model: Base.Model, and registerType: CellRegisterType = .nib) -> BaseCollectionCellGenerator { + func baseGenerator(with model: Base.Model, and registerType: RegistrationType = .nib) -> BaseCellGenerator { .init(with: model, registerType: registerType) } @@ -19,7 +19,7 @@ public extension StaticDataDisplayWrapper where Base: UICollectionViewCell & Con public extension StaticDataDisplayWrapper where Base: UICollectionViewCell & CalculatableHeightItem { func calculatableHeightGenerator(with model: Base.Model, - and registerType: CellRegisterType = .nib, + and registerType: RegistrationType = .nib, referencedWidth: CGFloat) -> CalculatableHeightCollectionCellGenerator { .init(with: model, width: referencedWidth, registerType: registerType) } @@ -28,7 +28,7 @@ public extension StaticDataDisplayWrapper where Base: UICollectionViewCell & Cal public extension StaticDataDisplayWrapper where Base: UICollectionViewCell & ConfigurableItem & FoldableStateHolder { - func foldableGenerator(with model: Base.Model, and registerType: CellRegisterType = .nib) -> FoldableCollectionCellGenerator { + func foldableGenerator(with model: Base.Model, and registerType: RegistrationType = .nib) -> FoldableCellGenerator { .init(with: model, registerType: registerType) } diff --git a/Source/Collection/CollectionSection.swift b/Source/Collection/CollectionSection.swift index 3cce41ff3..e4a13d8a6 100644 --- a/Source/Collection/CollectionSection.swift +++ b/Source/Collection/CollectionSection.swift @@ -21,4 +21,10 @@ public extension CollectionSection { self.init(generators: generators, header: header, footer: EmptyCollectionFooterGenerator()) } + static func create(header: HeaderGeneratorType, + footer: FooterGeneratorType, + @GeneratorsBuilder generators: CollectionContext.CellsBuilder) -> Self { + Self(generators: generators(CollectionContext.self), header: header, footer: footer) + } + } diff --git a/Source/Collection/Generators/BaseCollectionCellGenerator.swift b/Source/Collection/Generators/BaseCollectionCellGenerator.swift deleted file mode 100644 index feb4cdd8c..000000000 --- a/Source/Collection/Generators/BaseCollectionCellGenerator.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// BaseCollectionCellGenerator.swift -// ReactiveDataDisplayManager -// -// Created by Maxim MAMEDOV on 28/03/2019. -// Copyright © 2019 Александр Кравченков. All rights reserved. -// - -import UIKit - -open class BaseCollectionCellGenerator: SelectableItem where Cell: UICollectionViewCell { - - // MARK: - Public Properties - - public var isNeedDeselect = true - public var didSelectEvent = EmptyEvent() - public var didDeselectEvent = EmptyEvent() - public let model: Cell.Model - - // MARK: - Private Properties - - private let registerType: CellRegisterType - - // MARK: - Initialization - - public init(with model: Cell.Model, - registerType: CellRegisterType = .nib) { - self.model = model - self.registerType = registerType - } - - // MARK: - Open methods - - open func configure(cell: Cell, with model: Cell.Model) { - cell.configure(with: model) - } - -} - -// MARK: - CollectionCellGenerator - -extension BaseCollectionCellGenerator: CollectionCellGenerator { - - public var identifier: String { - return String(describing: Cell.self) - } - - public func generate(collectionView: UICollectionView, for indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? Cell else { - return UICollectionViewCell() - } - configure(cell: cell, with: model) - return cell - } - - public func registerCell(in collectionView: UICollectionView) { - switch registerType { - case .nib: - collectionView.registerNib(identifier, bundle: Cell.bundle()) - case .class: - collectionView.register(Cell.self, forCellWithReuseIdentifier: identifier) - } - } - -} diff --git a/Source/Collection/Generators/CalculatableHeightCollectionCellGenerator.swift b/Source/Collection/Generators/CalculatableHeightCollectionCellGenerator.swift index a6a5dfa07..ccf8f55d2 100644 --- a/Source/Collection/Generators/CalculatableHeightCollectionCellGenerator.swift +++ b/Source/Collection/Generators/CalculatableHeightCollectionCellGenerator.swift @@ -9,7 +9,7 @@ import UIKit // swiftlint:disable line_length -public class CalculatableHeightCollectionCellGenerator: BaseCollectionCellGenerator & SizableItem where Cell: UICollectionViewCell { +public class CalculatableHeightCollectionCellGenerator: BaseCellGenerator & SizableItem where Cell: UICollectionViewCell { // swiftlint:enable line_length // MARK: - Private Properties @@ -20,7 +20,7 @@ public class CalculatableHeightCollectionCellGenerator: BaseCollectionCellGenerator & SizableItem where Cell: UICollectionViewCell { +public class CalculatableWidthCollectionCellGenerator: BaseCellGenerator & SizableItem where Cell: UICollectionViewCell { // swiftlint:enable line_length // MARK: - Private Properties @@ -20,7 +20,7 @@ public class CalculatableWidthCollectionCellGenerator [CollectionCellGenerator] + + /// This method is used to build generator for some `UIView` to place it in `UICollectionView` + /// - Parameters: + /// - type: may be `SomeView.self` + /// - model: configurable model for generic `SomeView` + public static func gen(_ type: Item.Type, model: Item.Model) -> BaseCellGenerator> where Item: BaseItem { + Item.rddm.collectionGenerator(with: model) + } + + /// This method is used to build generator for some `UICollectionViewCell` to place it in `UICollectionView` + /// - Parameters: + /// - type: may be `SomeCell.self` + /// - model: configurable model for generic `SomeCell` + public static func gen(_ type: Item.Type, model: Item.Model) -> BaseCellGenerator where Item: ViewType & BaseItem { + Item.rddm.baseGenerator(with: model, and: Item.prefferedRegistration) + } + +} diff --git a/Source/Collection/Generators/DiffableCollectionCellGenerator.swift b/Source/Collection/Generators/DiffableCollectionCellGenerator.swift deleted file mode 100644 index 473d3f837..000000000 --- a/Source/Collection/Generators/DiffableCollectionCellGenerator.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// DiffableCollectionCellGenerator.swift -// ReactiveDataDisplayManager -// -// Created by korshunov on 25.02.2022. -// - -import UIKit - -open class DiffableCollectionCellGenerator: BaseCollectionCellGenerator, - IdOwner, - DiffableItemSource where Cell.Model: Equatable { - public let id: AnyHashable - - public var diffableItem: DiffableItem { - .init(id: id, state: AnyEquatable(model)) - } - - public init(uniqueId: AnyHashable, with model: Cell.Model, registerType: CellRegisterType = .nib) { - self.id = uniqueId - super.init(with: model, registerType: registerType) - } - -} - -extension StaticDataDisplayWrapper where Base: UICollectionViewCell & ConfigurableItem, Base.Model: Equatable { - - public func diffableGenerator( - uniqueId: AnyHashable, - with model: Base.Model, - and registerType: CellRegisterType = .nib - ) -> DiffableCollectionCellGenerator { - .init(uniqueId: uniqueId, with: model, registerType: registerType) - } - -} - -extension StaticDataDisplayWrapper where Base: UICollectionViewCell & ConfigurableItem, Base.Model: Equatable & IdOwner { - - public func diffableGenerator( - with model: Base.Model, - and registerType: CellRegisterType = .nib - ) -> DiffableCollectionCellGenerator { - .init(uniqueId: model.id, with: model, registerType: registerType) - } - -} diff --git a/Source/Collection/Generators/FoldableCollectionCellGenerator.swift b/Source/Collection/Generators/FoldableCollectionCellGenerator.swift deleted file mode 100644 index 9a517faba..000000000 --- a/Source/Collection/Generators/FoldableCollectionCellGenerator.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// FoldableCollectionCellGenerator.swift -// ReactiveDataDisplayManager -// -// Created by korshunov on 23.06.2023. -// - -import UIKit - -// swiftlint:disable line_length -/// A generator that can insert and remove child generators by tap -open class FoldableCollectionCellGenerator: BaseCollectionCellGenerator, CollectionFoldableItem { -// swiftlint:enable line_length - - // MARK: - FoldableItem - - /// Event on changed `isExpanded` state - open var didFoldEvent = Event() - - /// Folded/unfolded state - open var isExpanded = false - - /// Child generators which can be folded/unfolded - open var childGenerators: [CollectionCellGenerator] = [] - - // MARK: - BaseCollectionCellGenerator - - open override func configure(cell: Cell, with model: Cell.Model) { - super.configure(cell: cell, with: model) - - cell.setExpanded(isExpanded) - - didFoldEvent.addListner(with: "rddm.foldable-on-dequeue") { [weak cell] isExpanded in - cell?.setExpanded(isExpanded) - } - } - -} diff --git a/Source/Collection/Plugins/CollectionPluginsChecker.swift b/Source/Collection/Plugins/CollectionPluginsChecker.swift index eb4e65f9f..59ef8207c 100644 --- a/Source/Collection/Plugins/CollectionPluginsChecker.swift +++ b/Source/Collection/Plugins/CollectionPluginsChecker.swift @@ -31,7 +31,7 @@ final class CollectionPluginsChecker { func checkPlugins() { sections.flatMap(\.generators).forEach { checkPlugin(for: $0 as? SelectableItem) - checkPlugin(for: $0 as? CollectionFoldableItem, pluginName: CollectionFoldablePlugin.pluginName) + checkPlugin(for: $0 as? FoldableItem, pluginName: CollectionFoldablePlugin.pluginName) } } diff --git a/Source/Collection/Plugins/PluginAction/CollectionFoldablePlugin.swift b/Source/Collection/Plugins/PluginAction/CollectionFoldablePlugin.swift index 575fdd728..0b3ae3890 100644 --- a/Source/Collection/Plugins/PluginAction/CollectionFoldablePlugin.swift +++ b/Source/Collection/Plugins/PluginAction/CollectionFoldablePlugin.swift @@ -5,23 +5,29 @@ // Created by Vadim Tikhonov on 11.02.2021. // -/// Plugin to support `CollectionFoldableItem` +/// Plugin to support `FoldableItem` in `UICollectionView` /// /// Allow expand or collapse child cells public class CollectionFoldablePlugin: BaseCollectionPlugin { + // MARK: - Nested types + + public typealias GeneratorType = FoldableItem & CollectionChildrenHolder & FoldableStateToggling + + // MARK: - Plugin body + public override func process(event: CollectionEvent, with manager: BaseCollectionManager?) { switch event { case .didSelect(let indexPath): guard let generator = manager?.sections[indexPath.section].generators[indexPath.row], - let foldable = generator as? CollectionFoldableItem + let foldable = generator as? GeneratorType else { return } - let visibleGenerators = foldable.childGenerators + let visibleGenerators = foldable.children .map { getVisibleGenerators(for: $0) } .reduce([], +) @@ -35,7 +41,7 @@ public class CollectionFoldablePlugin: BaseCollectionPlugin { } } - foldable.isExpanded = !foldable.isExpanded + foldable.toggleEpanded() foldable.didFoldEvent.invoke(with: (foldable.isExpanded)) default: break @@ -49,8 +55,8 @@ public class CollectionFoldablePlugin: BaseCollectionPlugin { private extension CollectionFoldablePlugin { func getVisibleGenerators(for generator: CollectionCellGenerator) -> [CollectionCellGenerator] { - if let foldableItem = generator as? CollectionFoldableItem, foldableItem.isExpanded { - return foldableItem.childGenerators + if let foldableItem = generator as? GeneratorType, foldableItem.isExpanded { + return foldableItem.children .map { getVisibleGenerators(for: $0) } .reduce([generator], +) } else { @@ -64,7 +70,7 @@ private extension CollectionFoldablePlugin { public extension BaseCollectionPlugin { - /// Plugin to support `CollectionFoldableItem` + /// Plugin to support `FoldableItem` in `UICollectionView` /// /// Allow expand or collapse child cells static func foldable() -> BaseCollectionPlugin { diff --git a/Source/Collection/Protocols/CollectionChildrenHolder.swift b/Source/Collection/Protocols/CollectionChildrenHolder.swift new file mode 100644 index 000000000..508faf991 --- /dev/null +++ b/Source/Collection/Protocols/CollectionChildrenHolder.swift @@ -0,0 +1,21 @@ +// +// CollectionChildrenHolder.swift +// ReactiveDataDisplayManager +// +// Created by Vadim Tikhonov on 11.02.2021. +// Copyright © 2021 Александр Кравченков. All rights reserved. +// + +import UIKit + +// sourcery: AutoMockable +public protocol CollectionChildrenHolder: AccessibilityStrategyProvider { + var children: [CollectionCellGenerator] { get set } +} + +// MARK: - Defaults + +public extension CollectionChildrenHolder { + var labelStrategy: AccessibilityStringStrategy { .ignored } + var traitsStrategy: AccessibilityTraitsStrategy { children.isEmpty ? .ignored : .just(.button) } +} diff --git a/Source/Collection/Protocols/CollectionFoldableItem.swift b/Source/Collection/Protocols/CollectionFoldableItem.swift deleted file mode 100644 index 25f18fb0f..000000000 --- a/Source/Collection/Protocols/CollectionFoldableItem.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// CollectionFoldableItem.swift -// ReactiveDataDisplayManager -// -// Created by Vadim Tikhonov on 11.02.2021. -// Copyright © 2021 Александр Кравченков. All rights reserved. -// - -import UIKit - -// sourcery: AutoMockable -public protocol CollectionFoldableItem: AnyObject, AccessibilityStrategyProvider { - var didFoldEvent: Event { get } - var isExpanded: Bool { get set } - var childGenerators: [CollectionCellGenerator] { get set } -} - -public extension CollectionFoldableItem { - var labelStrategy: AccessibilityStringStrategy { .ignored } - var traitsStrategy: AccessibilityTraitsStrategy { childGenerators.isEmpty ? .ignored : .just(.button) } -} diff --git a/Components/Sources/Common/Extensions/CollectionWrappedCell+RDDM.swift b/Source/Collection/Wrapper/CollectionWrappedCell+RDDM.swift similarity index 71% rename from Components/Sources/Common/Extensions/CollectionWrappedCell+RDDM.swift rename to Source/Collection/Wrapper/CollectionWrappedCell+RDDM.swift index e4d756887..7222701d1 100644 --- a/Components/Sources/Common/Extensions/CollectionWrappedCell+RDDM.swift +++ b/Source/Collection/Wrapper/CollectionWrappedCell+RDDM.swift @@ -5,27 +5,25 @@ // Created by Никита Коробейников on 05.06.2023. // -import ReactiveDataDisplayManager import UIKit -public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem { +public extension StaticDataDisplayWrapper where Base: UIView & BaseItem { - func collectionGenerator(with model: Base.Model, - and registerType: CellRegisterType = .nib) -> BaseCollectionCellGenerator> { + func collectionGenerator(with model: Base.Model) -> BaseCellGenerator> { CollectionWrappedCell.rddm.baseGenerator(with: model, - and: registerType) + and: .class) } } // MARK: - CalculatableHeightItem -public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem & CalculatableHeightItem { +public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem & CalculatableHeightItem & RegistrationTypeProvider { func tableCalculatableHightGenerator( with model: Base.Model, width: CGFloat, - and registerType: CellRegisterType = .nib + and registerType: RegistrationType = .nib ) -> CalculatableHeightCollectionCellGenerator> { CalculatableHeightCollectionCellGenerator>(with: model, @@ -37,12 +35,12 @@ public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem // MARK: - CalculatableWidthItem -public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem & CalculatableWidthItem { +public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem & CalculatableWidthItem & RegistrationTypeProvider { func tableCalculatableWidthGenerator( with model: Base.Model, height: CGFloat, - and registerType: CellRegisterType = .nib + and registerType: RegistrationType = .nib ) -> CalculatableWidthCollectionCellGenerator> { CalculatableWidthCollectionCellGenerator>(with: model, diff --git a/Components/Sources/Collection/CollectionWrappedCell.swift b/Source/Collection/Wrapper/CollectionWrappedCell.swift similarity index 72% rename from Components/Sources/Collection/CollectionWrappedCell.swift rename to Source/Collection/Wrapper/CollectionWrappedCell.swift index 2d56ceeec..c80b34404 100644 --- a/Components/Sources/Collection/CollectionWrappedCell.swift +++ b/Source/Collection/Wrapper/CollectionWrappedCell.swift @@ -6,16 +6,15 @@ // import UIKit -import ReactiveDataDisplayManager /// Empty collection cell with `View`. Configuration is implemented within `ViewWrapper`. -public final class CollectionWrappedCell: UICollectionViewCell, ViewWrapper { +public final class CollectionWrappedCell: UICollectionViewCell, ViewWrapper { public typealias Model = View.Model // MARK: - Properties - public let nestedView: View = .init(frame: .zero) + public let nestedView: View = EmptyViewGenerator().generate() public var cachedInsets: UIEdgeInsets? @@ -58,3 +57,25 @@ extension CollectionWrappedCell: CalculatableWidthItem where View: CalculatableW } } + +// MARK: - HighlightableItem + +extension CollectionWrappedCell: HighlightableItem where View: HighlightableItem { + + public func applyUnhighlightedStyle() { + nestedView.applyUnhighlightedStyle() + } + + public func applyHighlightedStyle() { + nestedView.applyHighlightedStyle() + } + + public func applySelectedStyle() { + nestedView.applySelectedStyle() + } + + public func applyDeselectedStyle() { + nestedView.applyDeselectedStyle() + } + +} diff --git a/Source/Common/Generators/BaseCellGenerator.swift b/Source/Common/Generators/BaseCellGenerator.swift new file mode 100644 index 000000000..e320435cf --- /dev/null +++ b/Source/Common/Generators/BaseCellGenerator.swift @@ -0,0 +1,178 @@ +// +// BaseCellGenerator.swift +// ReactiveDataDisplayManager +// +// Created by Mikhail Monakov on 15/01/2019. +// Copyright © 2020 Александр Кравченков. All rights reserved. +// + +import UIKit + +/// Class for generating reusable Configurable UITableViewCell +open class BaseCellGenerator: SelectableItem, RegisterableItem { + + // MARK: - Public properties + + public var isNeedDeselect = true + public var didSelectEvent = EmptyEvent() + public var didDeselectEvent = EmptyEvent() + public let model: Cell.Model + + // MARK: - Private Properties + + private let registerType: RegistrationType + + // MARK: - Initialization + + public init(with model: Cell.Model, + registerType: RegistrationType = .nib) { + self.model = model + self.registerType = registerType + } + + // MARK: - Open methods + + open func configure(cell: Cell, with model: Cell.Model) { + cell.configure(with: model) + } + + // MARK: - TableCellGenerator + + public var identifier: String { + return String(describing: Cell.self) + } + + // TODO: - add protocol to move it into extension + + open var cellHeight: CGFloat { + UITableView.automaticDimension + } + + open var estimatedCellHeight: CGFloat? { + nil + } + +} + +// MARK: - CollectionCell + +extension BaseCellGenerator: CollectionCellRegisterableItem where Cell: UICollectionViewCell { + + public func registerCell(in collectionView: UICollectionView) { + switch registerType { + case .nib: + collectionView.registerNib(identifier, bundle: Cell.bundle()) + case .class: + collectionView.register(Cell.self, forCellWithReuseIdentifier: identifier) + } + } + +} + +extension BaseCellGenerator: CollectionCellGenerator where Cell: UICollectionViewCell { + + public func generate(collectionView: UICollectionView, for indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? Cell else { + return UICollectionViewCell() + } + configure(cell: cell, with: model) + return cell + } + +} + +// MARK: - TableCell + +extension BaseCellGenerator: TableCellRegisterableItem where Cell: UITableViewCell { + + public func registerCell(in tableView: UITableView) { + switch registerType { + case .nib: + tableView.registerNib(identifier, bundle: Cell.bundle()) + case .class: + tableView.register(Cell.self, forCellReuseIdentifier: identifier) + } + } + +} + +extension BaseCellGenerator: TableCellGenerator where Cell: UITableViewCell { + + public func generate(tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? Cell else { + return UITableViewCell() + } + configure(cell: cell, with: model) + return cell + } + +} + +// MARK: - Decorations + +public extension BaseCellGenerator { + + /// - Parameter isNeedDeselect: defines if cell should be deselected after selection + func isNeedDeselect(_ isNeedDeselect: Bool) -> Self { + self.isNeedDeselect = isNeedDeselect + return self + } + + /// - Parameter closure: closure that will be called when cell is selected + func didSelectEvent(_ closure: @escaping () -> Void) -> Self { + self.didSelectEvent.addListner(closure) + return self + } + + /// - Parameter closure: closure that will be called when cell is deselected in multiple selection mode + func didDeselectEvent(_ closure: @escaping () -> Void) -> Self { + self.didDeselectEvent.addListner(closure) + return self + } + +} + +// MARK: - Transformations + +public extension BaseCellGenerator where Cell: FoldableStateHolder, Cell: UITableViewCell { + + /// Creates FoldableCellGenerator with predefined children + /// - Parameter children: array of children + func asFoldable(_ children: [TableCellGenerator]) -> FoldableCellGenerator { + return .init(with: model, + registerType: registerType) + .children(children) + } + + /// Creates FoldableCellGenerator with children generated by resultBuilder + /// - Parameter content: resultBuilder based closure that returns array of children + func asFoldable(@GeneratorsBuilder_ content: @escaping TableContext.CellsBuilder) -> FoldableCellGenerator { + return .init(with: model, + registerType: registerType) + .children(content) + } + +} + +public extension BaseCellGenerator where Cell.Model: Equatable, Cell: UITableViewCell { + + /// Creates DiffableCellGenerator with constant but unique Id + /// - Parameter uniqueId: uniqueId to identify cell + func asDiffable(uniqueId: AnyHashable) -> DiffableCellGenerator { + .init(uniqueId: uniqueId, + with: model, + registerType: registerType) + } + +} + +public extension BaseCellGenerator where Cell.Model: Equatable & IdOwner, Cell: UITableViewCell { + + /// Creates DiffableCellGenerator with uniqueId from model + func asDiffable() -> DiffableCellGenerator { + .init(uniqueId: model.id, + with: model, + registerType: registerType) + } + +} diff --git a/Source/Table/Generators/DiffableCellGenerator.swift b/Source/Common/Generators/DiffableCellGenerator.swift similarity index 69% rename from Source/Table/Generators/DiffableCellGenerator.swift rename to Source/Common/Generators/DiffableCellGenerator.swift index 87ea9fd32..dd9c0b06e 100644 --- a/Source/Table/Generators/DiffableCellGenerator.swift +++ b/Source/Common/Generators/DiffableCellGenerator.swift @@ -7,7 +7,7 @@ import UIKit -open class DiffableCellGenerator: BaseCellGenerator, +open class DiffableCellGenerator: BaseCellGenerator, IdOwner, DiffableItemSource where Cell.Model: Equatable { public let id: AnyHashable @@ -16,30 +16,30 @@ open class DiffableCellGenerator: Base .init(id: id, state: AnyEquatable(model)) } - public init(uniqueId: AnyHashable, with model: Cell.Model, registerType: CellRegisterType = .nib) { + public init(uniqueId: AnyHashable, with model: Cell.Model, registerType: RegistrationType = .nib) { self.id = uniqueId super.init(with: model, registerType: registerType) } } -extension StaticDataDisplayWrapper where Base: UITableViewCell & ConfigurableItem, Base.Model: Equatable { +extension StaticDataDisplayWrapper where Base: ConfigurableItem, Base.Model: Equatable { public func diffableGenerator( uniqueId: AnyHashable, with model: Base.Model, - and registerType: CellRegisterType = .nib + and registerType: RegistrationType = .nib ) -> DiffableCellGenerator { .init(uniqueId: uniqueId, with: model, registerType: registerType) } } -extension StaticDataDisplayWrapper where Base: UITableViewCell & ConfigurableItem, Base.Model: Equatable & IdOwner { +extension StaticDataDisplayWrapper where Base: ConfigurableItem, Base.Model: Equatable & IdOwner { public func diffableGenerator( with model: Base.Model, - and registerType: CellRegisterType = .nib + and registerType: RegistrationType = .nib ) -> DiffableCellGenerator { .init(uniqueId: model.id, with: model, registerType: registerType) } diff --git a/Source/Common/Generators/FoldableCellGenerator.swift b/Source/Common/Generators/FoldableCellGenerator.swift new file mode 100644 index 000000000..c33e35029 --- /dev/null +++ b/Source/Common/Generators/FoldableCellGenerator.swift @@ -0,0 +1,141 @@ +// +// FoldableCellGenerator.swift +// ReactiveDataDisplayManager +// +// Created by korshunov on 23.06.2023. +// + +import UIKit + +/// A generator that can insert and remove child generators by tap +open class FoldableCellGenerator: BaseCellGenerator, FoldableItem, FoldableStateToggling { + + // MARK: - Private + + private var tableAnimation: TableChildrenAnimationGroup = (.left, .top) + private var tableChildren: [TableCellGenerator] = [] + + private var collectionChildren: [CollectionCellGenerator] = [] + + // MARK: - Public + + /// Event on changed `isExpanded` state + public var didFoldEvent = Event() + + /// Folded/unfolded state + public var isExpanded = false + + // MARK: - BaseCellGenerator + + public override func configure(cell: Cell, with model: Cell.Model) { + super.configure(cell: cell, with: model) + + cell.setExpanded(isExpanded) + + didFoldEvent.addListner(with: "rddm.foldable-on-dequeue") { [weak cell] isExpanded in + cell?.setExpanded(isExpanded) + } + } + + // MARK: - FoldableStateToggling + + public func toggleEpanded() { + isExpanded.toggle() + } + +} + +// MARK: - TableChildrenHolder + +extension FoldableCellGenerator: TableChildrenHolder where Cell: UITableViewCell { + + public var children: [TableCellGenerator] { + get { + tableChildren + } + set { + tableChildren = newValue + } + } + + public var animation: TableChildrenAnimationGroup { + get { + tableAnimation + } + set { + tableAnimation = newValue + } + } + +} + +// MARK: - CollectionChildrenHolder + +extension FoldableCellGenerator: CollectionChildrenHolder where Cell: UICollectionViewCell { + + public var children: [CollectionCellGenerator] { + get { + collectionChildren + } + set { + collectionChildren = newValue + } + } + +} + +// MARK: - Decorations + +public extension FoldableCellGenerator { + + /// - Parameter isExpanded: folded/unfolded state + func isExpanded(_ isExpanded: Bool) -> Self { + self.isExpanded = isExpanded + return self + } + + /// - Parameter closure: handler closure for folded/unfolded events + func didFoldEvent(_ closure: @escaping (Bool) -> Void) -> Self { + self.didFoldEvent.addListner(closure) + return self + } + +} + +public extension FoldableCellGenerator where Cell: UITableViewCell { + + /// - Parameter animation: animations for cells insertion and deletion + func animation(_ animation: TableChildrenAnimationGroup) -> Self { + self.animation = animation + return self + } + + /// - Parameter children: array of child generators + func children(_ children: [TableCellGenerator]) -> Self { + self.children = children + return self + } + + /// - Parameter content: resultBuilder based closure that returns an array of child generators + func children(@GeneratorsBuilder_ content: @escaping TableContext.CellsBuilder) -> Self { + self.children = content(TableContext.self) + return self + } + +} + +public extension FoldableCellGenerator where Cell: UICollectionViewCell { + + /// - Parameter children: array of child generators + func children(_ children: [CollectionCellGenerator]) -> Self { + self.children = children + return self + } + + /// - Parameter content: resultBuilder based closure that returns an array of child generators + func children(@GeneratorsBuilder_ content: @escaping CollectionContext.CellsBuilder) -> Self { + self.children = content(CollectionContext.self) + return self + } + +} diff --git a/Components/Sources/Common/Utils/UIView+Constraints.swift b/Source/Common/Wrapper/UIView+Constraints.swift similarity index 100% rename from Components/Sources/Common/Utils/UIView+Constraints.swift rename to Source/Common/Wrapper/UIView+Constraints.swift diff --git a/Components/Sources/Common/Utils/ViewWrapper.swift b/Source/Common/Wrapper/ViewWrapper.swift similarity index 97% rename from Components/Sources/Common/Utils/ViewWrapper.swift rename to Source/Common/Wrapper/ViewWrapper.swift index fc453c89c..e8935e353 100644 --- a/Components/Sources/Common/Utils/ViewWrapper.swift +++ b/Source/Common/Wrapper/ViewWrapper.swift @@ -6,7 +6,6 @@ // import UIKit -import ReactiveDataDisplayManager public protocol ViewWrapper: ConfigurableItem { diff --git a/Components/Sources/Common/Models/Alignment.swift b/Source/Models/Alignment.swift similarity index 100% rename from Components/Sources/Common/Models/Alignment.swift rename to Source/Models/Alignment.swift diff --git a/Source/Models/CellRegisterType.swift b/Source/Models/RegistrationType.swift similarity index 92% rename from Source/Models/CellRegisterType.swift rename to Source/Models/RegistrationType.swift index d64703b51..49e757ad6 100644 --- a/Source/Models/CellRegisterType.swift +++ b/Source/Models/RegistrationType.swift @@ -11,7 +11,7 @@ import UIKit /// Enum with different types of registering cells in collections /// - nib - register as nib /// - class - register as class -public enum CellRegisterType { +public enum RegistrationType { case nib case `class` } diff --git a/Source/Models/Section.swift b/Source/Models/Section.swift index b85010fcc..d9cfb7226 100644 --- a/Source/Models/Section.swift +++ b/Source/Models/Section.swift @@ -39,6 +39,14 @@ public struct Section { self.init(generators: generators(), header: header, footer: footer) } + /// Context based generic section for tables and collections with @resultBuilder support + static func create(contextType: Context.Type, + header: HeaderGeneratorType, + footer: FooterGeneratorType, + @GeneratorsBuilder generators: (Context.Type) -> [GeneratorType]) -> Self { + Self(generators: generators(Context.self), header: header, footer: footer) + } + } public extension Section { diff --git a/Source/Protocols/BuilderContext/BuilderContext.swift b/Source/Protocols/BuilderContext/BuilderContext.swift new file mode 100644 index 000000000..5d98517cf --- /dev/null +++ b/Source/Protocols/BuilderContext/BuilderContext.swift @@ -0,0 +1,14 @@ +// +// BuilderContext.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 26.07.2023. +// + +import UIKit + +/// Context which is used to build `UIView` in some container view which can be on of: `UIStackView`, `UICollectionView`, `UITableView` +public protocol BuilderContext { + /// Base type of child view + associatedtype ViewType: UIView +} diff --git a/Source/Protocols/BuilderContext/RegistrationTypeProvider.swift b/Source/Protocols/BuilderContext/RegistrationTypeProvider.swift new file mode 100644 index 000000000..6cf4c0ed2 --- /dev/null +++ b/Source/Protocols/BuilderContext/RegistrationTypeProvider.swift @@ -0,0 +1,13 @@ +// +// RegistrationTypeProvider.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 26.07.2023. +// + +/// Protocol which is used to provide link `RegistrationType` with dedicated view +public protocol RegistrationTypeProvider { + /// `nib` or `class` to choose registration type in `UICollectionView` or `UITableView` + /// or creation method for nested views + static var prefferedRegistration: RegistrationType { get } +} diff --git a/Source/Protocols/Plugins/Generators/BaseItem.swift b/Source/Protocols/Plugins/Generators/BaseItem.swift new file mode 100644 index 000000000..dde33fdda --- /dev/null +++ b/Source/Protocols/Plugins/Generators/BaseItem.swift @@ -0,0 +1,8 @@ +// +// BaseItem.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 28.07.2023. +// + +public typealias BaseItem = ConfigurableItem & RegistrationTypeProvider diff --git a/Source/Protocols/Plugins/Generators/FoldableItem.swift b/Source/Protocols/Plugins/Generators/FoldableItem.swift index b5812dd53..7cecee9ea 100644 --- a/Source/Protocols/Plugins/Generators/FoldableItem.swift +++ b/Source/Protocols/Plugins/Generators/FoldableItem.swift @@ -13,11 +13,12 @@ public protocol FoldableStateHolder: AnyObject { func setExpanded(_ isExpanded: Bool) } -/// Protocol for `TableCellGenerator` to manage expand/collapse state -public protocol FoldableItem: AnyObject, AccessibilityStrategyProvider { +public protocol FoldableStateToggling { + func toggleEpanded() +} - /// Set animation for folder's expand / shrink - var animation: TableFoldablePlugin.AnimationGroup { get } +/// Protocol for `TableCellGenerator` to manage expand/collapse state +public protocol FoldableItem: AnyObject { /// Invokes when cell `didSelect` var didFoldEvent: Event { get } @@ -25,16 +26,4 @@ public protocol FoldableItem: AnyObject, AccessibilityStrategyProvider { /// `true` if cell where expanded, `false` if cell where collapsed var isExpanded: Bool { get set } - /// Generators describing cells to be inserted in expanded state - var childGenerators: [TableCellGenerator] { get set } - -} - -public extension FoldableItem { - var animation: TableFoldablePlugin.AnimationGroup { - return (.none, .fade) - } - - var labelStrategy: AccessibilityStringStrategy { .ignored } - var traitsStrategy: AccessibilityTraitsStrategy { childGenerators.isEmpty ? .ignored : .just(.button) } } diff --git a/Source/Protocols/Protocols.swift b/Source/Protocols/Protocols.swift index f283610e2..746f0fcbf 100644 --- a/Source/Protocols/Protocols.swift +++ b/Source/Protocols/Protocols.swift @@ -9,7 +9,7 @@ import UIKit // sourcery: AutoMockable -open class TableHeaderGenerator: ViewGenerator, IdOwner, TableHeaderRegisterableItem, AccessibilityStrategyProvider { +open class TableHeaderGenerator: AccessoryViewGenerator, IdOwner, TableHeaderRegisterableItem, AccessibilityStrategyProvider { public let id: AnyHashable @@ -37,7 +37,7 @@ open class TableHeaderGenerator: ViewGenerator, IdOwner, TableHeaderRegisterable } -open class TableFooterGenerator: ViewGenerator, TableFooterRegisterableItem { +open class TableFooterGenerator: AccessoryViewGenerator, TableFooterRegisterableItem { public let id: AnyHashable @@ -164,7 +164,7 @@ public extension CollectionCellGenerator { } /// Protocol that incapsulated build logics for current View -public protocol ViewGenerator: AnyObject { +public protocol AccessoryViewGenerator: AnyObject { associatedtype ViewType @@ -271,11 +271,11 @@ public extension CollectionFooterGenerator where Self: ViewBuilder { } /// Protocol that incapsulated type of current cell -public protocol StackCellGenerator: AnyObject { +public protocol ViewGenerator: AnyObject { func generate(stackView: UIStackView, index: Int) -> UIView } -public extension StackCellGenerator where Self: ViewBuilder { +public extension ViewGenerator where Self: ViewBuilder { func generate(stackView: UIStackView, index: Int) -> UIView { let view = Self.ViewType() self.build(view: view) diff --git a/Components/Sources/Common/Protocols/AlignmentProvider.swift b/Source/Protocols/Wrapper/AlignmentProvider.swift similarity index 100% rename from Components/Sources/Common/Protocols/AlignmentProvider.swift rename to Source/Protocols/Wrapper/AlignmentProvider.swift diff --git a/Source/Stack/Generators/BaseStackCellGenerator.swift b/Source/Stack/Generators/BaseStackCellGenerator.swift deleted file mode 100644 index 911dd1841..000000000 --- a/Source/Stack/Generators/BaseStackCellGenerator.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// BaseStackCellGenerator.swift -// Pods -// -// Created by Никита Коробейников on 06.09.2021. -// - -open class BaseStackCellGenerator: StackCellGenerator, ViewBuilder { - - public let model: View.Model - - public init(with model: View.Model) { - self.model = model - } - - public func build(view: View) { - view.configure(with: model) - } - -} diff --git a/Source/Stack/Generators/BaseViewGenerator.swift b/Source/Stack/Generators/BaseViewGenerator.swift new file mode 100644 index 000000000..5f64bb5ec --- /dev/null +++ b/Source/Stack/Generators/BaseViewGenerator.swift @@ -0,0 +1,81 @@ +// +// BaseViewGenerator.swift +// Pods +// +// Created by Никита Коробейников on 06.09.2021. +// + +import UIKit + +public final class EmptyViewGenerator: AccessoryViewGenerator { + + public typealias ViewType = View + + public init() { } + + public func generate() -> View { + switch View.prefferedRegistration { + case .nib: + return View.loadFromNib(bundle: ViewType.bundle() ?? .main) + case .class: + return View(frame: .zero) + } + } + +} + +open class BaseViewGenerator: ViewGenerator, ViewBuilder { + + public let model: View.Model + + // MARK: - Private properties + + private let registerType: RegistrationType + + public init(with model: View.Model, + registerType: RegistrationType = .class) { + self.model = model + self.registerType = registerType + } + + public func build(view: View) { + view.configure(with: model) + } + + public func generate(stackView: UIStackView, index: Int) -> UIView { + let view = createView() + self.build(view: view) + return view + } + +} + +// MARK: - Private + +private extension BaseViewGenerator { + + func createView() -> ViewType { + switch registerType { + case .nib: + return ViewType.loadFromNib(bundle: ViewType.bundle() ?? .main) + case .class: + return ViewType() + } + } + +} + +private extension ConfigurableItem { + + static func loadFromNib(bundle: Bundle) -> T { + let nibName = String(describing: self) + let nib = UINib(nibName: nibName, bundle: bundle) + + guard let view = nib.instantiate(withOwner: self, options: nil).first as? T else { + return T() + } + + return view + } + +} diff --git a/Source/Stack/Generators/ViewContext.swift b/Source/Stack/Generators/ViewContext.swift new file mode 100644 index 000000000..f34a1ca84 --- /dev/null +++ b/Source/Stack/Generators/ViewContext.swift @@ -0,0 +1,25 @@ +// +// ViewContext.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 21.07.2023. +// + +import UIKit +import Foundation + +/// Context which is used to build `UIView` which can be used as child for `StackView` +public struct ViewContext: BuilderContext { + + public typealias ViewType = UIView + public typealias CellsBuilder = (ViewContext.Type) -> [ViewGenerator] + + /// This method is used to build generator for some `UIView` which can be used as child for `StackView` + /// - Parameters: + /// - type: may be `SomeView.self` + /// - model: configurable model for generic `SomeView` + public static func gen(_ type: Item.Type, model: Item.Model) -> BaseViewGenerator where Item: BaseItem { + Item.rddm.viewGenerator(with: model, and: Item.prefferedRegistration) + } + +} diff --git a/Source/Stack/Manager/BaseStackManager.swift b/Source/Stack/Manager/BaseStackManager.swift index 14081e333..d24c5c3ab 100644 --- a/Source/Stack/Manager/BaseStackManager.swift +++ b/Source/Stack/Manager/BaseStackManager.swift @@ -14,13 +14,13 @@ open class BaseStackManager: DataDisplayManager { // MARK: - Typealias public typealias CollectionType = UIStackView - public typealias GeneratorType = StackCellGenerator + public typealias GeneratorType = ViewGenerator // MARK: - Properties // swiftlint:disable implicitly_unwrapped_optional public weak var view: UIStackView! - public var cellGenerators: [StackCellGenerator] = [] + public var cellGenerators: [ViewGenerator] = [] // swiftlint:enable implicitly_unwrapped_optional // MARK: - DataDisplayManager @@ -35,11 +35,11 @@ open class BaseStackManager: DataDisplayManager { } } - public func addCellGenerator(_ generator: StackCellGenerator) { + public func addCellGenerator(_ generator: ViewGenerator) { cellGenerators.append(generator) } - public func addCellGenerators(_ generators: [StackCellGenerator], after: StackCellGenerator) { + public func addCellGenerators(_ generators: [ViewGenerator], after: ViewGenerator) { if let index = index(of: after) { generators.enumerated().forEach { offset, generator in cellGenerators.insert(generator, at: index + offset + 1) @@ -49,7 +49,7 @@ open class BaseStackManager: DataDisplayManager { } } - public func addCellGenerator(_ generator: StackCellGenerator, after: StackCellGenerator) { + public func addCellGenerator(_ generator: ViewGenerator, after: ViewGenerator) { if let index = index(of: after) { cellGenerators.insert(generator, at: index + 1) } else { @@ -57,11 +57,11 @@ open class BaseStackManager: DataDisplayManager { } } - public func addCellGenerators(_ generators: [StackCellGenerator]) { + public func addCellGenerators(_ generators: [ViewGenerator]) { cellGenerators.append(contentsOf: generators) } - public func update(generators: [StackCellGenerator]) { + public func update(generators: [ViewGenerator]) { generators.forEach { generator in if let index = index(of: generator) { cellGenerators.remove(at: index) @@ -82,7 +82,7 @@ open class BaseStackManager: DataDisplayManager { private extension BaseStackManager { - func index(of generator: StackCellGenerator) -> Int? { + func index(of generator: ViewGenerator) -> Int? { return cellGenerators.firstIndex(where: { $0 === generator }) } diff --git a/Source/Stack/View+RDDM.swift b/Source/Stack/View+RDDM.swift index baab22ecd..1c8fe4329 100644 --- a/Source/Stack/View+RDDM.swift +++ b/Source/Stack/View+RDDM.swift @@ -5,10 +5,12 @@ // Created by Никита Коробейников on 06.09.2021. // +import UIKit + public extension StaticDataDisplayWrapper where Base: ConfigurableItem { - func baseStackGenerator(with model: Base.Model) -> BaseStackCellGenerator { - .init(with: model) + func viewGenerator(with model: Base.Model, and registerType: RegistrationType = .class) -> BaseViewGenerator { + .init(with: model, registerType: registerType) } } diff --git a/Source/Table/Generators/BaseCellGenerator.swift b/Source/Table/Generators/BaseCellGenerator.swift deleted file mode 100644 index a8a0a1386..000000000 --- a/Source/Table/Generators/BaseCellGenerator.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// BaseCellGenerator.swift -// ReactiveDataDisplayManager -// -// Created by Mikhail Monakov on 15/01/2019. -// Copyright © 2020 Александр Кравченков. All rights reserved. -// - -import UIKit - -public typealias SelectableTableCellGenerator = TableCellGenerator & SelectableItem - -/// Class for generating reusable Configurable UITableViewCell -open class BaseCellGenerator: SelectableTableCellGenerator where Cell: UITableViewCell { - - // MARK: - Public properties - - public var isNeedDeselect = true - public var didSelectEvent = EmptyEvent() - public var didDeselectEvent = EmptyEvent() - public let model: Cell.Model - - // MARK: - Private Properties - - private let registerType: CellRegisterType - - // MARK: - Initialization - - public init(with model: Cell.Model, - registerType: CellRegisterType = .nib) { - self.model = model - self.registerType = registerType - } - - // MARK: - Open methods - - open func configure(cell: Cell, with model: Cell.Model) { - cell.configure(with: model) - } - - // MARK: - TableCellGenerator - - public var identifier: String { - return String(describing: Cell.self) - } - - public func generate(tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? Cell else { - return UITableViewCell() - } - configure(cell: cell, with: model) - return cell - } - - public func registerCell(in tableView: UITableView) { - switch registerType { - case .nib: - tableView.registerNib(identifier, bundle: Cell.bundle()) - case .class: - tableView.register(Cell.self, forCellReuseIdentifier: identifier) - } - } - - open var cellHeight: CGFloat { - UITableView.automaticDimension - } - - open var estimatedCellHeight: CGFloat? { - nil - } - -} diff --git a/Source/Table/Generators/BaseNonReusableCellGenerator.swift b/Source/Table/Generators/BaseNonReusableCellGenerator.swift index 491698299..d743b2fea 100644 --- a/Source/Table/Generators/BaseNonReusableCellGenerator.swift +++ b/Source/Table/Generators/BaseNonReusableCellGenerator.swift @@ -13,7 +13,8 @@ import UIKit /// Term *non-reusable* means that we are creating cell manually with constructor type defined in **ConstractableItem**. /// In other words this generator will not use `tableView.deqeueReusableCell`. /// - Warning: Do not use this generators in tables with many cells of same type. Because you may catch perfomance issues. -open class BaseNonReusableCellGenerator: SelectableTableCellGenerator where Cell: UITableViewCell { +open class BaseNonReusableCellGenerator: TableCellGenerator & SelectableItem +where Cell: UITableViewCell { // MARK: - Public Properties diff --git a/Source/Table/Generators/CalculatableHeightCellGenerator.swift b/Source/Table/Generators/CalculatableHeightCellGenerator.swift index 723f954a4..f62eea4f8 100644 --- a/Source/Table/Generators/CalculatableHeightCellGenerator.swift +++ b/Source/Table/Generators/CalculatableHeightCellGenerator.swift @@ -19,7 +19,7 @@ open class CalculatableHeightCellGenerator: BaseCe public init(with model: Cell.Model, cellWidth: CGFloat = UIScreen.main.bounds.width, - registerType: CellRegisterType = .nib) { + registerType: RegistrationType = .nib) { self.cellWidth = cellWidth super.init(with: model, registerType: registerType) } diff --git a/Source/Table/Generators/CalculatableHeightNonReusableCellGenerator.swift b/Source/Table/Generators/CalculatableHeightNonReusableCellGenerator.swift deleted file mode 100644 index 8afe86172..000000000 --- a/Source/Table/Generators/CalculatableHeightNonReusableCellGenerator.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// CalculatableHeightNonReusableCellGenerator.swift -// ReactiveDataDisplayManager -// -// Created by Alexander Filimonov on 02/03/2020. -// Copyright © 2020 Александр Кравченков. All rights reserved. -// - -import UIKit - -/// Class for generating non-reusable Configurable UITableViewCell with calculated height -public class CalculatableHeightNonReusableCellGenerator: SelectableTableCellGenerator where Cell: UITableViewCell { - - // MARK: - Public properties - - public var isNeedDeselect = true - public var didSelectEvent = EmptyEvent() - public var didDeselectEvent = EmptyEvent() - private(set) public var model: Cell.Model - private(set) public lazy var cell: Cell? = { - return Cell.fromXib(bundle: Cell.bundle()) - }() - - // MARK: - Private Properties - - private let cellWidth: CGFloat - private let registerType: CellRegisterType - - // MARK: - Initialization - - public init(with model: Cell.Model, - cellWidth: CGFloat = UIScreen.main.bounds.width, - registerType: CellRegisterType = .nib) { - self.model = model - self.cellWidth = cellWidth - self.registerType = registerType - } - - // MARK: - Public Methods - - public func update(model: Cell.Model) { - self.model = model - cell?.configure(with: model) - } - - // MARK: - TableCellGenerator - - public var identifier: String { - return String(describing: Cell.self) - } - - public var cellHeight: CGFloat { - return Cell.getHeight(forWidth: cellWidth, with: model) - } - - public var estimatedCellHeight: CGFloat? { - return Cell.getHeight(forWidth: cellWidth, with: model) - } - - public func generate(tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { - cell?.configure(with: model) - return cell ?? UITableViewCell() - } - - public func registerCell(in tableView: UITableView) { - switch registerType { - case .nib: - tableView.registerNib(identifier, bundle: Cell.bundle()) - case .class: - tableView.register(Cell.self, forCellReuseIdentifier: identifier) - } - } - -} diff --git a/Source/Table/Generators/FoldableCellGenerator.swift b/Source/Table/Generators/FoldableCellGenerator.swift deleted file mode 100644 index 19a0bb340..000000000 --- a/Source/Table/Generators/FoldableCellGenerator.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// FoldableCellGenerator.swift -// ReactiveDataDisplayManager -// -// Created by korshunov on 23.06.2023. -// - -import UIKit - -/// A generator that can insert and remove child generators by tap -open class FoldableCellGenerator: BaseCellGenerator, FoldableItem { - - // MARK: - FoldableItem - - /// Animations for cells insertion and deletion - open var animation: TableFoldablePlugin.AnimationGroup = (.left, .top) - - /// Event on changed `isExpanded` state - open var didFoldEvent = Event() - - /// Folded/unfolded state - open var isExpanded = false - - /// Child generators which can be folded/unfolded - open var childGenerators: [TableCellGenerator] = [] - - // MARK: - BaseCellGenerator - - open override func configure(cell: Cell, with model: Cell.Model) { - super.configure(cell: cell, with: model) - - cell.setExpanded(isExpanded) - - didFoldEvent.addListner(with: "rddm.foldable-on-dequeue") { [weak cell] isExpanded in - cell?.setExpanded(isExpanded) - } - } - -} diff --git a/Source/Table/Generators/TableContext.swift b/Source/Table/Generators/TableContext.swift new file mode 100644 index 000000000..b425d6bf0 --- /dev/null +++ b/Source/Table/Generators/TableContext.swift @@ -0,0 +1,33 @@ +// +// TableContext.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 21.07.2023. +// + +import UIKit +import Foundation + +/// Context which is used to prepare generators for `UIView` or `UITableViewCell` to place them into `UITableView` +public struct TableContext: BuilderContext { + + public typealias ViewType = UITableViewCell + public typealias CellsBuilder = (TableContext.Type) -> [TableCellGenerator] + + /// This method is used to build generator for some `UIView` to place it in `UITableView` + /// - Parameters: + /// - type: may be `SomeView.self` + /// - model: configurable model for generic `SomeView` + public static func gen(_ type: Item.Type, model: Item.Model) -> BaseCellGenerator> where Item: BaseItem { + Item.rddm.tableGenerator(with: model) + } + + /// This method is used to build generator for some `UITableViewCell` to place it in `UITableView` + /// - Parameters: + /// - type: may be `SomeCell.self` + /// - model: configurable model for generic `SomeCell` + public static func gen(_ type: Item.Type, model: Item.Model) -> BaseCellGenerator where Item: ViewType & BaseItem { + Item.rddm.baseGenerator(with: model, and: Item.prefferedRegistration) + } + +} diff --git a/Source/Table/Plugins/PluginAction/TableFoldablePlugin.swift b/Source/Table/Plugins/PluginAction/TableFoldablePlugin.swift index 8b80e581f..b526c6e60 100644 --- a/Source/Table/Plugins/PluginAction/TableFoldablePlugin.swift +++ b/Source/Table/Plugins/PluginAction/TableFoldablePlugin.swift @@ -15,34 +15,34 @@ public class TableFoldablePlugin: BaseTablePlugin { // MARK: - Nested types - public typealias AnimationGroup = (remove: UITableView.RowAnimation, insert: UITableView.RowAnimation) + public typealias GeneratorType = FoldableItem & TableChildrenHolder & FoldableStateToggling - // MARK: - BaseTablePlugin + // MARK: - Plugin body public override func process(event: TableEvent, with manager: BaseTableManager?) { switch event { case .didSelect(let indexPath): guard let generator = manager?.sections[indexPath.section].generators[indexPath.row], - let foldable = generator as? FoldableItem + let foldable = generator as? GeneratorType else { return } if foldable.isExpanded { - foldable.childGenerators.forEach { manager?.remove($0, + foldable.children.forEach { manager?.remove($0, with: .animated(foldable.animation.remove), needScrollAt: nil, needRemoveEmptySection: false) } } else { - addCellGenerators(foldable.childGenerators, after: generator, with: manager) + addCellGenerators(foldable.children, after: generator, with: manager) } - foldable.isExpanded = !foldable.isExpanded + foldable.toggleEpanded() foldable.didFoldEvent.invoke(with: (foldable.isExpanded)) - updateIfNeeded(foldable.childGenerators, with: manager) + updateIfNeeded(foldable.children, with: manager) default: break } @@ -54,21 +54,21 @@ public class TableFoldablePlugin: BaseTablePlugin { private extension TableFoldablePlugin { - func addCellGenerators(_ childGenerators: [TableCellGenerator], + func addCellGenerators(_ children: [TableCellGenerator], after generator: TableCellGenerator, with manager: BaseTableManager?) { if let manager = manager as? GravityTableManager { - manager.addCellGenerators(childGenerators, after: generator) - } else if let foldable = generator as? FoldableItem { - manager?.insertManual(after: generator, new: childGenerators, with: .animated(foldable.animation.insert)) + manager.addCellGenerators(children, after: generator) + } else if let foldable = generator as? GeneratorType { + manager?.insertManual(after: generator, new: children, with: .animated(foldable.animation.insert)) } else { - manager?.insertManual(after: generator, new: childGenerators, with: .notAnimated) + manager?.insertManual(after: generator, new: children, with: .notAnimated) } } - func updateIfNeeded(_ childGenerators: [TableCellGenerator], with manager: BaseTableManager?) { + func updateIfNeeded(_ children: [TableCellGenerator], with manager: BaseTableManager?) { guard let manager = manager as? GravityTableManager else { return } - manager.update(generators: childGenerators) + manager.update(generators: children) } } diff --git a/Source/Table/Protocols/TableChildrenHolder.swift b/Source/Table/Protocols/TableChildrenHolder.swift new file mode 100644 index 000000000..ede50c627 --- /dev/null +++ b/Source/Table/Protocols/TableChildrenHolder.swift @@ -0,0 +1,31 @@ +// +// TableChildrenHolder.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 28.07.2023. +// + +import UIKit + +public typealias TableChildrenAnimationGroup = (remove: UITableView.RowAnimation, insert: UITableView.RowAnimation) + +public protocol TableChildrenHolder: AccessibilityStrategyProvider { + + /// Set animation for folder's expand / shrink + var animation: TableChildrenAnimationGroup { get set } + + /// Generators describing cells to be inserted in expanded state + var children: [TableCellGenerator] { get set } + +} + +// MARK: - Defaults + +public extension TableChildrenHolder { + var animation: TableChildrenAnimationGroup { + return (.none, .fade) + } + + var labelStrategy: AccessibilityStringStrategy { .ignored } + var traitsStrategy: AccessibilityTraitsStrategy { children.isEmpty ? .ignored : .just(.button) } +} diff --git a/Source/Table/TableCell+RDDM.swift b/Source/Table/TableCell+RDDM.swift index 0a391824a..3b3bb1d62 100644 --- a/Source/Table/TableCell+RDDM.swift +++ b/Source/Table/TableCell+RDDM.swift @@ -10,7 +10,7 @@ import UIKit public extension StaticDataDisplayWrapper where Base: UITableViewCell & ConfigurableItem { - func baseGenerator(with model: Base.Model, and registerType: CellRegisterType = .nib) -> BaseCellGenerator { + func baseGenerator(with model: Base.Model, and registerType: RegistrationType = .nib) -> BaseCellGenerator { .init(with: model, registerType: registerType) } @@ -18,7 +18,7 @@ public extension StaticDataDisplayWrapper where Base: UITableViewCell & Configur public extension StaticDataDisplayWrapper where Base: UITableViewCell & ConfigurableItem & FoldableStateHolder { - func foldableGenerator(with model: Base.Model, and registerType: CellRegisterType = .nib) -> FoldableCellGenerator { + func foldableGenerator(with model: Base.Model, and registerType: RegistrationType = .nib) -> FoldableCellGenerator { .init(with: model, registerType: registerType) } @@ -35,7 +35,7 @@ public extension StaticDataDisplayWrapper where Base: UITableViewCell & Configur public extension StaticDataDisplayWrapper where Base: UITableViewCell & ConfigurableItem & CalculatableHeightItem { func calculatableHeightGenerator(with model: Base.Model, - and registerType: CellRegisterType = .nib, + and registerType: RegistrationType = .nib, referenceWidth: CGFloat = UIScreen.main.bounds.width) -> CalculatableHeightCellGenerator { .init(with: model, cellWidth: referenceWidth, registerType: registerType) } diff --git a/Source/Table/TableSection.swift b/Source/Table/TableSection.swift index 559ddd9ab..384ad06ed 100644 --- a/Source/Table/TableSection.swift +++ b/Source/Table/TableSection.swift @@ -21,4 +21,10 @@ public extension TableSection { self.init(generators: generators, header: header, footer: EmptyTableFooterGenerator()) } + static func create(header: HeaderGeneratorType, + footer: FooterGeneratorType, + @GeneratorsBuilder generators: TableContext.CellsBuilder) -> Self { + Self(generators: generators(TableContext.self), header: header, footer: footer) + } + } diff --git a/Source/Table/Wrapper/TableWrappedCell+RDDM.swift b/Source/Table/Wrapper/TableWrappedCell+RDDM.swift new file mode 100644 index 000000000..5458ebb9e --- /dev/null +++ b/Source/Table/Wrapper/TableWrappedCell+RDDM.swift @@ -0,0 +1,39 @@ +// +// TableWrappedCell+RDDM.swift +// ReactiveDataDisplayManager +// +// Created by Никита Коробейников on 05.06.2023. +// + +import UIKit + +public extension StaticDataDisplayWrapper where Base: UIView & BaseItem { + + func tableGenerator(with model: Base.Model) -> BaseCellGenerator> { + TableWrappedCell.rddm.baseGenerator(with: model, and: .class) + } + +} + +public extension StaticDataDisplayWrapper where Base: UITableViewCell & BaseItem { + + func tableGenerator(with model: Base.Model) -> BaseCellGenerator { + Base.rddm.baseGenerator(with: model, and: Base.prefferedRegistration) + } + +} + +// MARK: - CalculatableHeightItem + +public extension StaticDataDisplayWrapper where Base: UIView & ConfigurableItem & CalculatableHeightItem & RegistrationTypeProvider { + + func tableCalculatableHightGenerator( + with model: Base.Model, + and registerType: RegistrationType = .nib + ) -> CalculatableHeightCellGenerator> { + + CalculatableHeightCellGenerator>(with: model, + registerType: registerType) + } + +} diff --git a/Components/Sources/Table/TableWrappedCell.swift b/Source/Table/Wrapper/TableWrappedCell.swift similarity index 61% rename from Components/Sources/Table/TableWrappedCell.swift rename to Source/Table/Wrapper/TableWrappedCell.swift index d77a7c768..3e9d7db2b 100644 --- a/Components/Sources/Table/TableWrappedCell.swift +++ b/Source/Table/Wrapper/TableWrappedCell.swift @@ -6,16 +6,15 @@ // import UIKit -import ReactiveDataDisplayManager /// Empty table cell with `View`. Configuration is implemented within `ViewWrapper`. -public final class TableWrappedCell: UITableViewCell, ViewWrapper { +public final class TableWrappedCell: UITableViewCell, ViewWrapper { public typealias Model = View.Model // MARK: - Properties - public let nestedView: View = .init(frame: .zero) + public let nestedView: View = EmptyViewGenerator().generate() public var cachedInsets: UIEdgeInsets? @@ -40,3 +39,25 @@ extension TableWrappedCell: CalculatableHeightItem where View: CalculatableHeigh } } + +// MARK: - HighlightableItem + +extension TableWrappedCell: HighlightableItem where View: HighlightableItem { + + public func applyUnhighlightedStyle() { + nestedView.applyUnhighlightedStyle() + } + + public func applyHighlightedStyle() { + nestedView.applyHighlightedStyle() + } + + public func applySelectedStyle() { + nestedView.applySelectedStyle() + } + + public func applyDeselectedStyle() { + nestedView.applyDeselectedStyle() + } + +}