Skip to content

Commit 95dfcbc

Browse files
committed
Implement .onDragEnd for scrollable containers + CollectionView improvements
1 parent 289c34a commit 95dfcbc

File tree

8 files changed

+78
-174
lines changed

8 files changed

+78
-174
lines changed

Sources/Intermodular/Helpers/UIKit/UIHostingCollectionViewCell.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ extension UIHostingCollectionViewCell {
380380

381381
contentHostingController?.view.setNeedsDisplay()
382382
contentHostingController?.view.setNeedsLayout()
383+
contentHostingController?.view.layoutIfNeeded()
383384

384385
lastInvalidationContext = context
385386
}
@@ -402,6 +403,14 @@ extension UIHostingCollectionViewCell {
402403
func invalidateLayout(with context: CellProxy.InvalidationContext) {
403404
base?.invalidateContent(with: context)
404405
}
406+
407+
func select() {
408+
base?.isSelected = true
409+
}
410+
411+
func deselect() {
412+
base?.isSelected = false
413+
}
405414
}
406415
}
407416

Sources/Intermodular/Helpers/UIKit/UIHostingCollectionViewController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,10 @@ final class UIHostingCollectionViewController<
481481
contentOffset.wrappedValue = collectionView.contentOffset
482482
}
483483
}
484+
485+
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
486+
_scrollViewConfiguration.onDragEnd?()
487+
}
484488
}
485489

486490
extension UIHostingCollectionViewController {

Sources/Intermodular/Helpers/UIKit/UIHostingScrollView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ open class UIHostingScrollView<Content: View>: UIScrollView, _opaque_UIHostingSc
250250
targetContentOffset.pointee = contentOffset(forPageIndex: targetIndex)
251251
}
252252
}
253+
254+
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
255+
configuration.onDragEnd?()
256+
}
253257
}
254258

255259
// MARK: - Auxiliary Implementation -

Sources/Intermodular/Helpers/UIKit/UIHostingTableViewCell.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,15 @@ extension UIHostingTableViewCell {
120120
fatalError("unimplemented")
121121
}
122122

123-
func performWithAnimation(_ action: () -> ()) {
123+
func select() {
124+
base?.isSelected = true
125+
}
126+
127+
func deselect() {
128+
base?.isSelected = false
129+
}
130+
131+
private func performWithAnimation(_ action: () -> ()) {
124132
base?.tableViewController.tableView.beginUpdates()
125133
action()
126134
base?.tableViewController.tableView.endUpdates()

Sources/Intramodular/Collection View & List/CollectionView.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@ public struct CollectionView: View {
1616
private var _collectionViewConfiguration = _CollectionViewConfiguration()
1717
private var _dynamicViewContentTraitValues = _DynamicViewContentTraitValues()
1818
private var _scrollViewConfiguration = CocoaScrollViewConfiguration<AnyView>()
19-
19+
private var collectionViewLayout: CollectionViewLayout?
20+
2021
public var body: some View {
2122
internalBody
2223
.environment(\._collectionViewConfiguration, _collectionViewConfiguration)
2324
.environment(\._dynamicViewContentTraitValues, _dynamicViewContentTraitValues)
2425
.environment(\._scrollViewConfiguration, _scrollViewConfiguration)
26+
.transformEnvironment(\.collectionViewLayout, transform: { layout in
27+
layout = collectionViewLayout ?? layout
28+
})
2529
}
2630

2731
fileprivate init(internalBody: AnyView) {
@@ -284,6 +288,10 @@ extension CollectionView {
284288
// MARK: - API -
285289

286290
extension CollectionView {
291+
public func collectionViewLayout(_ layout: CollectionViewLayout) -> CollectionView {
292+
then({ $0.collectionViewLayout = layout })
293+
}
294+
287295
public func updateOnChange<T: Hashable>(of value: T) -> Self {
288296
then({ $0._collectionViewConfiguration.dataSourceUpdateToken = value })
289297
}
@@ -402,6 +410,10 @@ extension CollectionView {
402410
then({ $0._scrollViewConfiguration.onOffsetChange = body })
403411
}
404412

413+
public func onDragEnd(perform action: @escaping () -> Void) -> Self {
414+
then({ $0._scrollViewConfiguration.onDragEnd = action })
415+
}
416+
405417
/// Sets whether the collection view animates differences in the data source.
406418
public func disableAnimatingDifferences(_ disableAnimatingDifferences: Bool) -> Self {
407419
then({ $0._collectionViewConfiguration.disableAnimatingDifferences = disableAnimatingDifferences })

Sources/Intramodular/Collection View & List/ListRowManager.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import SwiftUI
77

88
protocol _CellProxyBase {
99
var globalFrame: CGRect { get }
10-
10+
1111
func invalidateLayout(with context: CellProxy.InvalidationContext)
12+
func select()
13+
func deselect()
1214
}
1315

1416
public struct CellProxy {
@@ -25,6 +27,14 @@ public struct CellProxy {
2527
public func invalidateLayout(with context: InvalidationContext = .init()) {
2628
base?.invalidateLayout(with: context)
2729
}
30+
31+
public func select() {
32+
base?.select()
33+
}
34+
35+
public func deselect() {
36+
base?.deselect()
37+
}
2838
}
2939

3040
public struct CellReader<Content: View>: View {

Sources/Intramodular/Scrolling/CocoaScrollView.Configuration.swift

Lines changed: 24 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -9,176 +9,33 @@ import SwiftUI
99

1010
/// The properties of a `CocoaScrollView` instance.
1111
public struct CocoaScrollViewConfiguration<Content: View> {
12-
var hasChanged: Bool = true
13-
14-
// MARK: General
15-
16-
var initialContentAlignment: Alignment? {
17-
didSet {
18-
if oldValue != initialContentAlignment {
19-
hasChanged = true
20-
}
21-
}
22-
}
23-
24-
var axes: Axis.Set = [.vertical] {
25-
didSet {
26-
if oldValue != axes {
27-
hasChanged = true
28-
}
29-
}
30-
}
31-
32-
var showsVerticalScrollIndicator: Bool = true {
33-
didSet {
34-
if oldValue != showsVerticalScrollIndicator {
35-
hasChanged = true
36-
}
37-
}
38-
}
39-
40-
var showsHorizontalScrollIndicator: Bool = true {
41-
didSet {
42-
if oldValue != showsHorizontalScrollIndicator {
43-
hasChanged = true
44-
}
45-
}
46-
}
47-
48-
var scrollIndicatorInsets: (horizontal: EdgeInsets, vertical: EdgeInsets) = (.zero, .zero) {
49-
didSet {
50-
if oldValue != scrollIndicatorInsets {
51-
hasChanged = true
52-
}
53-
}
54-
}
12+
var initialContentAlignment: Alignment?
13+
var axes: Axis.Set = [.vertical]
14+
var showsVerticalScrollIndicator: Bool = true
15+
var showsHorizontalScrollIndicator: Bool = true
16+
var scrollIndicatorInsets: (horizontal: EdgeInsets, vertical: EdgeInsets) = (.zero, .zero)
17+
var decelerationRate: UIScrollView.DecelerationRate = .normal
18+
var alwaysBounceVertical: Bool? = nil
19+
var alwaysBounceHorizontal: Bool? = nil
20+
var isDirectionalLockEnabled: Bool = false
21+
var isPagingEnabled: Bool = false
22+
var isScrollEnabled: Bool = true
23+
24+
var onOffsetChange: ((ScrollView<Content>.ContentOffset) -> ())?
25+
var onDragEnd: (() -> Void)?
26+
var contentOffset: Binding<CGPoint>? = nil
27+
28+
var contentInset: EdgeInsets = .zero
29+
var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior?
30+
var contentOffsetBehavior: ScrollContentOffsetBehavior = []
31+
32+
var onRefresh: (() -> Void)?
33+
var isRefreshing: Bool?
34+
var refreshControlTintColor: UIColor?
5535

56-
var decelerationRate: UIScrollView.DecelerationRate = .normal {
57-
didSet {
58-
if oldValue != decelerationRate {
59-
hasChanged = true
60-
}
61-
}
62-
}
6336

64-
var alwaysBounceVertical: Bool? = nil {
65-
didSet {
66-
if oldValue != alwaysBounceVertical {
67-
hasChanged = true
68-
}
69-
}
70-
}
71-
72-
var alwaysBounceHorizontal: Bool? = nil {
73-
didSet {
74-
if oldValue != alwaysBounceHorizontal {
75-
hasChanged = true
76-
}
77-
}
78-
}
79-
80-
var isDirectionalLockEnabled: Bool = false {
81-
didSet {
82-
if oldValue != isDirectionalLockEnabled {
83-
hasChanged = true
84-
}
85-
}
86-
}
87-
88-
var isPagingEnabled: Bool = false {
89-
didSet {
90-
if oldValue != isPagingEnabled {
91-
hasChanged = true
92-
}
93-
}
94-
}
95-
96-
var isScrollEnabled: Bool = true {
97-
didSet {
98-
if oldValue != isScrollEnabled {
99-
hasChanged = true
100-
}
101-
}
102-
}
103-
104-
var onOffsetChange: ((ScrollView<Content>.ContentOffset) -> ())? = nil {
105-
didSet {
106-
if (oldValue == nil) != (onOffsetChange == nil) {
107-
hasChanged = true
108-
}
109-
}
110-
}
111-
112-
// MARK: Content
113-
114-
var contentOffset: Binding<CGPoint>? = nil {
115-
didSet {
116-
if (oldValue == nil) != (contentOffset == nil) {
117-
hasChanged = true
118-
}
119-
}
120-
}
121-
122-
var contentInset: EdgeInsets = .zero {
123-
didSet {
124-
if oldValue != contentInset {
125-
hasChanged = true
126-
}
127-
}
128-
}
129-
130-
var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior? {
131-
didSet {
132-
if oldValue != contentInsetAdjustmentBehavior {
133-
hasChanged = true
134-
}
135-
}
136-
}
137-
138-
var contentOffsetBehavior: ScrollContentOffsetBehavior = [] {
139-
didSet {
140-
if oldValue != contentOffsetBehavior {
141-
hasChanged = true
142-
}
143-
}
144-
}
145-
146-
// MARK: Refresh
147-
148-
var onRefresh: (() -> Void)? {
149-
didSet {
150-
if (oldValue == nil) != (onRefresh == nil) {
151-
hasChanged = true
152-
}
153-
}
154-
}
155-
156-
var isRefreshing: Bool? {
157-
didSet {
158-
if oldValue != isRefreshing {
159-
hasChanged = true
160-
}
161-
}
162-
}
163-
164-
var refreshControlTintColor: UIColor? {
165-
didSet {
166-
if oldValue != refreshControlTintColor {
167-
hasChanged = true
168-
}
169-
}
170-
}
171-
172-
// MARK: Keyboard
173-
17437
@available(tvOS, unavailable)
175-
var keyboardDismissMode: UIScrollView.KeyboardDismissMode = .none {
176-
didSet {
177-
if oldValue != keyboardDismissMode {
178-
hasChanged = true
179-
}
180-
}
181-
}
38+
var keyboardDismissMode: UIScrollView.KeyboardDismissMode = .none
18239
}
18340

18441
extension CocoaScrollViewConfiguration {
@@ -229,10 +86,6 @@ extension UIScrollView {
22986
func configure<Content: View>(
23087
with configuration: CocoaScrollViewConfiguration<Content>
23188
) {
232-
guard configuration.hasChanged else {
233-
return
234-
}
235-
23689
if let alwaysBounceVertical = configuration.alwaysBounceVertical {
23790
assignIfNotEqual(alwaysBounceVertical, to: &self.alwaysBounceVertical)
23891
}

Sources/Intramodular/Scrolling/CocoaScrollView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ extension CocoaScrollView {
8787
then({ $0.configuration.onOffsetChange = body })
8888
}
8989

90+
public func onDragEnd(perform action: @escaping () -> Void) -> Self {
91+
then({ $0.configuration.onDragEnd = action })
92+
}
93+
9094
public func contentOffset(_ contentOffset: Binding<CGPoint>) -> Self {
9195
then({ $0.configuration.contentOffset = contentOffset })
9296
}

0 commit comments

Comments
 (0)