Skip to content

Commit ca36edc

Browse files
committed
Update APIs used to add elements to lists
1 parent 05cbbfe commit ca36edc

14 files changed

+640
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//
2+
// Element+HeaderFooter.swift
3+
// BlueprintUILists
4+
//
5+
// Created by Kyle Van Essen on 7/24/22.
6+
//
7+
8+
import BlueprintUI
9+
import ListableUI
10+
11+
12+
// MARK: HeaderFooter / HeaderFooterContent Extensions
13+
14+
15+
extension Element {
16+
17+
/// Converts the given `Element` into a Listable `HeaderFooter`. You many also optionally
18+
/// configure the header / footer, setting its values such as the `onTap` callbacks, etc.
19+
///
20+
/// ```swift
21+
/// MyElement(...)
22+
/// .headerFooter { header in
23+
/// header.onTap = { ... }
24+
/// }
25+
/// ```
26+
///
27+
/// ## ⚠️ Performance Considerations
28+
/// Unless your `Element` conforms to `Equatable` or `IsEquivalentContent`,
29+
/// it will return `false` for `isEquivalent` for each content update, which can dramatically
30+
/// hurt performance for longer lists (eg, more than 20 items): it will be re-measured for each content update.
31+
///
32+
/// It is encouraged for these longer lists, you ensure your `Element` conforms to one of these protocols.
33+
public func headerFooter(
34+
configure : (inout HeaderFooter<WrappedHeaderFooterContent<Self>>) -> () = { _ in }
35+
) -> HeaderFooter<WrappedHeaderFooterContent<Self>> {
36+
HeaderFooter(
37+
WrappedHeaderFooterContent(
38+
represented: self
39+
),
40+
configure: configure
41+
)
42+
}
43+
44+
/// Used by internal Listable methods to convert type-erased `Element` instances into `Item` instances.
45+
func toHeaderFooterConvertible() -> AnyHeaderFooterConvertible {
46+
/// We use `type(of:)` to ensure we get the actual type, not just `Element`.
47+
WrappedHeaderFooterContent(
48+
represented: self
49+
)
50+
}
51+
}
52+
53+
54+
public struct WrappedHeaderFooterContent<ElementType:Element> : BlueprintHeaderFooterContent
55+
{
56+
public let represented : ElementType
57+
58+
public func isEquivalent(to other: Self) -> Bool {
59+
false
60+
}
61+
62+
public var elementRepresentation: Element {
63+
represented
64+
}
65+
}
66+
67+
68+
extension WrappedHeaderFooterContent where ElementType : Equatable {
69+
70+
public func isEquivalent(to other: Self) -> Bool {
71+
represented == other.represented
72+
}
73+
74+
public var reappliesToVisibleView: ReappliesToVisibleView {
75+
.ifNotEquivalent
76+
}
77+
}
78+
79+
80+
extension WrappedHeaderFooterContent where ElementType : IsEquivalentContent {
81+
82+
public func isEquivalent(to other: Self) -> Bool {
83+
represented.isEquivalent(to: other.represented)
84+
}
85+
86+
public var reappliesToVisibleView: ReappliesToVisibleView {
87+
.ifNotEquivalent
88+
}
89+
}
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//
2+
// Element+Item.swift
3+
// BlueprintUILists
4+
//
5+
// Created by Kyle Van Essen on 7/24/22.
6+
//
7+
8+
import BlueprintUI
9+
import ListableUI
10+
11+
12+
// MARK: Item / ItemContent Extensions
13+
14+
extension Element {
15+
16+
/// Converts the given `Element` into a Listable `Item`. You many also optionally
17+
/// configure the item, setting its values such as the `onDisplay` callbacks, etc.
18+
///
19+
/// ```swift
20+
/// MyElement(...)
21+
/// .item { item in
22+
/// item.insertAndRemoveAnimations = .scaleUp
23+
/// }
24+
/// ```
25+
///
26+
/// ## ⚠️ Performance Considerations
27+
/// Unless your `Element` conforms to `Equatable` or `IsEquivalentContent`,
28+
/// it will return `false` for `isEquivalent` for each content update, which can dramatically
29+
/// hurt performance for longer lists (eg, more than 20 items): it will be re-measured for each content update.
30+
///
31+
/// It is encouraged for these longer lists, you ensure your `Element` conforms to one of these protocols.
32+
public func item(
33+
configure : (inout Item<WrappedElementContent<Self, ObjectIdentifier>>) -> () = { _ in }
34+
) -> Item<WrappedElementContent<Self, ObjectIdentifier>> {
35+
Item(
36+
WrappedElementContent(
37+
represented: self,
38+
identifierValue: ObjectIdentifier(Self.Type.self)
39+
),
40+
configure: configure
41+
)
42+
}
43+
44+
/// Converts the given `Element` into a Listable `Item` with the provided ID. You can use this ID
45+
/// to scroll to or later access the item through the regular list access APIs.
46+
/// You many also optionally configure the item, setting its values such as the `onDisplay` callbacks, etc.
47+
///
48+
/// ```swift
49+
/// MyElement(...)
50+
/// .item(id: "my-provided-id") { item in
51+
/// item.insertAndRemoveAnimations = .scaleUp
52+
/// }
53+
/// ```
54+
///
55+
/// ## ⚠️ Performance Considerations
56+
/// Unless your `Element` conforms to `Equatable` or `IsEquivalentContent`,
57+
/// it will return `false` for `isEquivalent` for each content update, which can dramatically
58+
/// hurt performance for longer lists (eg, more than 20 items): it will be re-measured for each content update.
59+
///
60+
/// It is encouraged for these longer lists, you ensure your `Element` conforms to one of these protocols.
61+
public func item<ID:Hashable>(
62+
id : ID,
63+
configure : (inout Item<WrappedElementContent<Self, ID>>) -> () = { _ in }
64+
) -> Item<WrappedElementContent<Self, ID>> {
65+
Item(
66+
WrappedElementContent(
67+
represented: self,
68+
identifierValue: id
69+
),
70+
configure: configure
71+
)
72+
}
73+
74+
/// Used by internal Listable methods to convert type-erased `Element` instances into `Item` instances.
75+
func toAnyItemConvertible() -> AnyItemConvertible {
76+
/// We use `type(of:)` to ensure we get the actual type, not just `Element`.
77+
WrappedElementContent(
78+
represented: self,
79+
identifierValue: ObjectIdentifier(type(of: self))
80+
)
81+
}
82+
}
83+
84+
85+
public struct WrappedElementContent<ElementType:Element, IdentifierValue:Hashable> : BlueprintItemContent
86+
{
87+
public let represented : ElementType
88+
89+
public let identifierValue: IdentifierValue
90+
91+
public func isEquivalent(to other: Self) -> Bool {
92+
false
93+
}
94+
95+
public func element(with info: ApplyItemContentInfo) -> Element {
96+
represented
97+
}
98+
}
99+
100+
101+
extension WrappedElementContent where ElementType : Equatable {
102+
103+
public func isEquivalent(to other: Self) -> Bool {
104+
represented == other.represented
105+
}
106+
107+
public var reappliesToVisibleView: ReappliesToVisibleView {
108+
.ifNotEquivalent
109+
}
110+
}
111+
112+
113+
extension WrappedElementContent where ElementType : IsEquivalentContent {
114+
115+
public func isEquivalent(to other: Self) -> Bool {
116+
represented.isEquivalent(to: other.represented)
117+
}
118+
119+
public var reappliesToVisibleView: ReappliesToVisibleView {
120+
.ifNotEquivalent
121+
}
122+
}

BlueprintUILists/Sources/HeaderFooter.swift BlueprintUILists/Sources/ElementHeaderFooter.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// HeaderFooter.swift
2+
// ElementHeaderFooter.swift
33
// BlueprintUILists
44
//
55
// Created by Kyle Van Essen on 10/9/20.
@@ -9,6 +9,8 @@ import ListableUI
99
import BlueprintUI
1010

1111

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

67+
///
68+
/// ⚠️ This method is soft-deprecated! Consider using `myElement.headerFooter(...)` instead.
6569
///
6670
/// Provides a way to create a `HeaderFooter` for your Blueprint elements without
6771
/// requiring the creation of a new `BlueprintHeaderFooterContent` struct.

BlueprintUILists/Sources/Item.swift BlueprintUILists/Sources/ElementItem.swift

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Item.swift
2+
// ElementItem.swift
33
// BlueprintUILists
44
//
55
// Created by Kyle Van Essen on 9/10/20.
@@ -9,6 +9,8 @@ import ListableUI
99
import BlueprintUI
1010

1111

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

6971

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

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

142-
public var defaultItemProperties: DefaultProperties {
143-
defaults
144-
}
145-
146145
public func isEquivalent(to other: Self) -> Bool {
147146
self.isEquivalentProvider(self.represented, other.represented)
148147
}

BlueprintUILists/Sources/List.swift

+17-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public struct List : Element
5959
//
6060
// MARK: Initialization
6161
//
62-
62+
6363
/// Create a new list, configured with the provided properties,
6464
/// configured with the provided `ListProperties` builder.
6565
public init(
@@ -76,13 +76,26 @@ public struct List : Element
7676
public init(
7777
measurement : List.Measurement = .fillParent,
7878
configure : ListProperties.Configure = { _ in },
79-
@ListableBuilder<Section> sections : () -> [Section]
79+
@ListableBuilder<Section> sections : () -> [Section],
80+
@ListableOptionalBuilder<AnyHeaderFooterConvertible> containerHeader : () -> AnyHeaderFooterConvertible? = { nil },
81+
@ListableOptionalBuilder<AnyHeaderFooterConvertible> header : () -> AnyHeaderFooterConvertible? = { nil },
82+
@ListableOptionalBuilder<AnyHeaderFooterConvertible> footer : () -> AnyHeaderFooterConvertible? = { nil },
83+
@ListableOptionalBuilder<AnyHeaderFooterConvertible> overscrollFooter : () -> AnyHeaderFooterConvertible? = { nil }
8084
) {
8185
self.measurement = measurement
8286

83-
self.properties = .default(with: configure)
87+
var properties = ListProperties.default {
88+
$0.sections = sections()
89+
90+
$0.content.containerHeader = containerHeader()
91+
$0.content.header = header()
92+
$0.content.footer = footer()
93+
$0.content.overscrollFooter = overscrollFooter()
94+
}
95+
96+
configure(&properties)
8497

85-
self.properties.sections += sections()
98+
self.properties = properties
8699
}
87100

88101
//

0 commit comments

Comments
 (0)