Skip to content

Commit

Permalink
Update APIs used to add elements to lists
Browse files Browse the repository at this point in the history
  • Loading branch information
kyleve committed Jul 25, 2022
1 parent 05cbbfe commit ca36edc
Show file tree
Hide file tree
Showing 14 changed files with 640 additions and 30 deletions.
89 changes: 89 additions & 0 deletions BlueprintUILists/Sources/Element+HeaderFooter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Element+HeaderFooter.swift
// BlueprintUILists
//
// Created by Kyle Van Essen on 7/24/22.
//

import BlueprintUI
import ListableUI


// MARK: HeaderFooter / HeaderFooterContent Extensions


extension Element {

/// Converts the given `Element` into a Listable `HeaderFooter`. You many also optionally
/// configure the header / footer, setting its values such as the `onTap` callbacks, etc.
///
/// ```swift
/// MyElement(...)
/// .headerFooter { header in
/// header.onTap = { ... }
/// }
/// ```
///
/// ## ⚠️ Performance Considerations
/// Unless your `Element` conforms to `Equatable` or `IsEquivalentContent`,
/// it will return `false` for `isEquivalent` for each content update, which can dramatically
/// hurt performance for longer lists (eg, more than 20 items): it will be re-measured for each content update.
///
/// It is encouraged for these longer lists, you ensure your `Element` conforms to one of these protocols.
public func headerFooter(
configure : (inout HeaderFooter<WrappedHeaderFooterContent<Self>>) -> () = { _ in }
) -> HeaderFooter<WrappedHeaderFooterContent<Self>> {
HeaderFooter(
WrappedHeaderFooterContent(
represented: self
),
configure: configure
)
}

/// Used by internal Listable methods to convert type-erased `Element` instances into `Item` instances.
func toHeaderFooterConvertible() -> AnyHeaderFooterConvertible {
/// We use `type(of:)` to ensure we get the actual type, not just `Element`.
WrappedHeaderFooterContent(
represented: self
)
}
}


public struct WrappedHeaderFooterContent<ElementType:Element> : BlueprintHeaderFooterContent
{
public let represented : ElementType

public func isEquivalent(to other: Self) -> Bool {
false
}

public var elementRepresentation: Element {
represented
}
}


extension WrappedHeaderFooterContent where ElementType : Equatable {

public func isEquivalent(to other: Self) -> Bool {
represented == other.represented
}

public var reappliesToVisibleView: ReappliesToVisibleView {
.ifNotEquivalent
}
}


extension WrappedHeaderFooterContent where ElementType : IsEquivalentContent {

public func isEquivalent(to other: Self) -> Bool {
represented.isEquivalent(to: other.represented)
}

public var reappliesToVisibleView: ReappliesToVisibleView {
.ifNotEquivalent
}
}
122 changes: 122 additions & 0 deletions BlueprintUILists/Sources/Element+Item.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// Element+Item.swift
// BlueprintUILists
//
// Created by Kyle Van Essen on 7/24/22.
//

import BlueprintUI
import ListableUI


// MARK: Item / ItemContent Extensions

extension Element {

/// Converts the given `Element` into a Listable `Item`. You many also optionally
/// configure the item, setting its values such as the `onDisplay` callbacks, etc.
///
/// ```swift
/// MyElement(...)
/// .item { item in
/// item.insertAndRemoveAnimations = .scaleUp
/// }
/// ```
///
/// ## ⚠️ Performance Considerations
/// Unless your `Element` conforms to `Equatable` or `IsEquivalentContent`,
/// it will return `false` for `isEquivalent` for each content update, which can dramatically
/// hurt performance for longer lists (eg, more than 20 items): it will be re-measured for each content update.
///
/// It is encouraged for these longer lists, you ensure your `Element` conforms to one of these protocols.
public func item(
configure : (inout Item<WrappedElementContent<Self, ObjectIdentifier>>) -> () = { _ in }
) -> Item<WrappedElementContent<Self, ObjectIdentifier>> {
Item(
WrappedElementContent(
represented: self,
identifierValue: ObjectIdentifier(Self.Type.self)
),
configure: configure
)
}

/// Converts the given `Element` into a Listable `Item` with the provided ID. You can use this ID
/// to scroll to or later access the item through the regular list access APIs.
/// You many also optionally configure the item, setting its values such as the `onDisplay` callbacks, etc.
///
/// ```swift
/// MyElement(...)
/// .item(id: "my-provided-id") { item in
/// item.insertAndRemoveAnimations = .scaleUp
/// }
/// ```
///
/// ## ⚠️ Performance Considerations
/// Unless your `Element` conforms to `Equatable` or `IsEquivalentContent`,
/// it will return `false` for `isEquivalent` for each content update, which can dramatically
/// hurt performance for longer lists (eg, more than 20 items): it will be re-measured for each content update.
///
/// It is encouraged for these longer lists, you ensure your `Element` conforms to one of these protocols.
public func item<ID:Hashable>(
id : ID,
configure : (inout Item<WrappedElementContent<Self, ID>>) -> () = { _ in }
) -> Item<WrappedElementContent<Self, ID>> {
Item(
WrappedElementContent(
represented: self,
identifierValue: id
),
configure: configure
)
}

/// Used by internal Listable methods to convert type-erased `Element` instances into `Item` instances.
func toAnyItemConvertible() -> AnyItemConvertible {
/// We use `type(of:)` to ensure we get the actual type, not just `Element`.
WrappedElementContent(
represented: self,
identifierValue: ObjectIdentifier(type(of: self))
)
}
}


public struct WrappedElementContent<ElementType:Element, IdentifierValue:Hashable> : BlueprintItemContent
{
public let represented : ElementType

public let identifierValue: IdentifierValue

public func isEquivalent(to other: Self) -> Bool {
false
}

public func element(with info: ApplyItemContentInfo) -> Element {
represented
}
}


extension WrappedElementContent where ElementType : Equatable {

public func isEquivalent(to other: Self) -> Bool {
represented == other.represented
}

public var reappliesToVisibleView: ReappliesToVisibleView {
.ifNotEquivalent
}
}


extension WrappedElementContent where ElementType : IsEquivalentContent {

public func isEquivalent(to other: Self) -> Bool {
represented.isEquivalent(to: other.represented)
}

public var reappliesToVisibleView: ReappliesToVisibleView {
.ifNotEquivalent
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// HeaderFooter.swift
// ElementHeaderFooter.swift
// BlueprintUILists
//
// Created by Kyle Van Essen on 10/9/20.
Expand All @@ -9,6 +9,8 @@ import ListableUI
import BlueprintUI


///
/// ⚠️ This method is soft-deprecated! Consider using `myElement.headerFooter(...)` instead.
///
/// Provides a way to create a `HeaderFooter` for your Blueprint elements without
/// requiring the creation of a new `BlueprintHeaderFooterContent` struct.
Expand Down Expand Up @@ -62,6 +64,8 @@ public func ElementHeaderFooter<Represented>(
)
}

///
/// ⚠️ This method is soft-deprecated! Consider using `myElement.headerFooter(...)` instead.
///
/// Provides a way to create a `HeaderFooter` for your Blueprint elements without
/// requiring the creation of a new `BlueprintHeaderFooterContent` struct.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Item.swift
// ElementItem.swift
// BlueprintUILists
//
// Created by Kyle Van Essen on 9/10/20.
Expand All @@ -9,6 +9,8 @@ import ListableUI
import BlueprintUI


///
/// ⚠️ This method is soft-deprecated! Consider using `myElement.item(...)` instead.
///
/// Provides a way to create an `Item` for your Blueprint elements without
/// requiring the creation of a new `BlueprintItemContent` struct.
Expand Down Expand Up @@ -68,6 +70,8 @@ public func ElementItem<Represented, IdentifierValue:Hashable>(


///
/// ⚠️ This method is soft-deprecated! Consider using `myElement.item(...)` instead.
///
/// Provides a way to create an `Item` for your Blueprint elements without
/// requiring the creation of a new `BlueprintItemContent` struct.
///
Expand Down Expand Up @@ -129,7 +133,6 @@ public struct ElementItemContent<Represented, IdentifierValue:Hashable> : Bluepr
public let represented : Represented

let idValueKeyPath : KeyPath<Represented, IdentifierValue>
let defaults: DefaultProperties = .init()
let isEquivalentProvider : (Represented, Represented) -> Bool
let elementProvider : (Represented, ApplyItemContentInfo) -> Element
let backgroundProvider : (Represented, ApplyItemContentInfo) -> Element?
Expand All @@ -139,10 +142,6 @@ public struct ElementItemContent<Represented, IdentifierValue:Hashable> : Bluepr
self.represented[keyPath: self.idValueKeyPath]
}

public var defaultItemProperties: DefaultProperties {
defaults
}

public func isEquivalent(to other: Self) -> Bool {
self.isEquivalentProvider(self.represented, other.represented)
}
Expand Down
21 changes: 17 additions & 4 deletions BlueprintUILists/Sources/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public struct List : Element
//
// MARK: Initialization
//

/// Create a new list, configured with the provided properties,
/// configured with the provided `ListProperties` builder.
public init(
Expand All @@ -76,13 +76,26 @@ public struct List : Element
public init(
measurement : List.Measurement = .fillParent,
configure : ListProperties.Configure = { _ in },
@ListableBuilder<Section> sections : () -> [Section]
@ListableBuilder<Section> sections : () -> [Section],
@ListableOptionalBuilder<AnyHeaderFooterConvertible> containerHeader : () -> AnyHeaderFooterConvertible? = { nil },
@ListableOptionalBuilder<AnyHeaderFooterConvertible> header : () -> AnyHeaderFooterConvertible? = { nil },
@ListableOptionalBuilder<AnyHeaderFooterConvertible> footer : () -> AnyHeaderFooterConvertible? = { nil },
@ListableOptionalBuilder<AnyHeaderFooterConvertible> overscrollFooter : () -> AnyHeaderFooterConvertible? = { nil }
) {
self.measurement = measurement

self.properties = .default(with: configure)
var properties = ListProperties.default {
$0.sections = sections()

$0.content.containerHeader = containerHeader()
$0.content.header = header()
$0.content.footer = footer()
$0.content.overscrollFooter = overscrollFooter()
}

configure(&properties)

self.properties.sections += sections()
self.properties = properties
}

//
Expand Down
Loading

0 comments on commit ca36edc

Please sign in to comment.