Skip to content

Commit b5ef539

Browse files
committed
Support re-ordering between sections. Add additional validation and reading options for re-order events. Improve Identifier to aid in type safety when creating and reading identifiers. See #292 for all changes and discussion.
1 parent 115da77 commit b5ef539

File tree

86 files changed

+2383
-623
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+2383
-623
lines changed

BlueprintUILists/Sources/BlueprintItemContent.swift

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import ListableUI
1515
/// you instead provide Blueprint elements, and `Listable` handles mapping this to an underlying `BlueprintView`.
1616
///
1717
/// A `BlueprintItemContent` that displays text might look like this:
18-
/// ```
18+
/// ```swift
1919
/// struct MyItemContent : BlueprintItemContent, Equatable
2020
/// {
2121
/// var text : String
22+
/// var id : UUID
2223
///
23-
/// var identifier: Identifier<MyItemContent> {
24-
/// return .init(self.text)
24+
/// var identifier: String {
25+
/// self.id
2526
/// }
2627
///
2728
/// func element(with info : ApplyItemContentInfo) -> Element

BlueprintUILists/Sources/Item.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension Item
4141
/// item.selectionStyle = .tappable
4242
/// }
4343
/// ```
44-
public init<Represented, IDType:Hashable>(
44+
public init<Represented, IDType>(
4545
_ representing : Represented,
4646

4747
identifier : KeyPath<Represented, IDType>,
@@ -99,7 +99,7 @@ extension Item
9999
/// item.selectionStyle = .tappable
100100
/// }
101101
/// ```
102-
public init<Represented, IDType:Hashable>(
102+
public init<Represented, IDType>(
103103
_ representing : Represented,
104104

105105
identifier : KeyPath<Represented, IDType>,
@@ -142,8 +142,8 @@ public struct BlueprintItemContentWrapper<Represented, IDType:Hashable> : Bluepr
142142
let backgroundProvider : (Represented, ApplyItemContentInfo) -> Element?
143143
let selectedBackgroundProvider : (Represented, ApplyItemContentInfo) -> Element?
144144

145-
public var identifier: Identifier<Self> {
146-
.init(self.representing[keyPath: self.identifierKeyPath])
145+
public var identifier: IDType {
146+
self.representing[keyPath: self.identifierKeyPath]
147147
}
148148

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

BlueprintUILists/Sources/ListReorderGesture.swift

+9-57
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import ListableUI
3131
/// row.add(child: ListReorderGesture(actions: info.actions, wrapping: MyReorderGrabber()))
3232
///
3333
/// // Could also be written as:
34-
/// row.add(child: MyReorderGrabber().listReorderGesture(with: info.reordering))
34+
/// row.add(child: MyReorderGrabber().listReorderGesture(with: info.reorderingActions))
3535
/// }
3636
/// }
3737
/// ```
@@ -43,14 +43,7 @@ public struct ListReorderGesture : Element
4343
/// If the gesture is enabled or not.
4444
public var isEnabled : Bool
4545

46-
typealias OnStart = () -> Bool
47-
var onStart : OnStart
48-
49-
typealias OnMove = (UIPanGestureRecognizer) -> ()
50-
var onMove : OnMove
51-
52-
typealias OnDone = () -> ()
53-
var onDone : OnDone
46+
let actions : ReorderingActions
5447

5548
/// Creates a new re-order gesture which wraps the provided element.
5649
///
@@ -63,9 +56,7 @@ public struct ListReorderGesture : Element
6356
) {
6457
self.isEnabled = isEnabled
6558

66-
self.onStart = { actions.beginMoving() }
67-
self.onMove = { actions.moved(with: $0) }
68-
self.onDone = { actions.end() }
59+
self.actions = actions
6960

7061
self.element = element
7162
}
@@ -80,17 +71,15 @@ public struct ListReorderGesture : Element
8071

8172
public func backingViewDescription(bounds: CGRect, subtreeExtent: CGRect?) -> ViewDescription?
8273
{
83-
return ViewDescription(WrapperView.self) { config in
74+
return ViewDescription(View.self) { config in
8475
config.builder = {
85-
WrapperView(frame: bounds, wrapping: self)
76+
View(frame: bounds, wrapping: self)
8677
}
8778

8879
config.apply { view in
8980
view.recognizer.isEnabled = self.isEnabled
9081

91-
view.recognizer.onStart = self.onStart
92-
view.recognizer.onMove = self.onMove
93-
view.recognizer.onDone = self.onDone
82+
view.recognizer.apply(actions: self.actions)
9483
}
9584
}
9685
}
@@ -109,50 +98,13 @@ public extension Element
10998

11099
fileprivate extension ListReorderGesture
111100
{
112-
private final class GestureRecognizer : UIPanGestureRecognizer
113-
{
114-
public var onStart : OnStart? = nil
115-
public var onMove : OnMove? = nil
116-
public var onDone : OnDone? = nil
117-
118-
override init(target: Any?, action: Selector?)
119-
{
120-
super.init(target: target, action: action)
121-
122-
self.addTarget(self, action: #selector(updated))
123-
124-
self.minimumNumberOfTouches = 1
125-
self.maximumNumberOfTouches = 1
126-
}
127-
128-
@objc func updated()
129-
{
130-
switch self.state {
131-
case .possible: break
132-
case .began:
133-
let canStart = self.onStart?()
134-
135-
if canStart == false {
136-
self.state = .cancelled
137-
}
138-
case .changed:
139-
self.onMove?(self)
140-
141-
case .ended: self.onDone?()
142-
case .cancelled, .failed: self.onDone?()
143-
144-
@unknown default: listableFatal()
145-
}
146-
}
147-
}
148-
149-
private final class WrapperView : UIView
101+
private final class View : UIView
150102
{
151-
let recognizer : GestureRecognizer
103+
let recognizer : ItemReordering.GestureRecognizer
152104

153105
init(frame: CGRect, wrapping : ListReorderGesture)
154106
{
155-
self.recognizer = GestureRecognizer()
107+
self.recognizer = .init()
156108

157109
super.init(frame: frame)
158110

BlueprintUILists/Tests/ListTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ fileprivate struct TestItemContent : BlueprintItemContent {
9191

9292
var wasCalled : () -> ()
9393

94-
var identifier: Identifier<TestItemContent> {
95-
.init()
94+
var identifier: String {
95+
""
9696
}
9797

9898
func element(with info: ApplyItemContentInfo) -> Element {

CHANGELOG.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,22 @@
44

55
### Added
66

7+
- [Reordering between multiple sections is now supported](https://github.com/kyleve/Listable/pull/292).
8+
- [Introduced type safe access to `Section` content following reorder events](https://github.com/kyleve/Listable/pull/292). See `Section.filtered`.
9+
- [`ListStateObserver.onItemReordered` was added](https://github.com/kyleve/Listable/pull/292) to observe reorder events at a list-wide level.
10+
- [`ListLayout` was extended](https://github.com/kyleve/Listable/pull/292) to allow customization of in-progress moves. Note that `ListLayout` is not yet public.
11+
712
### Removed
813

914
### Changed
1015

16+
- [`Reordering` has been renamed to `ItemReordering`, and a new `SectionReordering` has been introduced](https://github.com/kyleve/Listable/pull/292). This allows finer-grained control over validating reorder events at both the item level and section level.
17+
- [`ListReorderGesture` and `ItemReordering.GestureRecognizer` have been heavily refactored](https://github.com/kyleve/Listable/pull/292) to reduce visibility of internal state concerns.
18+
- [`Item.identifier` has been renamed to `Item.anyIdentifier`](https://github.com/kyleve/Listable/pull/292). The new `Item.identifier` property is now a fully type safe identifier.
19+
- [`ReorderingActions` was refactored](https://github.com/kyleve/Listable/pull/292) to expose less public state and ease use in UIView-backed list elements.
20+
- [`Identifier<Represented>` is now `Identifier<Represented, Value>`; eg `Identifier<MyContent, UUID>`](https://github.com/kyleve/Listable/pull/292). This is done to support reacting to reordering events in a more type safe manner, and to make `Identifier` creation more type safe. This is a large breaking change.
21+
- [Changed how `identifier`s for `ItemContent` are represented](https://github.com/kyleve/Listable/pull/292). `ItemContent` now returns a an identifier of a specific `IdentifierType` (eg, `String`, `UUID`, etc), which is then assembled into an `Identifier` by the containing item. Additional APIs have been added for creating `Identifier`s in a more type safe manner. This is a large breaking change.
22+
1123
### Misc
1224

1325
# Past Releases
@@ -59,7 +71,7 @@ Example usage:
5971

6072
```
6173
listActions.scrolling.scrollToSection(
62-
with: Identifier<Section>(id),
74+
with: MyItem.identifier(with: id),
6375
sectionPosition: .top,
6476
scrollPosition: ScrollPosition(position: .centered)
6577
)

Demo/Demo.xcodeproj/project.pbxproj

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
0A02B2F42675E14600B3501B /* PaymentTypesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A02B2F32675E14600B3501B /* PaymentTypesViewController.swift */; };
1011
0A07119324BA798400CDF65D /* ListStateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A07119224BA798400CDF65D /* ListStateViewController.swift */; };
1112
0A0E070423870A5700DDD27D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 0A0E070323870A5700DDD27D /* README.md */; };
1213
0A49210424E5E11300D17038 /* AccordionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A49210324E5E11300D17038 /* AccordionViewController.swift */; };
@@ -50,6 +51,7 @@
5051

5152
/* Begin PBXFileReference section */
5253
06908B38E59ACD7502B5332F /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.release.xcconfig"; path = "Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig"; sourceTree = "<group>"; };
54+
0A02B2F32675E14600B3501B /* PaymentTypesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentTypesViewController.swift; sourceTree = "<group>"; };
5355
0A07119224BA798400CDF65D /* ListStateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStateViewController.swift; sourceTree = "<group>"; };
5456
0A0E070323870A5700DDD27D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
5557
0A49210324E5E11300D17038 /* AccordionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccordionViewController.swift; sourceTree = "<group>"; };
@@ -152,6 +154,7 @@
152154
0AD6767925423BE500A49315 /* MultiSelectViewController.swift */,
153155
0AC2A1952489F93E00779459 /* PagedViewController.swift */,
154156
0AA4D9B7248064A300CF95A5 /* ReorderingViewController.swift */,
157+
0A02B2F32675E14600B3501B /* PaymentTypesViewController.swift */,
155158
1F46FB6A26010F4900760961 /* RefreshControlOffsetAdjustmentViewController.swift */,
156159
0AA4D9AA248064A200CF95A5 /* ScrollViewEdgesPlaygroundViewController.swift */,
157160
0A793B5724E4B53500850139 /* ManualSelectionManagementViewController.swift */,
@@ -292,7 +295,7 @@
292295
isa = PBXProject;
293296
attributes = {
294297
LastSwiftUpdateCheck = 1110;
295-
LastUpgradeCheck = 1210;
298+
LastUpgradeCheck = 1220;
296299
ORGANIZATIONNAME = "Kyle Van Essen";
297300
TargetAttributes = {
298301
0AE8554D2390933100F2E245 = {
@@ -464,6 +467,7 @@
464467
0AA4D9C7248064A300CF95A5 /* CollectionViewDictionaryDemoViewController.swift in Sources */,
465468
0ADC3B3524907C80008DF2C0 /* XcodePreviewDemo.swift in Sources */,
466469
0AD6767A25423BE500A49315 /* MultiSelectViewController.swift in Sources */,
470+
0A02B2F42675E14600B3501B /* PaymentTypesViewController.swift in Sources */,
467471
0AA4D9C4248064A300CF95A5 /* ItemizationEditorViewController.swift in Sources */,
468472
0A5F5CCF257EC2D7003CCE2C /* LocalizedCollationViewController.swift in Sources */,
469473
0AA4D9BA248064A300CF95A5 /* FlowLayoutViewController.swift in Sources */,

Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1210"
3+
LastUpgradeVersion = "1220"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "ReorderControl.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"filename" : "[email protected]",
10+
"idiom" : "universal",
11+
"scale" : "2x"
12+
},
13+
{
14+
"filename" : "[email protected]",
15+
"idiom" : "universal",
16+
"scale" : "3x"
17+
}
18+
],
19+
"info" : {
20+
"author" : "xcode",
21+
"version" : 1
22+
}
23+
}
Loading
Loading
Loading

Demo/Sources/Demos/Demo Screens/AccordionViewController.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ fileprivate struct AccordionRow : BlueprintItemContent, Equatable
8080
{
8181
var text : String
8282

83-
var identifier: Identifier<AccordionRow> {
84-
.init(text)
83+
var identifier: String {
84+
self.text
8585
}
8686

8787
func element(with info: ApplyItemContentInfo) -> Element {

Demo/Sources/Demos/Demo Screens/AutoLayoutDemoViewController.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ struct AutoLayoutContent : ItemContent, Equatable {
3434
var header : String
3535
var detail : String
3636

37-
var identifier: Identifier<AutoLayoutContent> {
38-
.init(header + detail)
37+
var identifier: String {
38+
header + detail
3939
}
4040

4141
var defaultItemProperties: DefaultItemProperties<Self> {

Demo/Sources/Demos/Demo Screens/AutoScrollingViewController.swift

+8-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class AutoScrollingViewController : UIViewController
1818
{
1919
let list = ListView()
2020

21-
private var items: [BottomPinnedItem] = []
21+
private var items: [Item<BottomPinnedItem>] = []
2222

2323
override func loadView()
2424
{
@@ -41,11 +41,11 @@ final class AutoScrollingViewController : UIViewController
4141

4242
@objc private func addItem()
4343
{
44-
let last = items.last?.identifier
44+
let last = items.last
4545

46-
items.append(BottomPinnedItem(text: "Item \(items.count)"))
46+
items.append(Item(BottomPinnedItem(text: "Item \(items.count)")))
4747

48-
updateItems(autoScrollIfVisible: last)
48+
updateItems(autoScrollIfVisible: last?.identifier)
4949
}
5050

5151
@objc private func removeItem()
@@ -62,10 +62,9 @@ final class AutoScrollingViewController : UIViewController
6262
list.appearance = .demoAppearance
6363
list.layout = .demoLayout
6464

65-
let items = self.items.map { Item($0) }
66-
list += Section("items", items: items)
65+
list += Section("items", items: self.items)
6766

68-
if let last = items.last {
67+
if let last = self.items.last {
6968

7069
list.autoScrollAction = .scrollTo(
7170
.lastItem,
@@ -102,8 +101,8 @@ struct BottomPinnedItem : BlueprintItemContent, Equatable
102101
{
103102
var text : String
104103

105-
var identifier: Identifier<BottomPinnedItem> {
106-
return .init(self.text)
104+
var identifier: String {
105+
self.text
107106
}
108107

109108
func element(with info : ApplyItemContentInfo) -> Element

Demo/Sources/Demos/Demo Screens/CollectionViewAppearance.swift

+27-6
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,33 @@ struct DemoItem : BlueprintItemContent, Equatable, LocalizedCollatableItemConten
8585
{
8686
var text : String
8787

88-
var identifier: Identifier<DemoItem> {
89-
return .init(self.text)
88+
var identifier: String {
89+
return self.text
9090
}
9191

9292
typealias SwipeActionsView = DefaultSwipeActionsView
9393

9494
func element(with info : ApplyItemContentInfo) -> Element
9595
{
96-
Label(text: self.text) {
97-
$0.font = .systemFont(ofSize: 16.0, weight: .medium)
98-
$0.color = info.state.isActive ? .white : .darkGray
96+
Row { row in
97+
row.verticalAlignment = .center
98+
99+
row.add(child: Label(text: self.text) {
100+
$0.font = .systemFont(ofSize: 16.0, weight: .medium)
101+
$0.color = info.state.isActive ? .white : .darkGray
102+
})
103+
104+
row.addFlexible(child: Spacer(width: 1.0))
105+
106+
if info.isReorderable {
107+
row.addFixed(
108+
child: Image(
109+
image: UIImage(named: "ReorderControl"),
110+
contentMode: .center
111+
)
112+
.listReorderGesture(with: info.reorderingActions)
113+
)
114+
}
99115
}
100116
.inset(horizontal: 15.0, vertical: 10.0)
101117
.accessibility(label: self.text, traits: [.button])
@@ -106,7 +122,12 @@ struct DemoItem : BlueprintItemContent, Equatable, LocalizedCollatableItemConten
106122
Box(
107123
backgroundColor: .white,
108124
cornerStyle: .rounded(radius: 8.0),
109-
shadowStyle: .simple(radius: 2.0, opacity: 0.15, offset: .init(width: 0.0, height: 1.0), color: .black)
125+
shadowStyle: .simple(
126+
radius: info.state.isReordering ? 5.0 : 2.0,
127+
opacity: info.state.isReordering ? 0.5 : 0.15,
128+
offset: .init(width: 0.0, height: 1.0),
129+
color: .black
130+
)
110131
)
111132
}
112133

0 commit comments

Comments
 (0)