diff --git a/Demo/Sources/Demos/Demo Screens/ScrollViewEdgesPlaygroundViewController.swift b/Demo/Sources/Demos/Demo Screens/ScrollViewEdgesPlaygroundViewController.swift index 28aa8363a..2a99dd35c 100644 --- a/Demo/Sources/Demos/Demo Screens/ScrollViewEdgesPlaygroundViewController.swift +++ b/Demo/Sources/Demos/Demo Screens/ScrollViewEdgesPlaygroundViewController.swift @@ -7,7 +7,7 @@ // import UIKit -@testable import Listable +import Listable final class ScrollViewEdgesPlaygroundViewController : UIViewController, UIScrollViewDelegate diff --git a/Demo/Sources/Demos/DemosRootViewController.swift b/Demo/Sources/Demos/DemosRootViewController.swift index b9a69ef70..6c6bf9d14 100644 --- a/Demo/Sources/Demos/DemosRootViewController.swift +++ b/Demo/Sources/Demos/DemosRootViewController.swift @@ -143,6 +143,14 @@ public final class DemosRootViewController : UIViewController DemoItem(text: "Tappable Row"), selectionStyle: .tappable ) + + section += Item( + DemoItem(text: "Tappable Row (Slow Is Selected)"), + selectionStyle: .tappable, + onSelect: { _ in + Thread.sleep(forTimeInterval: 0.5) + } + ) } list += Section(identifier: "collection-view") { section in diff --git a/Listable/Sources/Debugging and Logging/SignpostLogger.swift b/Listable/Sources/Debugging and Logging/SignpostLogger.swift index 0366c042c..afa1afbbb 100644 --- a/Listable/Sources/Debugging and Logging/SignpostLogger.swift +++ b/Listable/Sources/Debugging and Logging/SignpostLogger.swift @@ -19,6 +19,11 @@ extension OSLog { subsystem: "com.kve.Listable", category: "ListView ScrollView" ) + + static let listInteraction = OSLog( + subsystem: "com.kve.Listable", + category: "ListView Interaction" + ) } diff --git a/Listable/Sources/Identifier.swift b/Listable/Sources/Identifier.swift index 4b79283d4..d72e07f92 100644 --- a/Listable/Sources/Identifier.swift +++ b/Listable/Sources/Identifier.swift @@ -8,7 +8,7 @@ import Foundation -public final class AnyIdentifier : Hashable +public final class AnyIdentifier : Hashable, CustomDebugStringConvertible { private let value : AnyHashable @@ -36,13 +36,19 @@ public final class AnyIdentifier : Hashable { hasher.combine(self.hash) } + + // MARK: CustomDebugStringConvertible + + public var debugDescription: String { + self.value.identifierContentString + } } -public final class Identifier : Hashable +public final class Identifier : Hashable, CustomDebugStringConvertible { private let type : ObjectIdentifier - private let value : AnyHashable? + private let value : AnyHashable private let hash : Int @@ -82,4 +88,24 @@ public final class Identifier : Hashable { hasher.combine(self.hash) } + + // MARK: CustomDebugStringConvertible + + public var debugDescription: String { + "Identifier<\(String(describing: Represented.self))>: \(self.value.identifierContentString)" + } +} + + +fileprivate extension AnyHashable +{ + var identifierContentString : String { + if let base = self.base as? CustomDebugStringConvertible { + return base.debugDescription + } else if let base = self.base as? CustomStringConvertible { + return base.description + } else { + return self.debugDescription + } + } } diff --git a/Listable/Sources/Internal/Presentation State/PresentationState.ItemState.swift b/Listable/Sources/Internal/Presentation State/PresentationState.ItemState.swift index afdd3fe2f..0526add80 100644 --- a/Listable/Sources/Internal/Presentation State/PresentationState.ItemState.swift +++ b/Listable/Sources/Internal/Presentation State/PresentationState.ItemState.swift @@ -277,10 +277,27 @@ extension PresentationState { self.storage.state.isSelected = isSelected - if isSelected { - self.model.onSelect?(self.model.content) - } else { - self.model.onDeselect?(self.model.content) + /// Schedule the caller-provided callbacks to happen after one runloop. Why? + /// + /// Because this method is called from within `UICollectionViewDelegate` callbacks, + /// This delay gives the `UICollectionView` time to schedule any necessary animations + /// for changes to the highlight and selection state – otherwise, these animations get + /// stuck behind the call to the `onSelect` or `onDeselect` blocks, which creates the appearance + /// of a laggy UI if these callbacks are slow. + DispatchQueue.main.async { + if isSelected { + if let onSelect = self.model.onSelect { + SignpostLogger.log(log: .listInteraction, name: "Item onSelect", for: self.model) { + onSelect(self.model.content) + } + } + } else { + if let onDeselect = self.model.onDeselect { + SignpostLogger.log(log: .listInteraction, name: "Item onDeselect", for: self.model) { + onDeselect(self.model.content) + } + } + } } } diff --git a/Listable/Sources/Item/Item.swift b/Listable/Sources/Item/Item.swift index 35b57b505..99675537a 100644 --- a/Listable/Sources/Item/Item.swift +++ b/Listable/Sources/Item/Item.swift @@ -210,7 +210,7 @@ extension Item : SignpostLoggable var signpostInfo : SignpostLoggingInfo { SignpostLoggingInfo( identifier: self.debuggingIdentifier, - instanceIdentifier: nil + instanceIdentifier: self.identifier.debugDescription ) } } diff --git a/Listable/Sources/ListScrollPositionInfo.swift b/Listable/Sources/ListScrollPositionInfo.swift index 27e3bb6b1..376e0e947 100644 --- a/Listable/Sources/ListScrollPositionInfo.swift +++ b/Listable/Sources/ListScrollPositionInfo.swift @@ -89,7 +89,7 @@ public struct ListScrollPositionInfo : Equatable { private let scrollViewState : ScrollViewState /// Creates a `ListScrollPositionInfo` for the provided scroll view. - init( + public init( scrollView : UIScrollView, visibleItems : Set, isFirstItemVisible : Bool, diff --git a/Listable/Tests/IdentifierTests.swift b/Listable/Tests/IdentifierTests.swift index 28b3003b6..6ae948bfc 100644 --- a/Listable/Tests/IdentifierTests.swift +++ b/Listable/Tests/IdentifierTests.swift @@ -6,8 +6,41 @@ // import XCTest +import Listable -class IdentifierTests: XCTestCase + +class AnyIdentifierTests: XCTestCase { + func test_debugDescription() + { + XCTAssertEqual( + Identifier("The Value").toAny.debugDescription, + "Identifier: \"The Value\"" + ) + + XCTAssertEqual( + Identifier(123).toAny.debugDescription, + "Identifier: 123" + ) + } +} + +class IdentifierTests: XCTestCase +{ + func test_debugDescription() + { + XCTAssertEqual( + Identifier("The Value").debugDescription, + "Identifier: \"The Value\"" + ) + + XCTAssertEqual( + Identifier(123).debugDescription, + "Identifier: 123" + ) + } } + + +fileprivate struct TestingType { } diff --git a/Podfile.lock b/Podfile.lock index da33371ad..a11aa962b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - BlueprintLists (0.1.1): + - BlueprintLists (0.6.0): - BlueprintUI - Listable - - BlueprintLists/Tests (0.1.1): + - BlueprintLists/Tests (0.6.0): - BlueprintUI - Listable - BlueprintUI (0.11.0) - BlueprintUICommonControls (0.11.0): - BlueprintUI - EnglishDictionary (1.0.0.LOCAL) - - Listable (1.0.0.LOCAL) - - Listable/Tests (1.0.0.LOCAL): + - Listable (0.6.0) + - Listable/Tests (0.6.0): - EnglishDictionary - Snapshot - Snapshot (1.0.0.LOCAL) @@ -43,11 +43,11 @@ EXTERNAL SOURCES: :path: Internal Pods/Snapshot/Snapshot.podspec SPEC CHECKSUMS: - BlueprintLists: 519830595f2d2109299b599e46729cd22a0128ea + BlueprintLists: 7dde9b8cf1139be5aa5e84fb05f01a56df498c12 BlueprintUI: bc5bc9913d897c10dc44c544747afc2a63b26744 BlueprintUICommonControls: d982c07e38112d9d2d5f5d315c0edc54db0d349e EnglishDictionary: f03968b9382ddc5c8dd63535efbf783c6cd45f1c - Listable: c4273b65906a0624b9aefce781743095ab8971f9 + Listable: 836bc4b9756a96aa47e29a2e59e57386666a3288 Snapshot: cda3414db426919d09f775434b36289c8e864183 PODFILE CHECKSUM: ede5b0b1b0dfe04b49195609ea6bccb6b577e01f