├── .gitignore ├── .spi.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── florianzand.xcuserdatad │ │ └── IDEFindNavigatorScopes.plist │ └── xcuserdata │ └── florianzand.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── florianzand.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Example.entitlements │ ├── MainViewController.swift │ ├── Model │ ├── CollectionItem.swift │ ├── OutlineItem.swift │ ├── Section.swift │ └── SidebarItem.swift │ ├── OutlineSidebarViewController.swift │ ├── Sample Images │ ├── About Blank.png │ ├── Berghain.png │ ├── Cosmojelly.png │ ├── Dystopian City.png │ ├── Fireworker Monkey.png │ ├── Majestic Mouser.png │ ├── Neil Catstrong.png │ ├── Oxi.png │ ├── Plasmawhale.png │ ├── Tresor.png │ ├── Underground.png │ └── Vapor Cat.png │ ├── SplitViewController.swift │ └── TableSidebarViewController.swift ├── LICENSE ├── Package.swift ├── README.md └── Sources └── AdvancedCollectionTableView ├── Configuration ├── Configurations │ ├── NSItemContentConfiguration │ │ ├── NSItemContentConfiguration+Badge.swift │ │ ├── NSItemContentConfiguration+Content.swift │ │ ├── NSItemContentConfiguration+Image.swift │ │ ├── NSItemContentConfiguration.swift │ │ ├── NSItemContentView+Badge.swift │ │ ├── NSItemContentView+Content.swift │ │ ├── NSItemContentView.swift │ │ └── unused │ │ │ ├── NSItemContentConfiguration+Transition.swift │ │ │ ├── NSItemContentView+Badge_old.swift │ │ │ └── NSItemContentViewSwiftUI.swift │ ├── NSListContentConfiguration │ │ ├── Accessory │ │ │ ├── Accessories │ │ │ │ ├── TextAccessory.swift │ │ │ │ └── TextStackAccessory.swift │ │ │ ├── NSListContentView+Accessory+Image.swift │ │ │ ├── NSListContentView+Accessory.swift │ │ │ └── NSListContentView+AccessoryView.swift │ │ ├── NSListContentConfiguration+Badge.swift │ │ ├── NSListContentConfiguration+Image.swift │ │ ├── NSListContentConfiguration.swift │ │ ├── NSListContentView+Badge.swift │ │ ├── NSListContentView+ImageView.swift │ │ └── NSListContentView.swift │ └── Shared │ │ ├── ListItemTextField.swift │ │ ├── Protocols.swift │ │ ├── TextProperties.swift │ │ └── TextStackView.swift ├── Extensions │ ├── NSCollectionView+ │ │ ├── NSCollectionView+.swift │ │ └── NSCollectionViewItem+.swift │ ├── NSTableView+ │ │ ├── NSTableCellVew+.swift │ │ ├── NSTableRowView+.swift │ │ └── NSTableView+.swift │ └── Shared │ │ └── TableCollectionObserverView.swift └── States │ ├── ConfigurationState.swift │ ├── NSItemConfigurationState.swift │ └── NSListConfigurationState.swift ├── DiffableDataSource ├── NSCollectionView │ ├── CollectionViewDiffableDataSource+Delegate.swift │ └── CollectionViewDiffableDataSource.swift ├── NSOutlineView │ ├── unused │ │ ├── CellOutlineVItem.swift │ │ └── OutlineViewDiffableDataSourceSnapshot+OutlineItem.swift │ ├── OutlineViewDiffableDataSource+Delegate.swift │ ├── OutlineViewDiffableDataSource.swift │ └── Snapshot │ │ ├── OutlineChangeInstruction.swift │ │ ├── OutlineViewDiffableDataSourceSnapshot+Node.swift │ │ ├── OutlineViewDiffableDataSourceSnapshot.swift │ │ └── OutlineViewDiffableDataSourceTransaction.swift ├── NSTableView │ ├── TableViewDiffableDataSource+Delegate.swift │ └── TableViewDiffableDataSource.swift └── Shared │ ├── DiffableDataSourceTransaction.swift │ ├── EmptyView.swift │ └── QuicklookPreviewItem.swift ├── Documentation └── AdvancedCollectionTableView.docc │ ├── AdvancedCollectionTableView.md │ ├── Docs │ ├── Configurating Collection Items.md │ └── Configurating Table Cells.md │ ├── Extensions │ ├── AppKit Extensions │ │ ├── NSCollecionView │ │ │ ├── NSCollectionView+.md │ │ │ ├── NSCollectionViewDiffableDataSource+.md │ │ │ ├── NSCollectionViewDiffableDataSource+DeletingHandlers.md │ │ │ ├── NSCollectionViewItem+.md │ │ │ └── NSCollectionViewSupplementaryRegistration.md │ │ └── NSTableView │ │ │ ├── NSTableCellView+.md │ │ │ ├── NSTableRowView+.md │ │ │ ├── NSTableView+.md │ │ │ ├── NSTableViewCellRegistration.md │ │ │ ├── NSTableViewDiffableDataSource+.md │ │ │ └── NSTableViewDiffableDataSource+DeletingHandlers.md │ ├── Configuration │ │ ├── Configurations │ │ │ ├── ItemContentConfiguration │ │ │ │ ├── NSItemContentConfiguration+Badge.md │ │ │ │ ├── NSItemContentConfiguration+Content.md │ │ │ │ ├── NSItemContentConfiguration+ImageProperties.md │ │ │ │ ├── NSItemContentConfiguration.md │ │ │ │ └── NSItemContentView.md │ │ │ ├── ListContentConfiguration │ │ │ │ ├── NSListContentConfiguration+Badge.md │ │ │ │ ├── NSListContentConfiguration+Image.md │ │ │ │ ├── NSListContentConfiguration.md │ │ │ │ └── NSListContentView.md │ │ │ └── Shared │ │ │ │ └── TextProperties.md │ │ └── States │ │ │ ├── NSItemConfigurationState.md │ │ │ └── NSListConfigurationState.md │ ├── DiffableDataSource │ │ ├── CollectionViewDiffableData │ │ │ ├── CollectionViewDiffableDataSource+DeletingHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+DisplayHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+DragDropHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+HighlightHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+HoverHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+PrefetchHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+ReorderingHandlers.md │ │ │ ├── CollectionViewDiffableDataSource+SelectionHandlers.md │ │ │ ├── CollectionViewDiffableDataSource-Protocol-Implementations.md │ │ │ └── CollectionViewDiffableDataSource.md │ │ ├── OutlineViewDiffableDataSource │ │ │ ├── OutlineNode.md │ │ │ ├── OutlineViewDiffableDataSource+ColumnHandlers.md │ │ │ ├── OutlineViewDiffableDataSource+DeletingHandlers.md │ │ │ ├── OutlineViewDiffableDataSource+ExpansionHandlers.md │ │ │ ├── OutlineViewDiffableDataSource+HoverHandlers.md │ │ │ ├── OutlineViewDiffableDataSource+ReorderingHandlers.md │ │ │ ├── OutlineViewDiffableDataSource+SelectionHandlers.md │ │ │ ├── OutlineViewDiffableDataSource-Protocol-Implementations.md │ │ │ ├── OutlineViewDiffableDataSource.md │ │ │ ├── OutlineViewDiffableDataSourceSnapshot.md │ │ │ └── OutlineViewDiffableDataSourceTransaction.md │ │ ├── Shared │ │ │ ├── NSDiffableDataSourceSnapshotApplyOption.md │ │ │ └── NSDiffableDataSourceTransaction.md │ │ └── TableViewDiffableDataSource │ │ │ ├── TableViewDiffableDataSource+ColumnHandlers.md │ │ │ ├── TableViewDiffableDataSource+DeletingHandlers.md │ │ │ ├── TableViewDiffableDataSource+DragDropHandlers.md │ │ │ ├── TableViewDiffableDataSource+HoverHandlers.md │ │ │ ├── TableViewDiffableDataSource+ReorderingHandlers.md │ │ │ ├── TableViewDiffableDataSource+SelectionHandlers.md │ │ │ ├── TableViewDiffableDataSource-Protocol Implementations.md │ │ │ └── TableViewDiffableDataSource.md │ └── Registration │ │ ├── NSCollectionView │ │ ├── ItemRegistration.md │ │ └── SupplementaryRegistration.md │ │ └── NSTableView │ │ ├── CellRegistration.md │ │ ├── NSTableSectionHeaderView.md │ │ └── RowViewRegistration.md │ └── Resources │ ├── Extension.svg │ ├── NSItemContentConfiguration.png │ └── NSListContentConfiguration.png ├── Extensions ├── NSCollectionView │ ├── NSCollectionView+DragSessionMove.swift │ ├── NSCollectionView+ReconfigureItem.swift │ ├── NSCollectionView+Register.swift │ ├── NSCollectionViewDiffableDataSource+.swift │ ├── NSCollectionViewDiffableDataSource+Apply.swift │ ├── NSCollectionViewDiffableDataSource+Delete.swift │ ├── NSCollectionViewDiffableDataSource+Registration.swift │ └── unused │ │ └── NSCollectionView+SelfSizing.swift ├── NSTableView │ ├── NSTableView+DragSessionMove.swift │ ├── NSTableView+ReconfigureRows.swift │ ├── NSTableView+Register.swift │ ├── NSTableViewDiffableDataSource+.swift │ ├── NSTableViewDiffableDataSource+Apply.swift │ ├── NSTableViewDiffableDataSource+Delete.swift │ └── NSTableViewDiffableDataSource+Registration.swift └── Shared │ ├── NSDiffableDataSourceSnapshot+.swift │ ├── NSDiffableDataSourceSnapshot+ApplyOption.swift │ ├── NSPasteboardItem+.swift │ └── NSView+Transform.swift └── Registrations ├── NSCollectionView ├── ItemRegistration.swift └── SupplementaryRegistration.swift └── NSTableView ├── CellRegistration.swift └── RowRegistration.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.resolved 3 | **/.DS_Store 4 | .swiftpm/xcode/xcuserdata/florianzand.xcuserdatad/xcschemes/xcschememanagement.plist 5 | *.xcuserstate 6 | *.resolved 7 | *.resolved 8 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [AdvancedCollectionTableView] 5 | custom_documentation_parameters: [--include-extended-types] 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/florianzand.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/florianzand.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/florianzand.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AdvancedCollectionTableView-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | AdvancedCollectionTableView.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | AdvancedCollectionTableViewInterpose-Package.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 0 21 | 22 | AdvancedCollectionTableViewInterpose.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 1 26 | 27 | AdvancedCollectionTableViewObjC.xcscheme_^#shared#^_ 28 | 29 | orderHint 30 | 2 31 | 32 | 33 | SuppressBuildableAutocreation 34 | 35 | AdvancedCollectionTableView 36 | 37 | primary 38 | 39 | 40 | AdvancedCollectionTableViewObjC 41 | 42 | primary 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "9a3fc9d4e8c660ad4f02d3fe7cb14be2d4a8737c02d84fc43a36ece1c84e8b31", 3 | "pins" : [ 4 | { 5 | "identity" : "fzquicklook", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/flocked/FZQuicklook.git", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "835a344797c7b7bf4dbf98d77dd60a7cb6d4c7cf" 11 | } 12 | }, 13 | { 14 | "identity" : "fzswiftutils", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/flocked/FZSwiftUtils.git", 17 | "state" : { 18 | "branch" : "main", 19 | "revision" : "5af861b31f0128f4edf2bb4ae5e8868345631f71" 20 | } 21 | }, 22 | { 23 | "identity" : "fzuikit", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/flocked/FZUIKit.git", 26 | "state" : { 27 | "branch" : "main", 28 | "revision" : "2524cf36f5a2174a3ae51ce413b940a7d040961c" 29 | } 30 | } 31 | ], 32 | "version" : 3 33 | } 34 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/florianzand.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/florianzand.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CollectionViewSidebarTemplate.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | CollectionViewTemplate.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | Example.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 0 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CollectionViewTemplate 4 | // 5 | // Created by Florian Zand on 19.01.23. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | func applicationDidFinishLaunching(_: Notification) { 13 | // Insert code here to initialize your application 14 | } 15 | 16 | func applicationWillTerminate(_: Notification) { 17 | // Insert code here to tear down your application 18 | } 19 | 20 | func applicationSupportsSecureRestorableState(_: NSApplication) -> Bool { 21 | true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Example.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example/Example/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // 4 | // 5 | // Created by Florian Zand on 19.01.23. 6 | // 7 | 8 | import AppKit 9 | import AdvancedCollectionTableView 10 | 11 | class MainViewController: NSViewController { 12 | typealias DataSource = CollectionViewDiffableDataSource 13 | typealias ItemRegistration = NSCollectionView.ItemRegistration 14 | 15 | @IBOutlet var collectionView: NSCollectionView! 16 | 17 | var galleryItems = GalleryItem.sampleItems 18 | 19 | lazy var dataSource = DataSource(collectionView: collectionView, itemRegistration: itemRegistration) 20 | 21 | let itemRegistration = ItemRegistration() { collectionViewItem, _, galleryItem in 22 | /// Configurate the item 23 | var configuration = NSItemContentConfiguration() 24 | configuration.text = galleryItem.title 25 | configuration.secondaryText = galleryItem.detail 26 | configuration.image = NSImage(named: galleryItem.title) 27 | configuration.contentProperties.shadow = .black(opacity: 0.5, radius: 5.0) 28 | if let badgeText = galleryItem.badgeText { 29 | configuration.badges = [.text(badgeText, color: galleryItem.badgeColor, type: .attachment)] 30 | } 31 | 32 | if galleryItem.isFavorite { 33 | configuration.badges = [.text("􀋃", color: .systemRed, shape: .circle, type: .attachment)] 34 | } 35 | 36 | /// Apply the configuration 37 | collectionViewItem.contentConfiguration = configuration 38 | 39 | /// Gets called when the item state changes (on selection, mouse hover, etc.) 40 | collectionViewItem.configurationUpdateHandler = { item, state in 41 | /// Updates the configuration based on whether the mouse is hovering the item 42 | configuration.contentProperties.scaleTransform = state.isHovered ? 1.03 : 1.0 43 | configuration.overlayView = state.isHovered ? NSView(color: .white, opacity: 0.25) : nil 44 | 45 | /// Apply the updated configuration animated. 46 | item.contentConfiguration = configuration 47 | } 48 | } 49 | 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | 53 | collectionView.collectionViewLayout = .grid(columns: 3) 54 | collectionView.dataSource = dataSource 55 | 56 | /// Enables deleting selected items via backspace key. 57 | dataSource.deletingHandlers.canDelete = { selectedItems in return selectedItems } 58 | dataSource.deletingHandlers.didDelete = { deletedItems, _ in 59 | self.galleryItems.remove(deletedItems) 60 | } 61 | 62 | /// Enables reordering selected items by dragging them. 63 | dataSource.reorderingHandlers.canReorder = { selectedItems in return true } 64 | dataSource.reorderingHandlers.didReorder = { transaction in 65 | self.galleryItems = self.galleryItems.applying(transaction.difference)! 66 | } 67 | 68 | dataSource.rightClickHandler = { selectedItems in 69 | selectedItems.forEach({ item in 70 | item.isFavorite = !item.isFavorite 71 | }) 72 | /// Reconfigurates items without reloading them by calling the item registration handler. 73 | self.dataSource.reconfigureElements(selectedItems) 74 | } 75 | 76 | applySnapshot(using: galleryItems) 77 | 78 | dataSource.selectElements([galleryItems.first!], scrollPosition: .top) 79 | collectionView.makeFirstResponder() 80 | } 81 | 82 | func applySnapshot(using items: [GalleryItem]) { 83 | var snapshot = dataSource.emptySnapshot() 84 | snapshot.appendSections([.main]) 85 | snapshot.appendItems(items, toSection: .main) 86 | dataSource.apply(snapshot, .withoutAnimation) 87 | } 88 | } 89 | 90 | private extension NSView { 91 | /// Creates a colored view. 92 | convenience init(color: NSColor, opacity: CGFloat) { 93 | self.init(frame: .zero) 94 | backgroundColor = color 95 | alphaValue = opacity 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Example/Example/Model/CollectionItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionItem.swift 3 | // 4 | // 5 | // Created by Florian Zand on 24.01.23. 6 | // 7 | 8 | import AppKit 9 | import FZQuicklook 10 | 11 | public class GalleryItem: NSObject, Identifiable { 12 | 13 | public let title: String 14 | public let detail: String 15 | public let badgeText: String? 16 | public let badgeColor: NSColor 17 | public var isFavorite: Bool 18 | 19 | public init(_ title: String, detail: String, badgeText: String? = nil, badgeColor: NSColor = .controlAccentColor, isFavorite: Bool = false) { 20 | self.title = title 21 | self.detail = detail 22 | self.badgeText = badgeText 23 | self.badgeColor = badgeColor 24 | self.isFavorite = isFavorite 25 | } 26 | 27 | static let sampleItems = [GalleryItem("Neil Catstrong", detail: "Liquid Ink"), 28 | GalleryItem("Majestic Mouser", detail: "Painted by Vermeer"), 29 | GalleryItem("Vapor Cat", detail: "Vaporwave", badgeText: "new"), 30 | GalleryItem("Cosmojelly", detail: "Oil on Canvas"), 31 | GalleryItem("Plasmawhale", detail: "Science Fiction", badgeText: "favorite", badgeColor: .systemPurple), 32 | GalleryItem("Berghain", detail: "Surrealist Painting"), 33 | GalleryItem("About Blank", detail: "Oil Painting"), 34 | GalleryItem("Fireworker Monkey", detail: "Japanese Manga"), 35 | GalleryItem("Dystopian City", detail: "Oil Painting", isFavorite: true), 36 | GalleryItem("Underground", detail: "Oil on Canvas"), 37 | GalleryItem("Tresor", detail: "Painting", isFavorite: true), 38 | GalleryItem("Oxi", detail: "Oil on Canvas")] 39 | } 40 | 41 | /// By conforming to `QuicklookPreviewable` and providing `previewItemURL` the items can be previewed by pressing spacebar which opens a Quicklook panel (simliar to Finder). 42 | extension GalleryItem: QuicklookPreviewable { 43 | public var previewItemURL: URL? { 44 | Bundle.main.urlForImageResource(title) 45 | } 46 | 47 | public var previewItemTitle: String? { 48 | title 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Example/Example/Model/OutlineItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineItem.swift 3 | // OutlineTest 4 | // 5 | // Created by Florian Zand on 19.01.25. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct OutlineItem: Hashable, Identifiable, ExpressibleByStringLiteral, CustomStringConvertible { 11 | 12 | public let title: String 13 | 14 | public init(_ title: String) { 15 | self.title = title 16 | } 17 | 18 | public init(stringLiteral value: String) { 19 | self.title = value 20 | } 21 | 22 | public var description: String { 23 | title 24 | } 25 | 26 | public var id: String { 27 | title 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/Example/Model/Section.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Section.swift 3 | // 4 | // 5 | // Created by Florian Zand on 22.06.23. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Section: String, Hashable, Identifiable { 11 | case main = "Main" 12 | case section2 = "Section 2" 13 | case section3 = "Section 3" 14 | 15 | public var title: String { 16 | rawValue 17 | } 18 | 19 | public var id: String { 20 | rawValue 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Example/Example/Model/SidebarItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SidebarItem.swift 3 | // 4 | // 5 | // Created by Florian Zand on 22.06.23. 6 | // 7 | 8 | import Foundation 9 | 10 | public class SidebarItem: NSObject, Identifiable { 11 | 12 | public let title: String 13 | public let symbolName: String 14 | public var isFavorite: Bool = false 15 | 16 | public init(_ title: String, symbolName: String) { 17 | self.title = title 18 | self.symbolName = symbolName 19 | } 20 | 21 | static let sampleItems1 = [SidebarItem("Messages", symbolName: "message.fill"), 22 | SidebarItem("Photos", symbolName: "photo"), 23 | SidebarItem("Videos", symbolName: "film")] 24 | static let sampleItems2 = [SidebarItem("Archive", symbolName: "tray.full")] 25 | static let sampleItems3 = [SidebarItem("News", symbolName: "newspaper")] 26 | } 27 | -------------------------------------------------------------------------------- /Example/Example/OutlineSidebarViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineSidebarViewController.swift 3 | // 4 | // 5 | // Created by Florian Zand on 19.01.23. 6 | // 7 | 8 | import AppKit 9 | import AdvancedCollectionTableView 10 | 11 | class OutlineSidebarViewController: NSViewController { 12 | typealias DataSource = OutlineViewDiffableDataSource 13 | typealias CellRegistration = NSTableView.CellRegistration 14 | 15 | @IBOutlet var outlineView: NSOutlineView! 16 | 17 | lazy var dataSource = DataSource(outlineView: outlineView, cellRegistration: cellRegistration) 18 | 19 | let cellRegistration = CellRegistration { tableCell, _, _, outlineItem in 20 | var configuration = tableCell.defaultContentConfiguration() 21 | configuration.text = outlineItem.title 22 | tableCell.contentConfiguration = configuration 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | outlineView.dataSource = dataSource 29 | dataSource.applyGroupItemCellRegistration(cellRegistration) 30 | 31 | /// Enables reordering selected rows by dragging them. 32 | dataSource.reorderingHandlers.canReorder = { _,_ in return true } 33 | 34 | /// Enables deleting selected items via backspace key. 35 | dataSource.deletingHandlers.canDelete = { items in return items } 36 | 37 | applySnapshot() 38 | } 39 | 40 | func applySnapshot() { 41 | var snapshot = dataSource.emptySnapshot() 42 | let rootItems: [OutlineItem] = ["Root 1", "Root 2", "Root 3", "Root 4", "Root 5"] 43 | snapshot.append(rootItems) 44 | rootItems.forEach { rootItem in 45 | let childItems = (1...5).map { OutlineItem("\(rootItem.title).\($0)") } 46 | snapshot.append(childItems, to: rootItem) 47 | childItems.forEach { childItem in 48 | let grandchildItems = (1...5).map { OutlineItem("\(childItem.title).\($0)") } 49 | snapshot.append(grandchildItems, to: childItem) 50 | } 51 | } 52 | dataSource.apply(snapshot, .withoutAnimation) 53 | dataSource.expand(rootItems) 54 | } 55 | 56 | @IBAction func segmentedPressed(_ segmentedControl: NSSegmentedControl) { 57 | (view.window?.contentViewController as? SplitViewController)?.swapSidebar() 58 | segmentedControl.selectedSegment = 1 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Example/Example/Sample Images/About Blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/About Blank.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Berghain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Berghain.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Cosmojelly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Cosmojelly.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Dystopian City.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Dystopian City.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Fireworker Monkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Fireworker Monkey.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Majestic Mouser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Majestic Mouser.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Neil Catstrong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Neil Catstrong.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Oxi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Oxi.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Plasmawhale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Plasmawhale.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Tresor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Tresor.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Underground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Underground.png -------------------------------------------------------------------------------- /Example/Example/Sample Images/Vapor Cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Example/Example/Sample Images/Vapor Cat.png -------------------------------------------------------------------------------- /Example/Example/SplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplitViewController.swift 3 | // 4 | // 5 | // Created by Florian Zand on 20.01.25. 6 | // 7 | 8 | import Cocoa 9 | 10 | class SplitViewController: NSSplitViewController { 11 | func swapSidebar() { 12 | splitViewItems[0].isCollapsed = true 13 | splitViewItems.swapAt(0, 2) 14 | splitViewItems[0].isCollapsed = false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/Example/TableSidebarViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableSidebarViewController.swift 3 | // 4 | // 5 | // Created by Florian Zand on 19.01.23. 6 | // 7 | 8 | import AppKit 9 | import AdvancedCollectionTableView 10 | 11 | class TableSidebarViewController: NSViewController { 12 | typealias DataSource = TableViewDiffableDataSource 13 | typealias CellRegistration = NSTableView.CellRegistration 14 | typealias SectionHeaderRegistration = NSTableView.CellRegistration 15 | 16 | @IBOutlet var tableView: NSTableView! 17 | 18 | lazy var dataSource = DataSource(tableView: tableView, cellRegistration: cellRegistration) 19 | 20 | let cellRegistration = CellRegistration { tableCell, _, _, sidebarItem in 21 | /// `defaultContentConfiguration` returns a table cell content configuration with default styling based on the table view it's displayed at (in this case a sidebar table). 22 | var configuration = tableCell.defaultContentConfiguration() 23 | configuration.text = sidebarItem.title 24 | configuration.image = NSImage(systemSymbolName: sidebarItem.symbolName) 25 | if sidebarItem.isFavorite { 26 | configuration.badge = .symbolImage("star.fill", color: .systemYellow, backgroundColor: nil) 27 | } 28 | tableCell.contentConfiguration = configuration 29 | } 30 | 31 | let sectionHeaderRegistration = SectionHeaderRegistration { sectionHeaderCell, _, _, section in 32 | var configuration = sectionHeaderCell.defaultContentConfiguration() 33 | configuration.text = section.title 34 | sectionHeaderCell.contentConfiguration = configuration 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | tableView.dataSource = dataSource 41 | dataSource.applySectionHeaderRegistration(sectionHeaderRegistration) 42 | 43 | /// Enables reordering selected rows by dragging them. 44 | dataSource.reorderingHandlers.canReorder = { selectedItems in return selectedItems } 45 | 46 | /// Enables deleting selected rows via backspace key. 47 | dataSource.deletingHandlers.canDelete = { selectedItems in return selectedItems } 48 | 49 | /// Enable dropping strings to the table view by checking if the drop contains strings. 50 | dataSource.droppingHandlers.canDrop = { drop in 51 | return !drop.content.strings.isEmpty 52 | } 53 | 54 | /// Provides sidebar items for the dropped strings. 55 | dataSource.droppingHandlers.items = { drop in 56 | return drop.content.strings.compactMap({ SidebarItem($0, symbolName: "photo") }) 57 | } 58 | 59 | /// Swipe row actions for deleting and favoriting an item. 60 | dataSource.rowActionProvider = { swippedItem, edge in 61 | if edge == .leading { 62 | /// Left swipe 63 | return [NSTableViewRowAction.regular(symbolName: swippedItem.isFavorite ? "star" : "star.fill", color: .systemYellow) { _,_ in 64 | swippedItem.isFavorite = !swippedItem.isFavorite 65 | self.dataSource.reconfigureItems([swippedItem]) 66 | self.tableView.rowActionsVisible = false 67 | }] 68 | } else { 69 | /// Right swipe 70 | return [NSTableViewRowAction.destructive(symbolName: "trash.fill") { _,_ in 71 | var snapshot = self.dataSource.snapshot() 72 | snapshot.deleteItems([swippedItem]) 73 | self.dataSource.apply(snapshot) 74 | }] 75 | } 76 | } 77 | 78 | applySnapshot() 79 | } 80 | 81 | func applySnapshot() { 82 | var snapshot = dataSource.emptySnapshot() 83 | snapshot.appendSections([.main, .section2, .section3]) 84 | snapshot.appendItems(SidebarItem.sampleItems1, toSection: .main) 85 | snapshot.appendItems(SidebarItem.sampleItems2, toSection: .section2) 86 | snapshot.appendItems(SidebarItem.sampleItems3, toSection: .section3) 87 | dataSource.apply(snapshot, .usingReloadData) 88 | } 89 | 90 | @IBAction func segmentedPressed(_ segmentedControl: NSSegmentedControl) { 91 | (view.window?.contentViewController as? SplitViewController)?.swapSidebar() 92 | segmentedControl.selectedSegment = 0 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Florian Zand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "AdvancedCollectionTableView", 8 | platforms: [ 9 | .macOS("12.0"), 10 | ], 11 | products: [ 12 | .library( 13 | name: "AdvancedCollectionTableView", 14 | targets: ["AdvancedCollectionTableView"] 15 | ), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/flocked/FZSwiftUtils.git", branch: "main"), 19 | .package(url: "https://github.com/flocked/FZUIKit.git", branch: "main"), 20 | .package(url: "https://github.com/flocked/FZQuicklook.git", branch: "main"), 21 | ], 22 | targets: [ 23 | .target( 24 | name: "AdvancedCollectionTableView", 25 | dependencies: ["FZSwiftUtils", "FZUIKit", "FZQuicklook"], path: "Sources/AdvancedCollectionTableView" 26 | ), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSItemContentConfiguration/NSItemContentConfiguration+Content.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSItemContentConfiguration+Content.swift 3 | // 4 | // 5 | // Created by Florian Zand on 07.08.23. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | import SwiftUI 12 | 13 | public extension NSItemContentConfiguration { 14 | /// Properties that affect the content that displays the image and view. 15 | struct ContentProperties: Hashable { 16 | /// The corner radius of the content. 17 | public var cornerRadius: CGFloat = 10.0 18 | 19 | /// The maximum size of the content. 20 | public var maximumSize = ProposedSize() 21 | 22 | public struct ProposedSize: Hashable { 23 | /// The proposed width. 24 | public var width: CGFloat? 25 | /// The proposed height. 26 | public var height: CGFloat? 27 | /// The proposed Sizing mode. The default value is `absolute`. 28 | public var mode: Mode = .absolute 29 | 30 | /// Proposed Sizing mode. 31 | public enum Mode: Int, Hashable { 32 | /// The `width` and `height` is absolute. 33 | case absolute 34 | /// The `width` and `height` is relative. 35 | case relative 36 | } 37 | } 38 | 39 | /** 40 | The scaling of the content view. 41 | 42 | The default is `1.0`, which displays the content view at it's original scale. A larger value will display the content view at a larger, a smaller value at a smaller size. 43 | */ 44 | public var scaleTransform: Scale = 1.0 45 | 46 | /** 47 | The rotation of the content view, in degrees. 48 | 49 | The default is `zero`, which displays the content view with no rotation. 50 | */ 51 | public var rotation: Rotation = .zero 52 | 53 | /// The background color. 54 | public var backgroundColor: NSColor? = .lightGray 55 | 56 | /// The color transformer for resolving the background color. 57 | public var backgroundColorTransformer: ColorTransformer? 58 | 59 | /// Generates the resolved background color for the specified background color, using the background color and color transformer. 60 | public func resolvedBackgroundColor() -> NSColor? { 61 | if let backgroundColor = backgroundColor { 62 | return backgroundColorTransformer?(backgroundColor) ?? backgroundColor 63 | } 64 | return nil 65 | } 66 | 67 | /// The visual effect background of the content. 68 | public var visualEffect: VisualEffectConfiguration? 69 | 70 | /// The border of the content. 71 | public var border: BorderConfiguration = .none() 72 | 73 | /// The border transformer for resolving the border. 74 | public var borderTransformer: BorderTransformer? = nil 75 | 76 | /// Generates the resolved border, using the border and border transformer. 77 | public func resolvedBorder() -> BorderConfiguration { 78 | borderTransformer?(border) ?? border 79 | } 80 | 81 | var borderStateTransformer: BorderTransformer? = nil 82 | 83 | func _resolvedBorder() -> BorderConfiguration { 84 | let border = borderTransformer?(border) ?? border 85 | return borderStateTransformer?(border) ?? border 86 | } 87 | 88 | /// The shadow properties. 89 | public var shadow: ShadowConfiguration = .black() 90 | 91 | /// The shadow transformer for resolving the shadow. 92 | public var shadowTransformer: ShadowTransformer? = nil 93 | 94 | /// Generates the resolved shadow, using the shadow and shadow transformer. 95 | public func resolvedShadow() -> ShadowConfiguration { 96 | shadowTransformer?(shadow) ?? shadow 97 | } 98 | 99 | var shadowStateTransformer: ShadowTransformer? = nil 100 | 101 | func _resolvedShadow() -> ShadowConfiguration { 102 | let shadow = shadowTransformer?(shadow) ?? shadow 103 | return shadowStateTransformer?(shadow) ?? shadow 104 | } 105 | 106 | /// The text for the tooltip of the content. 107 | public var toolTip: String? = nil 108 | 109 | init() {} 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSItemContentConfiguration/NSItemContentConfiguration+Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSItemContentConfiguration+Image.swift 3 | // 4 | // 5 | // Created by Florian Zand on 08.12.24. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | import SwiftUI 12 | 13 | public extension NSItemContentConfiguration { 14 | /// Properties that affect the image of the content. 15 | struct ImageProperties: Hashable { 16 | /// The scaling of the image. 17 | public enum ImageScaling { 18 | /// The image is resized to fit the bounds size, while still preserving the aspect ratio of the image. 19 | case fit 20 | /// The image is resized to completely fill the bounds rectangle, while still preserving the aspect ratio of the image. The image is centered in the axis it exceeds. 21 | case fill 22 | /// The image is resized to the entire bounds rectangle. 23 | case resize 24 | /// The image isn't resized. 25 | case none 26 | 27 | var gravity: CALayerContentsGravity { 28 | switch self { 29 | case .fit: return .resizeAspect 30 | case .fill: return .resizeAspectFill 31 | case .resize: return .resize 32 | case .none: return .center 33 | } 34 | } 35 | 36 | var scaling: ImageView.ImageScaling { 37 | switch self { 38 | case .fit: return .scaleToFit 39 | case .fill: return .scaleToFill 40 | case .resize: return .resize 41 | case .none: return .none 42 | } 43 | } 44 | 45 | var swiftui: ContentMode { 46 | switch self { 47 | case .none: return .fit 48 | case .fit: return .fit 49 | case .fill: return .fill 50 | case .resize: return .fit 51 | } 52 | } 53 | 54 | var shouldResize: Bool { 55 | self == .fit 56 | } 57 | } 58 | 59 | /// The symbol configuration for the image. 60 | public var symbolConfiguration: ImageSymbolConfiguration? 61 | 62 | /// The image scaling. 63 | public var scaling: ImageScaling = .fit 64 | 65 | /// The tint color for an image that is a template or symbol image. 66 | public var tintColor: NSColor? 67 | 68 | /// The color transformer for resolving the image tint color. 69 | public var tintColorTransformer: ColorTransformer? 70 | 71 | /// Generates the resolved image tint color for the specified tint color, using the tint color and tint color transformer. 72 | public func resolvedTintColor() -> NSColor? { 73 | if let tintColor = tintColor { 74 | return tintColorTransformer?(tintColor) ?? tintColor 75 | } 76 | return nil 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSItemContentConfiguration/unused/NSItemContentConfiguration+Transition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSItemContentConfiguration+Transition.swift 3 | // 4 | // 5 | // Created by Florian Zand on 29.07.23. 6 | // 7 | 8 | import AppKit 9 | import FZUIKit 10 | 11 | /* 12 | // Currently not used 13 | extension NSItemContentConfiguration { 14 | func animate(with configuration: TransitionConfiguration) { 15 | guard let view = (self.collectionViewItem?.contentView as? NSItemContentView), let collectionView = self.collectionViewItem?._collectionView else { return } 16 | var backgroundView: NSView? = nil 17 | if let backgroundColor = configuration.backgroundColor { 18 | if let _backgroundView = collectionView.superview?.subviews(type: TransitionBackgroundView.self).first { 19 | backgroundView = _backgroundView 20 | _backgroundView.frame = collectionView.frame 21 | collectionView.superview?.addSubview(backgroundView!) 22 | } else { 23 | backgroundView = TransitionBackgroundView(frame: collectionView.frame) 24 | backgroundView?.backgroundColor = backgroundColor 25 | backgroundView?.alphaValue = 0.0 26 | collectionView.superview?.addSubview(backgroundView!) 27 | } 28 | } 29 | collectionView.superview?.addSubview(view) 30 | Wave.animate(withSpring: .defaultAnimated, animations: { 31 | if let alpha = configuration.text.alpha { 32 | view.textField.animator.alpha = alpha 33 | } 34 | if let alpha = configuration.secondaryText.alpha { 35 | view.secondaryTextField.animator.alpha = alpha 36 | } 37 | if let alpha = configuration.content.alpha { 38 | view.contentView.animator.alpha = alpha 39 | } 40 | if let frame = configuration.text.frame { 41 | view.textField.animator.frame = frame 42 | } 43 | if let frame = configuration.secondaryText.frame { 44 | view.secondaryTextField.animator.frame = frame 45 | } 46 | if let frame = configuration.content.frame { 47 | view.contentView.animator.frame = frame 48 | } 49 | backgroundView?.animator.alpha = 1.0 50 | collectionView.animator.alpha = 0.0 51 | }, completion: { _,_ in 52 | configuration.completion?() 53 | backgroundView?.removeFromSuperview() 54 | }) 55 | } 56 | 57 | struct TransitionConfiguration { 58 | public struct TransitionItem: Hashable { 59 | var alpha: CGFloat? = nil 60 | var frame: CGRect? = nil 61 | } 62 | 63 | public enum AnimationType: Int, Hashable { 64 | case spring 65 | case easeInOut 66 | case easeIn 67 | case easeOut 68 | case linear 69 | } 70 | 71 | var text: TransitionItem = TransitionItem() 72 | var secondaryText: TransitionItem = TransitionItem() 73 | var content: TransitionItem = TransitionItem() 74 | 75 | var backgroundColor: NSColor? = nil 76 | var fading: Bool = true 77 | var animationTime: CGFloat = 0.1 78 | var animationType: AnimationType = .spring 79 | var completion: (()->())? = nil 80 | } 81 | 82 | class TransitionBackgroundView: NSView { 83 | override var tag: Int { 84 | return 34576542 85 | } 86 | } 87 | } 88 | 89 | */ 90 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSListContentConfiguration/Accessory/Accessories/TextAccessory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextAccessory.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.03.25. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TextAccessory { 11 | /** 12 | The text. 13 | 14 | This value supersedes the ``attributedText`` property. 15 | */ 16 | public var text: String? { 17 | didSet { 18 | guard text != nil else { return } 19 | attributedText = nil 20 | } 21 | } 22 | 23 | /** 24 | An attributed variant of the text. 25 | 26 | This value supersedes the ``text`` property. 27 | */ 28 | public var attributedText: AttributedString? { 29 | didSet { 30 | guard attributedText != nil else { return } 31 | text = nil 32 | } 33 | } 34 | 35 | /** 36 | The placeholder text. 37 | 38 | This value supersedes the ``attributedPlaceholderText`` property. 39 | */ 40 | public var placeholderText: String? { 41 | didSet { 42 | guard placeholderText != nil else { return } 43 | attributedPlaceholderText = nil 44 | } 45 | } 46 | 47 | /** 48 | An attributed variant of the placeholder text. 49 | 50 | This value supersedes the ``placeholderText`` property. 51 | */ 52 | public var attributedPlaceholderText: AttributedString? { 53 | didSet { 54 | guard attributedPlaceholderText != nil else { return } 55 | placeholderText = nil 56 | } 57 | } 58 | 59 | /// Properties for configuring the primary text. 60 | public var textProperties: TextProperties = .primary 61 | } 62 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSListContentConfiguration/Accessory/Accessories/TextStackAccessory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextStackAccessory.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.03.25. 6 | // 7 | 8 | import Foundation 9 | 10 | struct TextStackAccessory { 11 | /** 12 | The leading text. 13 | 14 | This value supersedes the ``attributedLeadingText`` property. 15 | */ 16 | public var leadingText: String? { 17 | didSet { 18 | guard leadingText != nil else { return } 19 | attributedLeadingText = nil 20 | } 21 | } 22 | 23 | /** 24 | An attributed variant of the leading text. 25 | 26 | This value supersedes the ``leadingText`` property. 27 | */ 28 | public var attributedLeadingText: AttributedString? { 29 | didSet { 30 | guard attributedLeadingText != nil else { return } 31 | leadingText = nil 32 | } 33 | } 34 | 35 | /** 36 | The leading placeholder text. 37 | 38 | This value supersedes the ``attributedPlaceholderLeadingText`` property. 39 | */ 40 | public var placeholderLeadingText: String? { 41 | didSet { 42 | guard placeholderLeadingText != nil else { return } 43 | attributedPlaceholderLeadingText = nil 44 | } 45 | } 46 | 47 | /** 48 | An attributed variant of the leading placeholder text. 49 | 50 | This value supersedes the ``placeholderLeadingText`` property. 51 | */ 52 | public var attributedPlaceholderLeadingText: AttributedString? { 53 | didSet { 54 | guard attributedPlaceholderLeadingText != nil else { return } 55 | placeholderLeadingText = nil 56 | } 57 | } 58 | 59 | /** 60 | The trailing text. 61 | 62 | This value supersedes the ``attributedTrailingText`` property. 63 | */ 64 | public var trailingText: String? { 65 | didSet { 66 | guard trailingText != nil else { return } 67 | attributedTrailingText = nil 68 | } 69 | } 70 | 71 | /** 72 | An attributed variant of the trailing text. 73 | 74 | This value supersedes the ``trailingText`` property. 75 | */ 76 | public var attributedTrailingText: AttributedString? { 77 | didSet { 78 | guard attributedLeadingText != nil else { return } 79 | trailingText = nil 80 | } 81 | } 82 | 83 | /** 84 | The trailing placeholder text. 85 | 86 | This value supersedes the ``attributedPlaceholderTrailingText`` property. 87 | */ 88 | public var placeholderTrailingText: String? { 89 | didSet { 90 | guard placeholderLeadingText != nil else { return } 91 | attributedPlaceholderTrailingText = nil 92 | } 93 | } 94 | 95 | /** 96 | An attributed variant of the trailing placeholder text. 97 | 98 | This value supersedes the ``placeholderTrailingText`` property. 99 | */ 100 | public var attributedPlaceholderTrailingText: AttributedString? { 101 | didSet { 102 | guard attributedPlaceholderTrailingText != nil else { return } 103 | placeholderTrailingText = nil 104 | } 105 | } 106 | 107 | 108 | /// Properties for configuring the primary text. 109 | public var leadingTextProperties: TextProperties = .primary 110 | 111 | /// Properties for configuring the primary text. 112 | public var trailingTextProperties: TextProperties = .primary 113 | 114 | /// The spacing between the leading and trailing text. 115 | public var leadingToTrailingTextSpacing: CGFloat = 4.0 116 | } 117 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSListContentConfiguration/Accessory/NSListContentView+Accessory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSListContentView+Accessory.swift 3 | // 4 | // 5 | // Created by Florian Zand on 19.06.23. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | import SwiftUI 12 | 13 | // Currently unused. It allows to add additional information to a list configuration like an additional title. See the Mac Mail app and it's list of emails. Each entry displays the title and content of the email + the sender and date on top. 14 | extension NSListContentConfiguration { 15 | /** 16 | Configuration for a list accessory. 17 | 18 | An accessory displays additional content at the bottom or top of a list item. 19 | */ 20 | struct Accessory: Hashable { 21 | 22 | /// Position of the accessory. 23 | public enum Position: Int, Hashable { 24 | /// The accessory is positioned at the top of the list item. 25 | case top 26 | /// The accessory is positioned at the bottom of the list item. 27 | case bottom 28 | } 29 | 30 | /// The leading accessory item. 31 | var leading: AccessoryProperties = { 32 | var properties = AccessoryProperties() 33 | properties.textProperties.alignment = .left 34 | properties.secondaryTextProperties.alignment = .left 35 | return properties 36 | }() 37 | 38 | /// The trailing accessory item. 39 | var trailing: AccessoryProperties = { 40 | var properties = AccessoryProperties() 41 | properties.textProperties.alignment = .right 42 | properties.secondaryTextProperties.alignment = .right 43 | return properties 44 | }() 45 | 46 | /// The position of the accessory. 47 | public var position: Position = .top 48 | 49 | /// The padding to the next accessory. 50 | public var padding: CGFloat = 4.0 51 | } 52 | } 53 | 54 | extension NSListContentConfiguration { 55 | /// Properties for a list accessory item. 56 | struct AccessoryProperties: Hashable { 57 | // MARK: Customizing content 58 | 59 | /// The primary text. 60 | public var text: String? 61 | 62 | /// An attributed variant of the primary text. 63 | public var attributedText: AttributedString? 64 | 65 | /// The secondary text. 66 | public var secondaryText: String? 67 | 68 | /// An attributed variant of the secondary text. 69 | public var secondaryAttributedText: AttributedString? 70 | 71 | /// The image. 72 | public var image: NSImage? 73 | 74 | // MARK: Customizing appearance 75 | 76 | /// Properties for configuring the primary text. 77 | public var textProperties: TextProperties = .primary 78 | 79 | /// Properties for configuring the secondary text. 80 | public var secondaryTextProperties: TextProperties = .secondary 81 | 82 | /// Properties for configuring the image. 83 | public var imageProperties = ImageProperties() 84 | 85 | // MARK: Customizing layout 86 | 87 | /// The padding to the next accessory item. 88 | public var padding: CGFloat = 4.0 89 | 90 | /// The padding between the image and text. 91 | public var imageToTextPadding: CGFloat = 8.0 92 | 93 | /// The padding between primary and secndary text. 94 | public var textToSecondaryTextPadding: CGFloat = 2.0 95 | 96 | var hasText: Bool { 97 | text != nil || attributedText != nil 98 | } 99 | 100 | var hasSecondaryText: Bool { 101 | secondaryText != nil || secondaryAttributedText != nil 102 | } 103 | 104 | var hasContent: Bool { 105 | image != nil 106 | } 107 | 108 | var isVisible: Bool { 109 | image != nil || hasText || hasSecondaryText 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/NSListContentConfiguration/NSListContentView+ImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSListContentView+ImageView.swift 3 | // 4 | // 5 | // Created by Florian Zand on 28.07.23. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | import SwiftUI 12 | 13 | extension NSListContentView { 14 | class ListImageView: ImageView { 15 | var properties: NSListContentConfiguration.ImageProperties { 16 | didSet { 17 | guard oldValue != properties else { return } 18 | update() 19 | } 20 | } 21 | 22 | override var image: NSImage? { 23 | didSet { isHidden = image == nil } 24 | } 25 | 26 | override var intrinsicContentSize: NSSize { 27 | var intrinsicContentSize = super.intrinsicContentSize 28 | 29 | intrinsicContentSize = intrinsicContentSize.clamped(min: reservedLayoutSize) 30 | 31 | if reservedLayoutSize.width == 0, image?.isSymbolImage == true, properties.position.orientation == .horizontal { 32 | intrinsicContentSize.width = (intrinsicContentSize.height * 2.5).rounded(.towardZero) 33 | return intrinsicContentSize 34 | } 35 | 36 | if reservedLayoutSize.width == NSListContentConfiguration.ImageProperties.standardDimension { 37 | // intrinsicContentSize.width = intrinsicContentSize.width.c 38 | } 39 | 40 | if let calculatedSize = calculatedSize { 41 | return calculatedSize 42 | } 43 | 44 | return intrinsicContentSize 45 | } 46 | 47 | var calculatedSize: CGSize? { 48 | didSet { 49 | invalidateIntrinsicContentSize() 50 | } 51 | } 52 | 53 | var verticalConstraint: NSLayoutConstraint? 54 | var reservedLayoutSize: CGSize = .zero { 55 | didSet { 56 | if reservedLayoutSize.width == NSListContentConfiguration.ImageProperties.standardDimension { 57 | reservedLayoutSize.width = 36.0 58 | } 59 | if reservedLayoutSize.height == NSListContentConfiguration.ImageProperties.standardDimension { 60 | reservedLayoutSize.height = 9.0 61 | } 62 | } 63 | } 64 | 65 | func update() { 66 | imageScaling = image?.isSymbolImage == true ? .none : properties.scaling.imageScaling 67 | symbolConfiguration = properties.symbolConfiguration?.nsSymbolConfiguration() 68 | border = properties.resolvedBorder() 69 | backgroundColor = properties.resolvedBackgroundColor() 70 | tintColor = properties.resolvedTintColor() 71 | cornerRadius = properties.cornerRadius 72 | outerShadow = properties.resolvedShadow() 73 | toolTip = properties.toolTip 74 | reservedLayoutSize = properties.reservedLayoutSize 75 | invalidateIntrinsicContentSize() 76 | } 77 | 78 | init(properties: NSListContentConfiguration.ImageProperties) { 79 | self.properties = properties 80 | super.init(frame: .zero) 81 | wantsLayer = true 82 | imageAlignment = .alignCenter 83 | update() 84 | } 85 | 86 | @available(*, unavailable) 87 | required init?(coder _: NSCoder) { 88 | fatalError("init(coder:) has not been implemented") 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/Shared/Protocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutomaticHeightSizable.swift 3 | // 4 | // 5 | // Created by Florian Zand on 20.07.24. 6 | // 7 | 8 | import AppKit 9 | import FZUIKit 10 | 11 | /// Content configurations with views that can autolayout their height. 12 | protocol AutomaticHeightSizable: NSContentConfiguration { } 13 | 14 | extension NSListContentConfiguration: AutomaticHeightSizable { } 15 | extension NSHostingConfiguration: AutomaticHeightSizable { } 16 | 17 | /// Content configuration views with editable text fields. 18 | protocol EditingContentView: NSView { } 19 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Configurations/Shared/TextStackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextStackView.swift 3 | // 4 | // 5 | // Created by Florian Zand on 02.03.25. 6 | // 7 | 8 | import AppKit 9 | import FZUIKit 10 | 11 | class TextStackView: NSStackView { 12 | let textField = ListItemTextField.wrapping().truncatesLastVisibleLine(true) 13 | let secondaryTextField = ListItemTextField.wrapping().truncatesLastVisibleLine(true) 14 | private var prefersSideBySideTextAndSecondaryText = false 15 | private var previousBounds: CGRect = .zero 16 | private var verticalSpacing: CGFloat = .zero 17 | private var horizontalSpacing: CGFloat = .zero 18 | 19 | func update(with properties: TextStackProperties, for view: NSView) { 20 | textField.isEnabled = properties.isEnabled 21 | textField.properties = properties.textProperties 22 | textField.updateText(properties.text, properties.attributedText, properties.placeholderText, properties.attributedPlaceholderText) 23 | textField.updateLayoutGuide(for: view) 24 | secondaryTextField.isEnabled = properties.isEnabled 25 | secondaryTextField.properties = properties.secondaryTextProperties 26 | secondaryTextField.updateText(properties.secondaryText, properties.secondaryAttributedText, properties.secondaryPlaceholderText, properties.secondaryAttributedPlaceholderText) 27 | secondaryTextField.updateLayoutGuide(for: view) 28 | horizontalSpacing = properties.textToSecondaryTextHorizontalPadding 29 | verticalSpacing = properties.textToSecondaryTextPadding 30 | spacing = orientation == .vertical ? verticalSpacing : horizontalSpacing 31 | prefersSideBySideTextAndSecondaryText = properties.prefersSideBySideTextAndSecondaryText 32 | updateLayout() 33 | } 34 | 35 | override func layout() { 36 | super.layout() 37 | guard previousBounds.size != bounds.size else { return } 38 | previousBounds = bounds 39 | updateLayout() 40 | } 41 | 42 | func updateLayout() { 43 | if prefersSideBySideTextAndSecondaryText, bounds.width >= textField.intrinsicContentSize.width + secondaryTextField.intrinsicContentSize.width + horizontalSpacing { 44 | orientation = .horizontal 45 | spacing = horizontalSpacing 46 | } else { 47 | orientation = .vertical 48 | spacing = verticalSpacing 49 | } 50 | } 51 | 52 | init() { 53 | super.init(frame: .zero) 54 | addArrangedSubview(textField) 55 | addArrangedSubview(secondaryTextField) 56 | orientation = .vertical 57 | alignment = .leading 58 | } 59 | 60 | required init?(coder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | } 64 | 65 | extension NSListContentConfiguration: TextStackProperties { } 66 | extension NSItemContentConfiguration: TextStackProperties { } 67 | 68 | protocol TextStackProperties { 69 | var text: String? { get } 70 | var attributedText: AttributedString? { get } 71 | var placeholderText: String? { get } 72 | var attributedPlaceholderText: AttributedString? { get } 73 | var secondaryText: String? { get } 74 | var secondaryAttributedText: AttributedString? { get } 75 | var secondaryPlaceholderText: String? { get } 76 | var secondaryAttributedPlaceholderText: AttributedString? { get } 77 | var textToSecondaryTextPadding: CGFloat { get } 78 | var textProperties: TextProperties { get } 79 | var secondaryTextProperties: TextProperties { get } 80 | var isEnabled: Bool { get } 81 | var prefersSideBySideTextAndSecondaryText: Bool { get } 82 | var textToSecondaryTextHorizontalPadding: CGFloat { get } 83 | } 84 | 85 | extension TextStackProperties { 86 | var isEnabled: Bool { true } 87 | var prefersSideBySideTextAndSecondaryText: Bool { false } 88 | var textToSecondaryTextHorizontalPadding: CGFloat { 0.0 } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Extensions/NSCollectionView+/NSCollectionView+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionView+.swift 3 | // 4 | // 5 | // Created by Florian Zand on 01.11.22. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | 12 | extension NSCollectionView { 13 | /// The index path of the item that is hovered by the mouse. 14 | @objc dynamic var hoveredIndexPath: IndexPath? { 15 | get { getAssociatedValue("hoveredIndexPath") } 16 | set { 17 | guard newValue != hoveredIndexPath else { return } 18 | hoveredItem?.isHovered = false 19 | setAssociatedValue(newValue, key: "hoveredIndexPath") 20 | } 21 | } 22 | 23 | /// The item that is hovered by the mouse. 24 | var hoveredItem: NSCollectionViewItem? { 25 | guard let indexPath = hoveredIndexPath else { return nil } 26 | return item(at: indexPath) 27 | } 28 | 29 | func setupObservation(shouldObserve: Bool = true) { 30 | if !shouldObserve { 31 | observerView?._removeFromSuperview() 32 | observerView = nil 33 | } else if observerView == nil { 34 | observerView = .init(for: self) 35 | } 36 | } 37 | 38 | var observerView: TableCollectionObserverView? { 39 | get { getAssociatedValue("collectionViewObserverView") } 40 | set { setAssociatedValue(newValue, key: "collectionViewObserverView") } 41 | } 42 | 43 | var editingView: NSView? { 44 | observerView?.editingView 45 | } 46 | 47 | var activeState: NSItemConfigurationState.ActiveState { 48 | isActive ? isFocused ? .focused : .active : .inactive 49 | } 50 | 51 | var isFocused: Bool { 52 | observerView?.isFocused == true 53 | } 54 | 55 | var isActive: Bool { 56 | window?.isKeyWindow == true 57 | } 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Extensions/NSTableView+/NSTableView+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+.swift 3 | // 4 | // 5 | // Created by Florian Zand on 10.12.22. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | 12 | extension NSTableView { 13 | func setupObservation(shouldObserve: Bool = true) { 14 | if !shouldObserve { 15 | observerView?._removeFromSuperview() 16 | observerView = nil 17 | } else if observerView == nil { 18 | observerView = .init(for: self) 19 | } 20 | } 21 | 22 | @objc dynamic var hoveredRow: Int { 23 | get { getAssociatedValue("hoveredRow", initialValue: -1) } 24 | set { 25 | guard newValue != hoveredRow else { return } 26 | let previousRow = hoveredRowView 27 | setAssociatedValue(newValue, key: "hoveredRow") 28 | previousRow?.setNeedsAutomaticUpdateConfiguration() 29 | hoveredRowView?.setNeedsAutomaticUpdateConfiguration() 30 | } 31 | } 32 | 33 | var hoveredRowView: NSTableRowView? { 34 | if hoveredRow != -1, hoveredRow < numberOfRows { 35 | return rowView(atRow: hoveredRow, makeIfNecessary: false) 36 | } 37 | return nil 38 | } 39 | 40 | var observerView: TableCollectionObserverView? { 41 | get { getAssociatedValue("tableViewObserverView") } 42 | set { setAssociatedValue(newValue, key: "tableViewObserverView") } 43 | } 44 | 45 | var editingView: NSView? { 46 | observerView?.editingView 47 | } 48 | 49 | var activeState: NSListConfigurationState.ActiveState { 50 | isActive ? isFocused ? .focused : .active : .inactive 51 | } 52 | 53 | var isFocused: Bool { 54 | observerView?.isFocused == true 55 | } 56 | 57 | var isActive: Bool { 58 | window?.isKeyWindow == true 59 | } 60 | 61 | func enableAutomaticRowHeights() { 62 | guard !usesAutomaticRowHeights else { return } 63 | Self.swizzleViewRegistration() 64 | isEnablingAutomaticRowHeights = true 65 | usesAutomaticRowHeights = true 66 | reloadData() 67 | } 68 | 69 | var isEnablingAutomaticRowHeights: Bool { 70 | get { getAssociatedValue("isEnablingAutomaticRowHeights") ?? false } 71 | set { setAssociatedValue(newValue, key: "isEnablingAutomaticRowHeights") } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/Extensions/Shared/TableCollectionObserverView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableCollectionObserverView.swift 3 | // 4 | // 5 | // Created by Florian Zand on 25.01.25. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | 12 | class TableCollectionObserverView: NSView { 13 | var tokens: [NotificationToken] = [] 14 | lazy var trackingArea = TrackingArea(for: self, options: [.mouseMoved, .mouseEnteredAndExited, .activeInKeyWindow]) 15 | weak var collectionView: NSCollectionView? 16 | weak var tableView: NSTableView? 17 | var focusObservation: KeyValueObservation? 18 | var isEnabledObservation: KeyValueObservation? 19 | 20 | var isFocused = false { 21 | didSet { 22 | guard oldValue != isFocused else { return } 23 | collectionView?.visibleItems().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 24 | tableView?.visibleRows().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 25 | } 26 | } 27 | 28 | weak var editingView: NSView? { 29 | didSet { 30 | guard oldValue != editingView else { return } 31 | if collectionView != nil { 32 | (oldValue?.firstSuperview(where: { $0.parentController is NSCollectionViewItem })?.parentController as? NSCollectionViewItem)?.setNeedsAutomaticUpdateConfiguration() 33 | (editingView?.firstSuperview(where: { $0.parentController is NSCollectionViewItem })?.parentController as? NSCollectionViewItem)?.setNeedsAutomaticUpdateConfiguration() 34 | } else { 35 | oldValue?.firstSuperview(for: NSTableRowView.self)?.setNeedsAutomaticUpdateConfiguration() 36 | editingView?.firstSuperview(for: NSTableRowView.self)?.setNeedsAutomaticUpdateConfiguration() 37 | } 38 | } 39 | } 40 | 41 | init(for collectionView: NSCollectionView) { 42 | super.init(frame: .zero) 43 | self.collectionView = collectionView 44 | initialSetup(for: collectionView) 45 | } 46 | 47 | init(for tableView: NSTableView) { 48 | super.init(frame: .zero) 49 | self.tableView = tableView 50 | initialSetup(for: tableView) 51 | isEnabledObservation = tableView.observeChanges(for: \.isEnabled) { [weak self] old, new in 52 | guard let self = self, old != new else { return } 53 | self.tableView?.visibleRows().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 54 | } 55 | } 56 | 57 | func initialSetup(for view: NSView) { 58 | view.addSubview(withConstraint: self) 59 | zPosition = -CGFloat.greatestFiniteMagnitude 60 | isFocused = view.isDescendantFirstResponder 61 | sendToBack() 62 | updateTrackingAreas() 63 | focusObservation = observeChanges(for: \.window?.firstResponder) { [weak self] oldValue, newValue in 64 | guard let self = self, let _view = self.collectionView ?? self.tableView else { return } 65 | if let view = (newValue as? NSView ?? (newValue as? NSText)?.delegate as? NSView), view.isDescendant(of: _view) { 66 | self.isFocused = true 67 | self.editingView = (view as? EditiableView)?.isEditable == true ? view : nil 68 | } else { 69 | self.isFocused = false 70 | } 71 | } 72 | } 73 | 74 | required init?(coder: NSCoder) { 75 | fatalError("init(coder:) has not been implemented") 76 | } 77 | 78 | override func updateTrackingAreas() { 79 | super.updateTrackingAreas() 80 | trackingArea.update() 81 | } 82 | 83 | override func mouseEntered(with event: NSEvent) { 84 | updateHovered(for: event) 85 | } 86 | 87 | override func mouseMoved(with event: NSEvent) { 88 | updateHovered(for: event) 89 | } 90 | 91 | override func mouseExited(with event: NSEvent) { 92 | collectionView?.hoveredIndexPath = nil 93 | tableView?.hoveredRow = -1 94 | } 95 | 96 | func updateHovered(for event: NSEvent) { 97 | if let collectionView = collectionView { 98 | let location = event.location(in: collectionView) 99 | collectionView.hoveredIndexPath = collectionView.indexPathForItem(at: location) 100 | if let item = collectionView.hoveredItem { 101 | if let view = item.view as? NSItemContentView { 102 | item.isHovered = view.isHovering(at: collectionView.convert(location, to: view)) 103 | } else { 104 | item.isHovered = true 105 | } 106 | } 107 | } else if let tableView = tableView { 108 | let location = event.location(in: tableView) 109 | tableView.hoveredRow = tableView.row(at: event.location(in: tableView)) 110 | } 111 | } 112 | 113 | override func hitTest(_ point: NSPoint) -> NSView? { 114 | return nil 115 | } 116 | 117 | func _removeFromSuperview() { 118 | super.removeFromSuperview() 119 | } 120 | 121 | override func removeFromSuperview() { } 122 | 123 | override func viewWillMove(toWindow newWindow: NSWindow?) { 124 | tokens = [] 125 | guard let newWindow = newWindow else { return } 126 | tokens = [NotificationCenter.default.observe(NSWindow.didBecomeKeyNotification, object: newWindow) { [weak self] _ in 127 | guard let self = self else { return } 128 | self.collectionView?.visibleItems().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 129 | self.tableView?.visibleRows().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 130 | }, NotificationCenter.default.observe(NSWindow.didResignKeyNotification, object: newWindow) { [weak self] _ in 131 | guard let self = self else { return } 132 | self.collectionView?.hoveredIndexPath = nil 133 | self.collectionView?.visibleItems().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 134 | self.tableView?.hoveredRow = -1 135 | self.tableView?.visibleRows().forEach { $0.setNeedsAutomaticUpdateConfiguration() } 136 | }] 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Configuration/States/ConfigurationState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurationState.swift 3 | // 4 | // 5 | // Created by Florian Zand on 12.10.23. 6 | // 7 | 8 | import Foundation 9 | import FZUIKit 10 | 11 | protocol ConfigurationState: NSConfigurationState { 12 | var isSelected: Bool { get } 13 | var isActive: Bool { get } 14 | var isHovered: Bool { get } 15 | } 16 | 17 | extension NSItemConfigurationState: ConfigurationState { } 18 | extension NSListConfigurationState: ConfigurationState { } 19 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/NSOutlineView/ unused/CellOutlineVItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellOutlineVItem.swift 3 | // 4 | // 5 | // Created by Florian Zand on 20.01.25. 6 | // 7 | 8 | import AppKit 9 | 10 | /* 11 | public protocol OutlineItemObject: AnyObject, Hashable { 12 | /// The parent of the item. 13 | var parent: Self? { get set } 14 | 15 | /// The children of the item. 16 | var children: [Self] { get set } 17 | 18 | /** 19 | A Boolean value indicating whether the item is expanded. 20 | 21 | The default value is `true`. 22 | */ 23 | var isExpanded: Bool { get set } 24 | 25 | /** 26 | A Boolean value indicating whether the item can be expanded / collapsed by the user. 27 | 28 | The default value is `true`. 29 | */ 30 | var isExpandable: Bool { get } 31 | 32 | /** 33 | A Boolean value indicating whether the item can be selected. 34 | 35 | The default value is `true`. 36 | */ 37 | var isSelectable: Bool { get } 38 | 39 | /** 40 | A Boolean value indicating whether the item can be deleted by the user pressing `Backspace`. 41 | 42 | The default value is `false`. 43 | */ 44 | var isDeletable: Bool { get } 45 | 46 | /** 47 | A Boolean value indicating whether the item can be reorded. 48 | 49 | The default value is `false`. 50 | */ 51 | var isReordable: Bool { get } 52 | } 53 | 54 | 55 | extension OutlineItemObject { 56 | public var isExpandable: Bool { !children.isEmpty } 57 | public var isSelectable: Bool { true } 58 | public var isDeletable: Bool { false } 59 | public var isReordable: Bool { false } 60 | 61 | /// Checks if the specified item is a children of the items or the items children. 62 | public func contains(_ item: Self) -> Bool { 63 | children.contains(item) || children.contains(where: { $0.contains($0) }) 64 | } 65 | } 66 | */ 67 | 68 | /* 69 | import AppKit 70 | 71 | /// A outline view item that creates it's cell view. 72 | public protocol CellOutlineItem: Hashable { 73 | /// Returns the cell view for the item. 74 | func cellView(_ outlineView: NSOutlineView, column: NSTableColumn, row: Int) -> NSView 75 | 76 | /** 77 | A Boolean value indicating whether the item can be expanded / collapsed by the user. 78 | 79 | The default value is `true`. 80 | */ 81 | var isExpandable: Bool { get } 82 | 83 | /** 84 | A Boolean value indicating whether the item can be selected. 85 | 86 | The default value is `true`. 87 | */ 88 | var isSelectable: Bool { get } 89 | 90 | /** 91 | A Boolean value indicating whether the item can be deleted by the user pressing `Backspace`. 92 | 93 | The default value is `false`. 94 | */ 95 | var isDeletable: Bool { get } 96 | 97 | /** 98 | A Boolean value indicating whether the item can be reorded. 99 | 100 | The default value is `false`. 101 | */ 102 | var isReordable: Bool { get } 103 | 104 | /** 105 | A Boolean value indicating whether the user can insert items as children. 106 | 107 | The default value is `true`. 108 | */ 109 | var canInsertChildren: Bool { get } 110 | 111 | /** 112 | A Boolean value indicating whether the item is a group item. 113 | 114 | The default value is `false`. 115 | */ 116 | var isGroupItem: Bool { get } 117 | } 118 | 119 | extension CellOutlineItem { 120 | public var isExpandable: Bool { true } 121 | public var isSelectable: Bool { true } 122 | public var isDeletable: Bool { false } 123 | public var isReordable: Bool { false } 124 | public var canInsertChildren: Bool { true } 125 | public var isGroupItem: Bool { false } 126 | } 127 | 128 | /// A outline view item that creates it's cell view. 129 | public protocol RegisteredCellOutlineItem: CellOutlineItem { 130 | associatedtype Cell: NSTableCellView 131 | /// The cell registration that creates the cell view for the item. 132 | static var cellRegistration: NSTableView.CellRegistration { get } 133 | } 134 | 135 | extension RegisteredCellOutlineItem { 136 | public func cellView(_ outlineView: NSOutlineView, column: NSTableColumn, row: Int) -> NSView { 137 | outlineView.makeCellView(using: Self.cellRegistration, forColumn: column, row: row, item: self) ?? NSTableCellView() 138 | } 139 | } 140 | */ 141 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/NSOutlineView/ unused/OutlineViewDiffableDataSourceSnapshot+OutlineItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineViewDiffableDataSourceSnapshot+OutlineItem.swift 3 | // 4 | // 5 | // Created by Florian Zand on 09.01.25. 6 | // 7 | 8 | /* 9 | import AppKit 10 | import FZSwiftUtils 11 | 12 | 13 | extension OutlineViewDiffableDataSourceSnapshot where ItemIdentifierType: ExpandingOutlineItem { 14 | public mutating func append(_ items: [ItemIdentifierType], to parent: ItemIdentifierType? = nil) { 15 | validateItems(items + items.flatMap({ $0.descendants() })) 16 | if let parent = parent { 17 | validateItem(parent, "Parent item does not exist in section snapshot: ") 18 | nodes[parent]?.children.append(contentsOf: items) 19 | } else { 20 | rootItems.append(contentsOf: items) 21 | } 22 | items.forEach({ nodes[$0] = Node(parent: parent) }) 23 | items.forEach({ nodes.merge($0.nodes()) }) 24 | updateOrderedItems() 25 | } 26 | 27 | private mutating func insert(_ items: [ItemIdentifierType], to item: ItemIdentifierType, before: Bool) { 28 | validateItem(item, "ItemIdentifierType to insert \(before ? "before" : "after") does not exist in section snapshot: ") 29 | validateItems(items + items.flatMap({ $0.descendants() })) 30 | if let rootIndex = rootItems.firstIndex(of: item) { 31 | rootItems.insert(contentsOf: items, at: before ? rootIndex : rootIndex + 1) 32 | items.forEach { nodes[$0] = .init() } 33 | } else if let parent = parent(of: item), let childIndex = nodes[parent]?.children.firstIndex(of: item) { 34 | nodes[parent]?.children.insert(contentsOf: items, at: before ? childIndex : childIndex + 1) 35 | items.forEach { nodes[$0] = .init(parent: parent) } 36 | } 37 | items.forEach({ nodes.merge($0.nodes()) }) 38 | updateOrderedItems() 39 | } 40 | 41 | private mutating func insert(_ snapshot: OutlineViewDiffableDataSourceSnapshot, to item: ItemIdentifierType, before: Bool) { 42 | validateItem(item, "ItemIdentifierType to insert \(before ? "before" : "after") does not exist in section snapshot: ") 43 | validateItems(snapshot.items + snapshot.items.flatMap({ $0.descendants() })) 44 | if let rootIndex = rootItems.firstIndex(of: item) { 45 | rootItems.insert(contentsOf: snapshot.rootItems, at: before ? rootIndex : rootIndex + 1) 46 | nodes.merge(snapshot.nodes) 47 | } else if let parentItem = parent(of: item), 48 | let childIndex = nodes[parentItem]?.children.firstIndex(of: item) { 49 | nodes[parentItem]?.children.insert(contentsOf: snapshot.rootItems, at: before ? childIndex : childIndex + 1) 50 | nodes.merge(snapshot.nodes) 51 | snapshot.rootItems.forEach({ nodes[$0]?.parent = parentItem }) 52 | } 53 | items.forEach({ nodes.merge($0.nodes()) }) 54 | updateOrderedItems() 55 | } 56 | } 57 | 58 | extension ExpandingOutlineItem { 59 | func descendants() -> [Self] { 60 | var items: [Self] = [] 61 | for child in children { 62 | items.append(child) 63 | items.append(contentsOf: child.descendants()) 64 | } 65 | return items 66 | } 67 | 68 | 69 | func nodes() -> [Self: OutlineViewDiffableDataSourceSnapshot.Node] { 70 | var nodes: [Self: OutlineViewDiffableDataSourceSnapshot.Node] = [:] 71 | func setup(for child: Self, parent: Self?) { 72 | nodes[child] = OutlineViewDiffableDataSourceSnapshot.Node(parent: parent, children: child.children, isExpanded: child.isExpanded) 73 | for _child in child.children { 74 | setup(for: _child, parent: child) 75 | } 76 | } 77 | setup(for: self, parent: nil) 78 | return nodes 79 | } 80 | } 81 | */ 82 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/NSOutlineView/Snapshot/OutlineChangeInstruction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineChangeInstruction.swift 3 | // 4 | // 5 | // Created by Florian Zand on 09.01.25. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | enum OutlineChangeInstruction: CustomStringConvertible, Hashable, Equatable { 12 | case insert(_ item: AnyHashable, at: Int, parent: AnyHashable?) 13 | case remove(_ item: AnyHashable, at: Int, parent: AnyHashable?) 14 | case move(_ item: AnyHashable, from: Int, _ fromParent: AnyHashable?, to: Int, _ toParent: AnyHashable?) 15 | 16 | var description: String { 17 | switch self { 18 | case .insert(let item, let index, let parent): 19 | let parent = "\(parent != nil ? "\(parent!)" : "Root")" 20 | return "insert \"\(item)\" in \"\(parent)\" at \(index)" 21 | case .remove(let item, let index, let parent): 22 | let parent = "\(parent != nil ? "\(parent!)" : "Root")" 23 | return "Remove \"\(item)\"from \"\(parent)\" at \(index)" 24 | case .move(let item, let from, let fromParent, let to, let toParent): 25 | let fromParent = "\(fromParent != nil ? "\(fromParent!)" : "Root")" 26 | let toParent = "\(toParent != nil ? "\(toParent!)" : "Root")" 27 | return "Move \"\(item)\" from \"\(fromParent)\" at \(from) to \"\(toParent)\" at \(to)" 28 | } 29 | } 30 | } 31 | 32 | extension OutlineViewDiffableDataSourceSnapshot { 33 | func instructions(forMorphingTo newSnapshot: OutlineViewDiffableDataSourceSnapshot) -> [OutlineChangeInstruction] { 34 | var movedItems: Set = [] 35 | var work = self 36 | work.isCalculatingDiff = true 37 | func calculateSteps(from source: [Item], to destination: [Item], parent: Item? = nil) -> [OutlineChangeInstruction] { 38 | var instructions: [OutlineChangeInstruction] = [] 39 | for step in destination.difference(from: source).steps { 40 | switch step { 41 | case .insert(let item, let index): 42 | if let fromIndex = work.childIndex(of: item) { 43 | guard !movedItems.contains(item) else { continue } 44 | movedItems.insert(item) 45 | instructions.append(.move(item, from: fromIndex, work.parent(of: item), to: index, parent)) 46 | work.move([item], toIndex: index, of: parent) 47 | } else { 48 | instructions.append(.insert(item, at: index, parent: parent)) 49 | work.insert(item, at: index, of: parent) 50 | } 51 | case .remove(let item, let index): 52 | if let toIndex = newSnapshot.childIndex(of: item) { 53 | guard !movedItems.contains(item) else { continue } 54 | movedItems.insert(item) 55 | let newParent = newSnapshot.parent(of: item) 56 | instructions.append(.move(item, from: index, work.parent(of: item), to: toIndex, newParent)) 57 | work.move([item], toIndex: toIndex, of: newParent) 58 | } else { 59 | instructions.append(.remove(item, at: index, parent: parent)) 60 | work.delete([item]) 61 | } 62 | case .move(let item, let from, let to): 63 | instructions.append(.move(item, from: from, parent, to: to, parent)) 64 | work.move([item], toIndex: to, of: parent) 65 | } 66 | } 67 | for item in destination { 68 | instructions += calculateSteps(from: work.children(of: item), to: newSnapshot.children(of: item), parent: item) 69 | } 70 | return instructions 71 | } 72 | let instructions = calculateSteps(from: rootItems, to: newSnapshot.rootItems) 73 | return instructions 74 | } 75 | } 76 | 77 | extension NSOutlineView { 78 | func apply(_ snapshot: OutlineViewDiffableDataSourceSnapshot, currentSnapshot: OutlineViewDiffableDataSourceSnapshot, option: NSDiffableDataSourceSnapshotApplyOption, animation: AnimationOptions, completion: (() -> Void)?) { 79 | func applySnapshot() { 80 | beginUpdates() 81 | for instruction in currentSnapshot.instructions(forMorphingTo: snapshot) { 82 | switch instruction { 83 | case .insert(_, let index, let parent): 84 | insertItems(at: IndexSet(integer: index), inParent: parent, withAnimation: animation) 85 | case .remove(_, let index, let parent): 86 | removeItems(at: IndexSet(integer: index), inParent: parent, withAnimation: animation) 87 | case .move(_, let from, let fromParent, let to, let toParent): 88 | moveItem(at: from, inParent: fromParent, to: to, inParent: toParent) 89 | } 90 | } 91 | expandCollapseItems() 92 | endUpdates() 93 | } 94 | 95 | func expandCollapseItems() { 96 | let oldExpanded = currentSnapshot.expandedItems 97 | let newExpanded = snapshot.expandedItems 98 | oldExpanded.subtracting(newExpanded).forEach({ animator().collapseItem($0) }) 99 | newExpanded.subtracting(oldExpanded).forEach({ animator().expandItem($0) }) 100 | } 101 | 102 | if option.isReloadData { 103 | reloadData() 104 | expandCollapseItems() 105 | completion?() 106 | } else if let duration = option.animationDuration, duration > 0.0 { 107 | NSView.animate(withDuration: duration, { 108 | applySnapshot() 109 | }, completion: completion) 110 | } else { 111 | NSView.performWithoutAnimation { 112 | applySnapshot() 113 | completion?() 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/NSOutlineView/Snapshot/OutlineViewDiffableDataSourceSnapshot+Node.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineViewDiffableDataSourceSnapshot+Node.swift 3 | // 4 | // 5 | // Created by Florian Zand on 20.01.25. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | A outline node that can be used to construct the items of a `OutlineViewDiffableDataSourceSnapshot`. 12 | 13 | Example usage: 14 | ```swift 15 | var snapshot = OutlineViewDiffableDataSourceSnapshot { 16 | OutlineNode("Food") { 17 | OutlineNode("Root 1 Child 1") 18 | OutlineNode("Root 1 Child 2") 19 | OutlineNode("Root 1 Child 3") 20 | }.isExpanded(true) 21 | OutlineNode("Root 2") 22 | OutlineNode("Root 3") { 23 | OutlineNode("Root 3 Child 1") 24 | OutlineNode("Root 3 Child 2") 25 | } 26 | } 27 | ``` 28 | */ 29 | public struct OutlineNode { 30 | /// The item of the node. 31 | public let item: Item 32 | 33 | /// The children of the node. 34 | public let children: [OutlineNode] 35 | 36 | /// A Boolean value indicating whether the item of the node is expanded. 37 | public let isExpanded: Bool 38 | 39 | /// Sets the Boolean value indicating whether the item of the node is expanded. 40 | public func isExpanded(_ isExpanded: Bool) -> Self { 41 | .init(item, children: children, isExpanded: isExpanded) 42 | } 43 | 44 | /* 45 | /// A Boolean value indicating whether the node contains the item. 46 | public func contains(_ item: Item) -> Bool { 47 | children.contains(where: { $0.item == item || $0.contains(item) }) 48 | } 49 | */ 50 | 51 | /// Creates a node with the specified item. 52 | public init(_ item: Item) { 53 | self.item = item 54 | self.children = [] 55 | self.isExpanded = false 56 | } 57 | 58 | /// Creates a node with the specified item and children. 59 | public init(_ item: Item, @Builder _ children: () -> [OutlineNode]) { 60 | self.item = item 61 | self.children = children() 62 | self.isExpanded = false 63 | } 64 | 65 | init(_ item: Item, children: [OutlineNode], isExpanded: Bool) { 66 | self.item = item 67 | self.children = children 68 | self.isExpanded = isExpanded 69 | } 70 | } 71 | 72 | extension OutlineViewDiffableDataSourceSnapshot { 73 | /** 74 | Creates a snapshot from the specified nodes. 75 | 76 | Example usage: 77 | ```swift 78 | var snapshot = OutlineViewDiffableDataSourceSnapshot { 79 | OutlineNode("Food") { 80 | OutlineNode("Root 1 Child 1") 81 | OutlineNode("Root 1 Child 2") 82 | OutlineNode("Root 1 Child 3") 83 | }.isExpanded(true) 84 | OutlineNode("Root 2") 85 | OutlineNode("Root 3") { 86 | OutlineNode("Root 3 Child 1") 87 | OutlineNode("Root 3 Child 2") 88 | } 89 | } 90 | ``` 91 | */ 92 | public init(@OutlineNode.Builder nodes: () -> [OutlineNode]) { 93 | apply(nodes()) 94 | } 95 | 96 | /// Adds the items of the specified nodes as child items of the specified parent item in the snapshot. 97 | public mutating func append(@OutlineNode.Builder _ nodes: () -> [OutlineNode], to parent: Item? = nil) { 98 | apply(nodes(), to: parent) 99 | } 100 | 101 | /// Inserts the items of the provided nodes immediately after the item with the specified identifier in the snapshot. 102 | public mutating func insert(@OutlineNode.Builder _ nodes: () -> [OutlineNode], after item: Item) { 103 | let nodes = nodes() 104 | insert(nodes.compactMap({$0.item}), after: item) 105 | nodes.forEach({ apply($0.children, to: $0.item) }) 106 | } 107 | 108 | /// Inserts the items of the provided nodes immediately before the item with the specified identifier in the snapshot. 109 | public mutating func insert(@OutlineNode.Builder _ nodes: () -> [OutlineNode], before item: Item) { 110 | let nodes = nodes() 111 | insert(nodes.compactMap({$0.item}), before: item) 112 | nodes.forEach({ apply($0.children, to: $0.item) }) 113 | } 114 | 115 | mutating func apply(_ nodes: [OutlineNode], to item: Item? = nil) { 116 | append(nodes.compactMap({$0.item}), to: item) 117 | nodes.forEach({ self.nodes[$0.item]?.isExpanded = $0.isExpanded }) 118 | nodes.forEach({ apply($0.children, to: $0.item) }) 119 | } 120 | } 121 | 122 | extension OutlineNode { 123 | /// A function builder type that produces an array of nodes. 124 | @resultBuilder 125 | public enum Builder { 126 | public static func buildBlock(_ block: [OutlineNode]...) -> [OutlineNode] { 127 | block.flatMap { $0 } 128 | } 129 | 130 | public static func buildOptional(_ item: [OutlineNode]?) -> [OutlineNode] { 131 | item ?? [] 132 | } 133 | 134 | public static func buildEither(first: [OutlineNode]?) -> [OutlineNode] { 135 | first ?? [] 136 | } 137 | 138 | public static func buildEither(second: [OutlineNode]?) -> [OutlineNode] { 139 | second ?? [] 140 | } 141 | 142 | public static func buildArray(_ components: [[OutlineNode]]) -> [OutlineNode] { 143 | components.flatMap { $0 } 144 | } 145 | 146 | public static func buildExpression(_ expr: [OutlineNode]?) -> [OutlineNode] { 147 | expr ?? [] 148 | } 149 | 150 | public static func buildExpression(_ expr: OutlineNode?) -> [OutlineNode] { 151 | expr.map { [$0] } ?? [] 152 | } 153 | 154 | public static func buildExpression(_ expr: Item?) -> [OutlineNode] { 155 | if let item = expr { 156 | return [.init(item)] 157 | } 158 | return [] 159 | } 160 | 161 | public static func buildExpression(_ expr: [Item]?) -> [OutlineNode] { 162 | expr?.compactMap({.init($0)}) ?? [] 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/NSOutlineView/Snapshot/OutlineViewDiffableDataSourceTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutlineViewDiffableDataSourceTransaction.swift 3 | // 4 | // 5 | // Created by Florian Zand on 09.01.25. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A transaction that describes the changes after reordering the items of a outline view.. 11 | public struct OutlineViewDiffableDataSourceTransaction { 12 | /// The section snapshot before the transaction occured. 13 | public let initialSnapshot: OutlineViewDiffableDataSourceSnapshot 14 | 15 | /// The section snapshot after the transaction occured. 16 | public let finalSnapshot: OutlineViewDiffableDataSourceSnapshot 17 | 18 | /// A collection of insertions and removals that describe the difference between initial and final snapshots. 19 | public let difference: CollectionDifference 20 | } 21 | 22 | extension OutlineViewDiffableDataSourceTransaction { 23 | init(initial: OutlineViewDiffableDataSourceSnapshot, final: OutlineViewDiffableDataSourceSnapshot) { 24 | initialSnapshot = initial 25 | finalSnapshot = final 26 | difference = initial.items.difference(from: final.items) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/NSTableView/TableViewDiffableDataSource+Delegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewDiffableDataSource+Delegate.swift 3 | // 4 | // 5 | // Created by Florian Zand on 31.08.23. 6 | // 7 | 8 | import AppKit 9 | import FZUIKit 10 | 11 | extension TableViewDiffableDataSource { 12 | class Delegate: NSObject, NSTableViewDelegate { 13 | weak var dataSource: TableViewDiffableDataSource! 14 | var previousSelectedIDs: [Item.ID] = [] 15 | 16 | init(_ dataSource: TableViewDiffableDataSource) { 17 | self.dataSource = dataSource 18 | super.init() 19 | dataSource.tableView.delegate = self 20 | } 21 | 22 | // MARK: - Selecting 23 | 24 | public func tableViewSelectionDidChange(_: Notification) { 25 | guard dataSource.selectionHandlers.didSelect != nil || dataSource.selectionHandlers.didDeselect != nil else { 26 | previousSelectedIDs = dataSource.selectedItems.ids 27 | return 28 | } 29 | let selectedIDs = dataSource.selectedItems.ids 30 | let diff = previousSelectedIDs.difference(to: selectedIDs) 31 | 32 | if diff.added.isEmpty == false, let didSelect = dataSource.selectionHandlers.didSelect { 33 | let selectedItems = dataSource.items[ids: diff.added] 34 | didSelect(selectedItems) 35 | } 36 | 37 | if diff.removed.isEmpty == false, let didDeselect = dataSource.selectionHandlers.didDeselect { 38 | let deselectedItems = dataSource.items[ids: diff.removed] 39 | if deselectedItems.isEmpty == false { 40 | didDeselect(deselectedItems) 41 | } 42 | } 43 | previousSelectedIDs = selectedIDs 44 | } 45 | 46 | public func tableView(_ tableView: NSTableView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet { 47 | var proposedSelectionIndexes = proposedSelectionIndexes 48 | let isEmpty = proposedSelectionIndexes.isEmpty 49 | dataSource.sectionRowIndexes.forEach { proposedSelectionIndexes.remove($0) } 50 | if !isEmpty, proposedSelectionIndexes.isEmpty { 51 | return dataSource.tableView.selectedRowIndexes 52 | } 53 | guard dataSource.selectionHandlers.shouldSelect != nil || dataSource.selectionHandlers.shouldDeselect != nil else { 54 | return proposedSelectionIndexes 55 | } 56 | let selectedRows = Array(dataSource.tableView.selectedRowIndexes) 57 | let proposedRows = Array(proposedSelectionIndexes) 58 | 59 | let diff = selectedRows.difference(to: proposedRows) 60 | let selectedItems = diff.added.compactMap { dataSource.item(forRow: $0) } 61 | let deselectedItems = diff.removed.compactMap { dataSource.item(forRow: $0) } 62 | 63 | var selections: [Item] = selectedItems 64 | if !selectedItems.isEmpty, let shouldSelectRows = dataSource.selectionHandlers.shouldSelect?(selectedItems) { 65 | selections = selectedItems.filter({ shouldSelectRows.contains($0) }) 66 | } 67 | 68 | if !deselectedItems.isEmpty, let shouldDeselectRows = dataSource.selectionHandlers.shouldDeselect?(deselectedItems) { 69 | selections += deselectedItems.filter({ !shouldDeselectRows.contains($0) }) 70 | } 71 | 72 | return IndexSet(selections.compactMap { dataSource.row(for: $0) }) 73 | } 74 | 75 | // MARK: - View 76 | 77 | public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 78 | dataSource.dataSource.tableView(tableView, viewFor: tableColumn, row: row) 79 | } 80 | 81 | public func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { 82 | dataSource.dataSource.tableView(tableView, isGroupRow: row) 83 | } 84 | 85 | public func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { 86 | dataSource.dataSource.tableView(tableView, rowViewForRow: row) 87 | } 88 | 89 | // MARK: - Row Action 90 | 91 | public func tableView(_: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] { 92 | if let rowActionProvider = dataSource.rowActionProvider, let item = dataSource.item(forRow: row) { 93 | return rowActionProvider(item, edge) 94 | } 95 | return [] 96 | } 97 | 98 | // MARK: - Column 99 | 100 | func tableView(_ tableView: NSTableView, userCanChangeVisibilityOf column: NSTableColumn) -> Bool { 101 | dataSource.columnHandlers.userCanChangeVisibility?(column) ?? false 102 | } 103 | 104 | func tableView(_ tableView: NSTableView, userDidChangeVisibilityOf columns: [NSTableColumn]) { 105 | dataSource.columnHandlers.userDidChangeVisibility?(columns) 106 | } 107 | 108 | public func tableViewColumnDidMove(_ notification: Notification) { 109 | guard let didReorder = dataSource.columnHandlers.didReorder else { return } 110 | guard let oldPos = notification.userInfo?["NSOldColumn"] as? Int, 111 | let newPos = notification.userInfo?["NSNewColumn"] as? Int, 112 | let tableColumn = dataSource.tableView.tableColumns[safe: newPos] else { return } 113 | didReorder(tableColumn, oldPos, newPos) 114 | } 115 | 116 | public func tableViewColumnDidResize(_ notification: Notification) { 117 | guard let didResize = dataSource.columnHandlers.didResize else { return } 118 | guard let tableColumn = notification.userInfo?["NSTableColumn"] as? NSTableColumn, let oldWidth = notification.userInfo?["NSOldWidth"] as? CGFloat else { return } 119 | didResize(tableColumn, oldWidth) 120 | } 121 | 122 | public func tableView(_: NSTableView, shouldReorderColumn columnIndex: Int, toColumn newColumnIndex: Int) -> Bool { 123 | guard let tableColumn = dataSource.tableView.tableColumns[safe: columnIndex] else { return true } 124 | return dataSource.columnHandlers.shouldReorder?(tableColumn, newColumnIndex) ?? true 125 | } 126 | 127 | func tableView(_ tableView: NSTableView, didClick tableColumn: NSTableColumn) { 128 | dataSource.columnHandlers.didClick?(tableColumn) 129 | } 130 | 131 | func tableView(_ tableView: NSTableView, mouseDownInHeaderOf tableColumn: NSTableColumn) { 132 | dataSource.columnHandlers.didClickHeader?(tableColumn) 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/Shared/DiffableDataSourceTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffableDataSourceTransaction.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.09.23. 6 | // 7 | 8 | import AppKit 9 | 10 | /// A transaction that describes the changes after reordering the items in the view. 11 | public struct DiffableDataSourceTransaction where Section: Hashable, Item: Hashable { 12 | /// The snapshot before the transaction occured. 13 | public let initialSnapshot: NSDiffableDataSourceSnapshot 14 | 15 | /// The snapshot after the transaction occured. 16 | public let finalSnapshot: NSDiffableDataSourceSnapshot 17 | 18 | /// A collection of insertions and removals that describe the difference between initial and final snapshots. 19 | public let difference: CollectionDifference 20 | } 21 | 22 | extension DiffableDataSourceTransaction { 23 | init(initial: NSDiffableDataSourceSnapshot, final: NSDiffableDataSourceSnapshot) { 24 | initialSnapshot = initial 25 | finalSnapshot = final 26 | difference = initial.itemIdentifiers.difference(from: final.itemIdentifiers) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/Shared/EmptyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyView.swift 3 | // 4 | // 5 | // Created by Florian Zand on 25.10.24. 6 | // 7 | 8 | import AppKit 9 | import FZUIKit 10 | 11 | /// View that is displayed if a table or collection view is empty. 12 | class EmptyView: NSView { 13 | 14 | var view: NSView? { 15 | didSet { 16 | guard oldValue != view else { return } 17 | oldValue?.removeFromSuperview() 18 | if let view = view { 19 | view.frame.size = bounds.size 20 | addSubview(view) 21 | } 22 | } 23 | } 24 | 25 | var configuration: NSContentConfiguration? { 26 | get { (view as? NSContentView)?.configuration } 27 | set { 28 | if let newValue = newValue { 29 | if let view = view as? NSContentView, view.supports(newValue) { 30 | view.configuration = newValue 31 | } else { 32 | view = newValue.makeContentView() 33 | } 34 | } else { 35 | view = nil 36 | } 37 | } 38 | } 39 | 40 | public init(view: NSView) { 41 | super.init(frame: .zero) 42 | defer { self.view = view } 43 | } 44 | 45 | public init(configuration: NSContentConfiguration) { 46 | super.init(frame: .zero) 47 | defer { self.configuration = configuration } 48 | } 49 | 50 | required init?(coder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | override func layout() { 55 | super.layout() 56 | view?.frame.size = bounds.size 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/DiffableDataSource/Shared/QuicklookPreviewItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuicklookPreviewItem.swift 3 | // 4 | // 5 | // Created by Florian Zand on 15.09.23. 6 | // 7 | 8 | import AppKit 9 | import FZQuicklook 10 | import QuickLookUI 11 | 12 | /// Quicklook item for selected collection view items and table view cells. 13 | class QuicklookPreviewItem: NSObject, QLPreviewItem, QuicklookPreviewable { 14 | let preview: QuicklookPreviewable 15 | weak var view: NSView? 16 | 17 | public var previewItemURL: URL? { 18 | preview.previewItemURL 19 | } 20 | 21 | public var previewItemFrame: CGRect? { 22 | if let view = self.view { 23 | if let collectionViewItem = view.parentController as? NSCollectionViewItem { 24 | if let view = view as? NSItemContentView, view.appliedConfiguration.image != nil { 25 | let imageView = view.contentView.imageView 26 | if collectionViewItem.collectionView?.visibleRect.intersects(imageView.frame) == true { 27 | return imageView.frameOnScreen 28 | } 29 | } else if collectionViewItem.collectionView?.visibleRect.intersects(view.frame) == true { 30 | return view.frameOnScreen 31 | } 32 | } else if let view = view as? NSTableRowView { 33 | if view.tableView?.visibleRect.intersects(view.frame) == true { 34 | return view.frameOnScreen 35 | } 36 | } else { 37 | return view.frameOnScreen 38 | } 39 | return .zero 40 | } 41 | return preview.previewItemFrame 42 | } 43 | 44 | public var previewItemTitle: String? { 45 | preview.previewItemTitle 46 | } 47 | 48 | public var previewItemTransitionImage: NSImage? { 49 | if let view = view as? NSItemContentView, view.appliedConfiguration.image != nil { 50 | return view.contentView.imageView.renderedImage 51 | } 52 | return view?.renderedImage ?? preview.previewItemTransitionImage 53 | } 54 | 55 | init(_ preview: QuicklookPreviewable, view: NSView? = nil) { 56 | self.preview = preview 57 | self.view = view 58 | } 59 | } 60 | 61 | extension NSPasteboard.PasteboardType { 62 | /// Collection view and table view drag & drop type. 63 | static let itemID: NSPasteboard.PasteboardType = .init("DiffableDataSource.ItemID") 64 | } 65 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/AdvancedCollectionTableView.md: -------------------------------------------------------------------------------- 1 | # ``AdvancedCollectionTableView`` 2 | 3 | A collection of classes and extensions for `NSCollectionView` and `NSTableView`, many of them ported from modern `UIKit`. 4 | 5 | ## Overview 6 | 7 | `AdvancedCollectionTableView` provides a collection of classes and extensions for `NSCollectionView` and `NSTableView`. 8 | 9 | ## Topics 10 | 11 | ### Collection- & TableView Data Sources 12 | 13 | - ``CollectionViewDiffableDataSource`` 14 | - ``TableViewDiffableDataSource`` 15 | - ``DiffableDataSourceTransaction`` 16 | - ``NSDiffableDataSourceSnapshotApplyOption`` 17 | 18 | ### OutlineView Data Source 19 | 20 | - ``OutlineViewDiffableDataSource`` 21 | - ``OutlineViewDiffableDataSourceSnapshot`` 22 | - ``OutlineViewDiffableDataSourceTransaction`` 23 | 24 | ### CollectionView Registration 25 | 26 | - ``AppKit/NSCollectionView/ItemRegistration`` 27 | - ``AppKit/NSCollectionView/SupplementaryRegistration`` 28 | 29 | ### TableView Registration 30 | 31 | - ``AppKit/NSTableView/CellRegistration`` 32 | - ``AppKit/NSTableView/RowRegistration`` 33 | 34 | ### Item Content Configuration 35 | 36 | - 37 | - ``NSItemContentConfiguration`` 38 | - ``NSItemContentView`` 39 | - ``NSItemConfigurationState`` 40 | 41 | ### List Content Configuration 42 | 43 | - 44 | - ``NSListContentConfiguration`` 45 | - ``NSListContentView`` 46 | - ``NSListConfigurationState`` 47 | 48 | ### Collection View Extensions 49 | 50 | - 51 | - 52 | - 53 | - ``NSCollectionViewSupplementaryRegistration`` 54 | 55 | ### Table View Extensions 56 | 57 | - 58 | - 59 | - 60 | - 61 | - ``NSTableViewCellRegistration`` 62 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Docs/Configurating Collection Items.md: -------------------------------------------------------------------------------- 1 | # Configurating Collection View Items 2 | 3 | Configurate the content and background of a collection view item. 4 | 5 | ## Overview 6 | 7 | The content and background of a `NSCollectionViewItem` can be configurated by providing a `NSContentConfiguration` to an item's ``AppKit/NSCollectionViewItem/contentConfiguration`` and ``AppKit/NSCollectionViewItem/backgroundConfiguration``. 8 | 9 | ## Item content configuration 10 | 11 | - ``NSItemContentConfiguration`` 12 | - ``NSItemContentView`` 13 | 14 | ``NSItemContentConfiguration`` is a content configuration suitable for a collection view item. It displays an image and/or view with a text and secondary text. 15 | 16 | ![An item content configuration](NSItemContentConfiguration.png) 17 | 18 | ```swift 19 | var content = NSItemContentConfiguration() 20 | 21 | // Configure content. 22 | content.image = NSImage(named: "Mozart") 23 | content.text = "Mozart" 24 | content.secondaryText = "A genius composer" 25 | 26 | // Customize appearance. 27 | content.textProperties.font = .body 28 | content.secondaryTextProperties.font = .caption1 29 | 30 | collectionViewItem.contentConfiguration = content 31 | ``` 32 | 33 | ## Managing the content 34 | 35 | To manage the content of the item you provide a `NSContentConfiguration` to items `contentConfiguration`. 36 | 37 | - ``AppKit/NSCollectionViewItem/contentConfiguration`` 38 | - ``AppKit/NSCollectionViewItem/automaticallyUpdatesContentConfiguration`` 39 | 40 | ## Configuring the background 41 | 42 | To configurate the background of the item you provide a `NSContentConfiguration` to items `backgroundConfiguration`. 43 | 44 | - ``AppKit/NSCollectionViewItem/backgroundConfiguration`` 45 | - ``AppKit/NSCollectionViewItem/automaticallyUpdatesContentConfiguration`` 46 | 47 | ## Managing the state 48 | 49 | `configurationState` provides the current state of an item (e.g. `isSelected` or `highlightState). 50 | 51 | - ``AppKit/NSCollectionViewItem/configurationState`` 52 | - ``AppKit/NSCollectionViewItem/configurationUpdateHandler-swift.property`` 53 | 54 | ### Handling updates to the state 55 | 56 | To handle updates of an item’s state, provide a handler to the items `configurationUpdateHandler`. 57 | 58 | - ``NSItemConfigurationState`` 59 | - ``AppKit/NSCollectionViewItem/ConfigurationUpdateHandler-swift.typealias`` 60 | 61 | **Example usage of the configuration update handler:** 62 | 63 | ```swift 64 | var content = NSItemContentConfiguration() 65 | content.text = "Mozart" 66 | content.image = NSImage(named: "Mozart") 67 | 68 | collectionViewItem.contentConfiguration = content 69 | 70 | collectionViewItem.configurationUpdateHandler = { item, state in 71 | if state.isSelected { 72 | content.contentProperties.borderWidth = 1.0 73 | content.contentProperties.borderColor = .controlAccentColor 74 | } else { 75 | content.contentProperties.borderWidth = 0.0 76 | content.contentProperties.borderColor = nil 77 | } 78 | collectionViewItem.contentConfiguration = content 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Docs/Configurating Table Cells.md: -------------------------------------------------------------------------------- 1 | # Configurating Table Cells 2 | 3 | Configurate the content of a table view cell. 4 | 5 | ## Overview 6 | 7 | The content of a `NSTableCellView` can be configurated by providing a `NSContentConfiguration` to a table cell's ``AppKit/NSTableCellView/contentConfiguration``. 8 | 9 | ## Topics 10 | 11 | ### Table cell content configuration 12 | 13 | - ``NSListContentConfiguration`` 14 | - ``NSListContentView`` 15 | 16 | ``NSListContentConfiguration`` is a content configuration suitable for a table row. It can display a text, secondary text, image and view. 17 | 18 | ![A list content configuration](NSListContentConfiguration.png) 19 | 20 | ```swift 21 | var content = tableCell.defaultContentConfiguration() 22 | 23 | // Configure content. 24 | content.image = NSImage(systemSymbolName: "star") 25 | content.text = "Favorites" 26 | 27 | // Customize appearance. 28 | content.imageProperties.tintColor = .purple 29 | 30 | tableCell.contentConfiguration = content 31 | ``` 32 | 33 | ### Managing the content 34 | 35 | To manage the content of the cell you provide a `NSContentConfiguration` to cells `contentConfiguration`. 36 | 37 | - ``AppKit/NSTableCellView/contentConfiguration`` 38 | - ``AppKit/NSTableCellView/defaultContentConfiguration()`` 39 | - ``AppKit/NSTableCellView/automaticallyUpdatesContentConfiguration`` 40 | 41 | ### Managing the state 42 | 43 | `configurationState` provides the current state of a table view cell (e.g. `isSelected` or `isHovered`). 44 | 45 | - ``AppKit/NSTableCellView/configurationState`` 46 | - ``NSListConfigurationState`` 47 | - ``AppKit/NSTableCellView/setNeedsUpdateConfiguration()`` 48 | - ``AppKit/NSTableCellView/updateConfiguration(using:)`` 49 | 50 | ### Handling updates to the state 51 | 52 | To handle updates of a table view cell’s state, provide a handler to the cells `configurationUpdateHandler`. 53 | 54 | - ``AppKit/NSTableCellView/configurationUpdateHandler-swift.property`` 55 | - ``AppKit/NSTableCellView/ConfigurationUpdateHandler-swift.typealias`` 56 | 57 | **Example usage of the configuration update handler:** 58 | 59 | ```swift 60 | var content = tableCell.defaultContentConfiguration() 61 | content.image = NSImage(systemSymbolName: "star") 62 | content.imageProperties.tintColor = .black 63 | 64 | tableCell.contentConfiguration = content 65 | 66 | tableCell.configurationUpdateHandler = { 67 | newState in 68 | if newState.isSelected { 69 | content.imageProperties.tintColor = .controlAccentColor 70 | } else { 71 | content.imageProperties.tintColor = .black 72 | } 73 | tableCell.contentConfiguration = content 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSCollecionView/NSCollectionView+.md: -------------------------------------------------------------------------------- 1 | # NSCollectionView 2 | 3 | Extensions for `NSCollectionView`. 4 | 5 | ## Topics 6 | 7 | ### Creating items 8 | 9 | - ``AppKit/NSCollectionView/register(_:)`` 10 | - ``AppKit/NSCollectionView/register(_:nib:)`` 11 | - ``AppKit/NSCollectionView/makeItem(for:)`` 12 | 13 | ### Creating items using item registration 14 | 15 | - ``AppKit/NSCollectionView/ItemRegistration`` 16 | - ``AppKit/NSCollectionView/makeItem(using:for:element:)`` 17 | 18 | ### Creating supplementary views 19 | 20 | - ``AppKit/NSCollectionView/register(_:forSupplementaryKind:)`` 21 | - ``AppKit/NSCollectionView/register(_:nib:forSupplementaryKind:)`` 22 | - ``AppKit/NSCollectionView/makeSupplementaryView(ofKind:for:)`` 23 | 24 | ### Creating supplementary views using supplementary registration 25 | 26 | - ``AppKit/NSCollectionView/SupplementaryRegistration`` 27 | - ``AppKit/NSCollectionView/makeSupplementaryView(using:for:)`` 28 | 29 | ### Reloading content 30 | 31 | - ``AppKit/NSCollectionView/reconfigureItems(at:)`` 32 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSCollecionView/NSCollectionViewDiffableDataSource+.md: -------------------------------------------------------------------------------- 1 | # NSCollectionViewDiffableDataSource 2 | 3 | Extensions for `NSCollectionViewDiffableDataSource`. 4 | 5 | ## Topics 6 | 7 | ### Creating a Diffable Data Source 8 | 9 | - ``AppKit/NSCollectionViewDiffableDataSource/init(collectionView:itemRegistration:)`` 10 | - ``AppKit/NSCollectionViewDiffableDataSource/init(collectionView:itemRegistration:supplementaryRegistrations:)`` 11 | 12 | ### Creating supplementary views 13 | 14 | - ``AppKit/NSCollectionViewDiffableDataSource/useSupplementaryRegistrations(_:)`` 15 | 16 | ### Updating data 17 | 18 | - ``AppKit/NSCollectionViewDiffableDataSource/apply(_:_:completion:)`` 19 | 20 | ### Supporting deleting 21 | 22 | - ``AppKit/NSCollectionViewDiffableDataSource/deletingHandlers-swift.property`` 23 | - ``AppKit/NSCollectionViewDiffableDataSource/DeletingHandlers-swift.struct`` 24 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSCollecionView/NSCollectionViewDiffableDataSource+DeletingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``AppKit/NSCollectionViewDiffableDataSource/DeletingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Deleting items 6 | 7 | - ``canDelete`` 8 | - ``willDelete`` 9 | - ``didDelete`` 10 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSCollecionView/NSCollectionViewItem+.md: -------------------------------------------------------------------------------- 1 | # NSCollectionViewItem 2 | 3 | Extensions for `NSCollectionViewItem`. 4 | 5 | ## Topics 6 | 7 | ### Configuring the background 8 | 9 | - ``AppKit/NSCollectionViewItem/defaultBackgroundConfiguration()`` 10 | - ``AppKit/NSCollectionViewItem/backgroundConfiguration`` 11 | - ``AppKit/NSCollectionViewItem/automaticallyUpdatesBackgroundConfiguration`` 12 | 13 | ### Managing the content 14 | 15 | - ``AppKit/NSCollectionViewItem/contentConfiguration`` 16 | - ``AppKit/NSCollectionViewItem/automaticallyUpdatesContentConfiguration`` 17 | 18 | ### Managing the state 19 | 20 | - ``AppKit/NSCollectionViewItem/configurationState`` 21 | - ``AppKit/NSCollectionViewItem/setNeedsUpdateConfiguration()`` 22 | - ``AppKit/NSCollectionViewItem/updateConfiguration(using:)`` 23 | - ``AppKit/NSCollectionViewItem/configurationUpdateHandler-swift.property`` 24 | - ``AppKit/NSCollectionViewItem/ConfigurationUpdateHandler-swift.typealias`` 25 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSCollecionView/NSCollectionViewSupplementaryRegistration.md: -------------------------------------------------------------------------------- 1 | # ``NSCollectionViewSupplementaryRegistration`` 2 | 3 | ## Topics 4 | 5 | ### Supplementary kind 6 | 7 | - ``elementKind`` 8 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSTableView/NSTableCellView+.md: -------------------------------------------------------------------------------- 1 | # NSTableCellView 2 | 3 | Extensions for `NSTableCellView`. 4 | 5 | ## Topics 6 | 7 | ### Managing the content 8 | 9 | - ``AppKit/NSTableCellView/defaultContentConfiguration()`` 10 | - ``AppKit/NSTableCellView/contentConfiguration`` 11 | - ``AppKit/NSTableCellView/automaticallyUpdatesContentConfiguration`` 12 | 13 | ### Managing the state 14 | 15 | - ``AppKit/NSTableCellView/configurationState`` 16 | - ``AppKit/NSTableCellView/setNeedsUpdateConfiguration()`` 17 | - ``AppKit/NSTableCellView/updateConfiguration(using:)`` 18 | - ``AppKit/NSTableCellView/configurationUpdateHandler-swift.property`` 19 | - ``AppKit/NSTableCellView/ConfigurationUpdateHandler-swift.typealias`` 20 | 21 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSTableView/NSTableRowView+.md: -------------------------------------------------------------------------------- 1 | # NSTableRowView 2 | 3 | Extensions for `NSTableRowView`. 4 | 5 | ## Topics 6 | 7 | ### Managing the content 8 | 9 | - ``AppKit/NSTableRowView/contentConfiguration`` 10 | - ``AppKit/NSTableRowView/automaticallyUpdatesContentConfiguration`` 11 | 12 | ### Managing the state 13 | 14 | - ``AppKit/NSTableRowView/configurationState`` 15 | - ``AppKit/NSTableRowView/setNeedsUpdateConfiguration()`` 16 | - ``AppKit/NSTableRowView/updateConfiguration(using:)`` 17 | - ``AppKit/NSTableRowView/configurationUpdateHandler-swift.property`` 18 | - ``AppKit/NSTableRowView/ConfigurationUpdateHandler-swift.typealias`` 19 | 20 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSTableView/NSTableView+.md: -------------------------------------------------------------------------------- 1 | # NSTableView 2 | 3 | Extensions for `NSTableView`. 4 | 5 | ## Topics 6 | 7 | ### Creating table cell views 8 | 9 | - ``AppKit/NSTableView/register(_:)`` 10 | - ``AppKit/NSTableView/makeView(for:)`` 11 | 12 | ### Creating table cell views using cell registration 13 | 14 | - ``AppKit/NSTableView/CellRegistration`` 15 | - ``AppKit/NSTableView/makeCellView(using:forColumn:row:item:)`` 16 | 17 | ### Creating table row views 18 | 19 | - ``AppKit/NSTableView/RowRegistration`` 20 | - ``AppKit/NSTableView/makeRowView(using:forRow:item:)`` 21 | 22 | ### Reloading content 23 | 24 | - ``AppKit/NSTableView/reconfigureRows(at:)`` 25 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSTableView/NSTableViewCellRegistration.md: -------------------------------------------------------------------------------- 1 | # ``NSTableViewCellRegistration`` 2 | 3 | ## Topics 4 | 5 | ### Configurating columns 6 | 7 | - ``columnIdentifiers`` 8 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSTableView/NSTableViewDiffableDataSource+.md: -------------------------------------------------------------------------------- 1 | # NSTableViewDiffableDataSource 2 | 3 | Extensions for `NSTableViewDiffableDataSource`. 4 | 5 | ## Topics 6 | 7 | ### Creating a Diffable Data Source 8 | 9 | - ``AppKit/NSTableViewDiffableDataSource/init(tableView:cellRegistration:)`` 10 | - ``AppKit/NSTableViewDiffableDataSource/init(tableView:cellRegistration:sectionHeaderRegistration:)`` 11 | - ``AppKit/NSTableViewDiffableDataSource/init(tableView:cellRegistrations:)`` 12 | - ``AppKit/NSTableViewDiffableDataSource/init(tableView:cellRegistrations:sectionHeaderRegistration:)`` 13 | 14 | ### Creating Section Views 15 | 16 | - ``AppKit/NSTableViewDiffableDataSource/applySectionHeaderViewRegistration(_:)`` 17 | 18 | ### Updating data 19 | 20 | - ``AppKit/NSTableViewDiffableDataSource/apply(_:_:completion:)`` 21 | 22 | ### Supporting deleting 23 | 24 | - ``AppKit/NSTableViewDiffableDataSource/deletingHandlers-swift.property`` 25 | - ``AppKit/NSTableViewDiffableDataSource/DeletingHandlers-swift.struct`` 26 | 27 | ### Supporting protocol requirements 28 | 29 | - ``AppKit/NSTableViewDiffableDataSource/tableView(_:isGroupRow:)`` 30 | - ``AppKit/NSTableViewDiffableDataSource/tableView(_:rowViewForRow:)`` 31 | - ``AppKit/NSTableViewDiffableDataSource/tableView(_:viewFor:row:)`` 32 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/AppKit Extensions/NSTableView/NSTableViewDiffableDataSource+DeletingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``AppKit/NSTableViewDiffableDataSource/DeletingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Deleting items 6 | 7 | - ``canDelete`` 8 | - ``willDelete`` 9 | - ``didDelete`` 10 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ItemContentConfiguration/NSItemContentConfiguration+Badge.md: -------------------------------------------------------------------------------- 1 | # ``NSItemContentConfiguration/Badge-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Creating a badge 6 | 7 | - ``init()`` 8 | - ``text(_:textStyle:textColor:color:shape:type:position:)`` 9 | - ``image(_:text:textStyle:color:shape:type:position:)`` 10 | - ``symbolImage(_:text:size:color:backgroundColor:shape:type:position:)`` 11 | - ``view(_:color:shape:type:position:)`` 12 | 13 | ### Configurating badge 14 | 15 | - ``type`` 16 | - ``position-swift.property`` 17 | - ``BadgeType`` 18 | - ``Position-swift.enum`` 19 | 20 | ### Customizing content 21 | 22 | - ``text`` 23 | - ``attributedText`` 24 | - ``image`` 25 | - ``view`` 26 | - ``toolTip`` 27 | 28 | ### Customizing content appearance 29 | 30 | - ``textProperties-swift.property`` 31 | - ``imageProperties-swift.property`` 32 | - ``TextProperties-swift.struct`` 33 | - ``ImageProperties-swift.struct`` 34 | 35 | ### Customizing background 36 | 37 | - ``backgroundColor`` 38 | - ``backgroundColorTransformer`` 39 | - ``resolvedBackgroundColor()`` 40 | - ``visualEffect`` 41 | 42 | ### Customizing shape 43 | 44 | - ``shape`` 45 | - ``Shape`` 46 | 47 | ### Customizing border 48 | 49 | - ``border`` 50 | 51 | ### Customizing shadow 52 | 53 | - ``shadow`` 54 | 55 | ### Customizing layout 56 | 57 | - ``margins`` 58 | - ``maxWidth`` 59 | - ``imageToTextPadding`` 60 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ItemContentConfiguration/NSItemContentConfiguration+Content.md: -------------------------------------------------------------------------------- 1 | # ``NSItemContentConfiguration/ContentProperties-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Configuring content properties 6 | 7 | - ``cornerRadius`` 8 | - ``scaleTransform`` 9 | - ``rotation`` 10 | - ``visualEffect`` 11 | - ``toolTip`` 12 | 13 | ### Configuring background color 14 | 15 | - ``backgroundColor`` 16 | - ``backgroundColorTransformer`` 17 | - ``resolvedBackgroundColor()`` 18 | 19 | ### Configuring border 20 | 21 | - ``border`` 22 | - ``borderTransformer`` 23 | - ``resolvedBorder()`` 24 | 25 | ### Configuring shadow 26 | 27 | - ``shadow`` 28 | - ``shadowTransformer`` 29 | - ``resolvedShadow()`` 30 | 31 | ### Configuring size 32 | 33 | - ``maximumSize`` 34 | - ``ProposedSize`` 35 | 36 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ItemContentConfiguration/NSItemContentConfiguration+ImageProperties.md: -------------------------------------------------------------------------------- 1 | # ``NSItemContentConfiguration/ImageProperties-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Customizing appearance 6 | 7 | - ``scaling`` 8 | - ``symbolConfiguration`` 9 | - ``ImageScaling`` 10 | 11 | ### Configuring tint color 12 | 13 | - ``tintColor`` 14 | - ``tintColorTransformer`` 15 | - ``resolvedTintColor()`` 16 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ItemContentConfiguration/NSItemContentConfiguration.md: -------------------------------------------------------------------------------- 1 | # ``NSItemContentConfiguration`` 2 | 3 | ## Topics 4 | 5 | ### Creating item configurations 6 | 7 | - ``init()`` 8 | - ``viewItem(_:text:secondaryText:cornerRadius:)`` 9 | - ``imageItem(_:text:secondaryText:cornerRadius:)`` 10 | - ``listItem(_:secondaryText:image:)`` 11 | 12 | ### Customizing text 13 | 14 | - ``text`` 15 | - ``attributedText`` 16 | - ``secondaryText`` 17 | - ``secondaryAttributedText`` 18 | 19 | ### Customizing placeholder 20 | 21 | - ``placeholderText`` 22 | - ``attributedPlaceholderText`` 23 | - ``secondaryPlaceholderText`` 24 | - ``secondaryAttributedPlaceholderText`` 25 | 26 | ### Customizing content 27 | 28 | - ``image`` 29 | - ``view`` 30 | - ``overlayView`` 31 | 32 | ### Configurating badges 33 | 34 | - ``badges`` 35 | - ``Badge`` 36 | 37 | ### Customizing appearance 38 | 39 | - ``textProperties`` 40 | - ``secondaryTextProperties`` 41 | - ``imageProperties-swift.property`` 42 | - ``contentProperties-swift.property`` 43 | - ``scaleTransform`` 44 | - ``rotation`` 45 | - ``alpha`` 46 | - ``toolTip`` 47 | - ``TextProperties`` 48 | - ``ImageProperties-swift.struct`` 49 | - ``ContentProperties-swift.struct`` 50 | 51 | ### Customizing layout 52 | 53 | - ``contentPosition-swift.property`` 54 | - ``contentToTextPadding`` 55 | - ``textToSecondaryTextPadding`` 56 | - ``margins`` 57 | - ``ContentPosition-swift.enum`` 58 | 59 | ### Creating a content view 60 | 61 | - ``makeContentView()`` 62 | 63 | ### Updating the configuration 64 | 65 | - ``updated(for:)`` 66 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ItemContentConfiguration/NSItemContentView.md: -------------------------------------------------------------------------------- 1 | # ``NSItemContentView`` 2 | 3 | ## Topics 4 | 5 | ### Creating a collection item content view 6 | 7 | - ``init(configuration:)`` 8 | 9 | ### Managing the content configuration 10 | 11 | - ``configuration`` 12 | 13 | ### Managing the content layout 14 | 15 | - ``textLayoutGuide`` 16 | - ``secondaryTextLayoutGuide`` 17 | - ``contentLayoutGuide`` 18 | 19 | ### Determining configuration support 20 | 21 | - ``supports(_:)`` 22 | 23 | ### Handling events 24 | 25 | - ``hitTest(_:)`` 26 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ListContentConfiguration/NSListContentConfiguration+Badge.md: -------------------------------------------------------------------------------- 1 | # ``NSListContentConfiguration/Badge-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Creating a badge 6 | 7 | - ``init()`` 8 | - ``text(_:font:color:backgroundColor:)`` 9 | - ``image(_:backgroundColor:)`` 10 | - ``symbolImage(_:textStyle:color:backgroundColor:)`` 11 | 12 | ### Configurating badge 13 | 14 | - ``position-swift.property`` 15 | - ``Position-swift.enum`` 16 | 17 | ### Customizing content 18 | 19 | - ``text`` 20 | - ``attributedText`` 21 | - ``image`` 22 | - ``toolTip`` 23 | 24 | ### Customizing content appearance 25 | 26 | - ``font`` 27 | - ``imageProperties-swift.property`` 28 | - ``ImageProperties-swift.struct`` 29 | 30 | ### Customizing color 31 | 32 | - ``color`` 33 | - ``colorTransformer`` 34 | - ``resolvedColor()`` 35 | 36 | ### Customizing background color 37 | 38 | - ``backgroundColor`` 39 | - ``backgroundColorTransformer`` 40 | - ``resolvedBackgroundColor()`` 41 | 42 | ### Customizing border 43 | 44 | - ``border`` 45 | 46 | ### Customizing shape 47 | 48 | - ``shape`` 49 | - ``Shape`` 50 | 51 | ### Customizing shadow 52 | 53 | - ``shadow`` 54 | 55 | ### Customizing layout 56 | 57 | - ``alignment-swift.property`` 58 | - ``margins`` 59 | - ``maxWidth`` 60 | - ``imageToTextPadding`` 61 | - ``margins`` 62 | - ``Alignment-swift.enum`` 63 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ListContentConfiguration/NSListContentConfiguration+Image.md: -------------------------------------------------------------------------------- 1 | # ``NSListContentConfiguration/ImageProperties-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Configuring scaling 6 | 7 | - ``scaling-swift.property`` 8 | - ``Scaling-swift.enum`` 9 | 10 | ### Configuring symbol configuration 11 | 12 | - ``symbolConfiguration`` 13 | 14 | ### Configuring tint color 15 | 16 | - ``tintColor`` 17 | - ``tintColorTransformer`` 18 | - ``resolvedTintColor()`` 19 | 20 | ### Configuring background color 21 | 22 | - ``backgroundColor`` 23 | - ``backgroundColorTransformer`` 24 | - ``resolvedBackgroundColor()`` 25 | 26 | ### Configuring border 27 | 28 | - ``border`` 29 | - ``borderTransformer`` 30 | - ``resolvedBorder()`` 31 | - ``cornerRadius`` 32 | 33 | ### Configuring shadow 34 | 35 | - ``shadow`` 36 | - ``shadowTransformer`` 37 | - ``resolvedShadow()`` 38 | 39 | ### Configuring tooltip 40 | 41 | - ``toolTip`` 42 | 43 | ### Customizing layout 44 | 45 | - ``sizing-swift.property`` 46 | - ``Sizing-swift.enum`` 47 | - ``position-swift.property`` 48 | - ``Position-swift.enum`` 49 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ListContentConfiguration/NSListContentConfiguration.md: -------------------------------------------------------------------------------- 1 | # ``NSListContentConfiguration`` 2 | 3 | ## Topics 4 | 5 | ### Creating item configurations 6 | 7 | - ``init()`` 8 | - ``plain(imageColor:)`` 9 | - ``sidebar(imageColor:)`` 10 | - ``sidebarHeader(imageColor:)`` 11 | - ``sidebarLarge(imageColor:)`` 12 | - ``text(_:)`` 13 | - ``editableText(_:placeholderText:onTextEditEnd:stringValidation:)`` 14 | 15 | 16 | ### Customizing content 17 | 18 | - ``text`` 19 | - ``attributedText`` 20 | - ``secondaryText`` 21 | - ``secondaryAttributedText`` 22 | - ``image`` 23 | 24 | ### Customizing placeholder 25 | 26 | - ``placeholderText`` 27 | - ``attributedPlaceholderText`` 28 | - ``secondaryPlaceholderText`` 29 | - ``secondaryAttributedPlaceholderText`` 30 | 31 | ### Configurating badge 32 | 33 | - ``badge-swift.property`` 34 | - ``Badge-swift.struct`` 35 | 36 | ### Customizing appearance 37 | 38 | - ``textProperties`` 39 | - ``secondaryTextProperties`` 40 | - ``imageProperties-swift.property`` 41 | - ``scaleTransform`` 42 | - ``rotation`` 43 | - ``alpha`` 44 | - ``toolTip`` 45 | - ``ImageProperties-swift.struct`` 46 | - ``TextProperties`` 47 | 48 | ### Customizing layout 49 | 50 | - ``imageToTextPadding`` 51 | - ``textToSecondaryTextPadding`` 52 | - ``textToBadgePadding`` 53 | - ``margins`` 54 | 55 | ### Creating a content view 56 | 57 | - ``makeContentView()`` 58 | 59 | ### Updating the configuration 60 | 61 | - ``updated(for:)`` 62 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/ListContentConfiguration/NSListContentView.md: -------------------------------------------------------------------------------- 1 | # ``NSListContentView`` 2 | 3 | ## Topics 4 | 5 | ### Creating a list content view 6 | 7 | - ``init(configuration:)`` 8 | 9 | ### Managing the content configuration 10 | 11 | - ``configuration`` 12 | 13 | ### Managing the content layout 14 | 15 | - ``textLayoutGuide`` 16 | - ``secondaryTextLayoutGuide`` 17 | - ``imageLayoutGuide`` 18 | 19 | ### Determining configuration support 20 | 21 | - ``supports(_:)`` 22 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/Configurations/Shared/TextProperties.md: -------------------------------------------------------------------------------- 1 | # ``TextProperties`` 2 | 3 | ## Topics 4 | 5 | ### Creating text properties 6 | 7 | - ``system(_:weight:design:)`` 8 | - ``system(size:weight:design:)`` 9 | - ``primary`` 10 | - ``secondary`` 11 | - ``tertiary`` 12 | - ``body`` 13 | - ``callout`` 14 | - ``caption1`` 15 | - ``caption2`` 16 | - ``footnote`` 17 | - ``headline`` 18 | - ``subheadline`` 19 | - ``largeTitle`` 20 | - ``title1`` 21 | - ``title2`` 22 | - ``title3`` 23 | 24 | ### Configurating appearance 25 | 26 | - ``font`` 27 | - ``color`` 28 | - ``colorTransformer`` 29 | - ``resolvedColor()`` 30 | - ``alignment`` 31 | - ``toolTip`` 32 | 33 | ### Configurating lines and layout 34 | 35 | - ``maximumNumberOfLines`` 36 | - ``lineBreakMode`` 37 | - ``adjustsFontSizeToFitWidth`` 38 | - ``minimumScaleFactor`` 39 | - ``allowsDefaultTighteningForTruncation`` 40 | 41 | ### Configurating interaction 42 | 43 | - ``isSelectable`` 44 | - ``isEditable`` 45 | - ``onEditEnd`` 46 | - ``stringValidation`` 47 | - ``editingActionOnEnterKeyDown`` 48 | - ``editingActionOnEscapeKeyDown`` 49 | - ``EnterKeyAction`` 50 | - ``EscapeKeyAction`` 51 | 52 | ### Configurating formatting 53 | 54 | - ``numberFormatter`` 55 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/States/NSItemConfigurationState.md: -------------------------------------------------------------------------------- 1 | # ``NSItemConfigurationState`` 2 | 3 | ## Topics 4 | 5 | ### Creating item configuration states 6 | 7 | - ``init(isSelected:highlight:isEditing:activeState:isHovered:isReordering:isDropTarget:)`` 8 | 9 | ### Managing item configuration states 10 | 11 | - ``isSelected`` 12 | - ``activeState-swift.property`` 13 | - ``isEditing`` 14 | - ``isHovered`` 15 | - ``isDropTarget`` 16 | - ``isReordering`` 17 | - ``highlight`` 18 | - ``ActiveState-swift.enum`` 19 | 20 | ### Creating a configuration state manually 21 | 22 | - ``subscript(_:)`` 23 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Configuration/States/NSListConfigurationState.md: -------------------------------------------------------------------------------- 1 | # ``NSListConfigurationState`` 2 | 3 | ## Topics 4 | 5 | ### Creating list configuration states 6 | 7 | - ``init(isSelected:isEnabled:isHovered:isEditing:activeState:isReordering:isDropTarget:isNextSelected:isPreviousSelected:)`` 8 | 9 | ### Managing list configuration states 10 | 11 | - ``isSelected`` 12 | - ``isEditing`` 13 | - ``activeState-swift.property`` 14 | - ``isPreviousSelected`` 15 | - ``isNextSelected`` 16 | - ``isEnabled`` 17 | - ``isHovered`` 18 | - ``isDropTarget`` 19 | - ``isReordering`` 20 | - ``ActiveState-swift.enum`` 21 | 22 | 23 | ### Creating a configuration state manually 24 | 25 | - ``subscript(_:)`` 26 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+DeletingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/DeletingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Deleting items 6 | 7 | - ``canDelete`` 8 | - ``willDelete`` 9 | - ``didDelete`` 10 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+DisplayHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/DisplayHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Displaying items 6 | 7 | - ``isDisplaying`` 8 | - ``didEndDisplaying`` 9 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+DragDropHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/DragDropHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Drag and drop items 6 | 7 | - ``canDragOutside`` 8 | - ``didDragOutside`` 9 | - ``pasteboardValue`` 10 | - ``canDragInside`` 11 | - ``didDragInside`` 12 | - ``draggingImage`` 13 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+HighlightHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/HighlightHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Highlighting items 6 | 7 | - ``shouldChange`` 8 | - ``didChange`` 9 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+HoverHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/HoverHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Hovering items by mouse 6 | 7 | - ``isHovering`` 8 | - ``didEndHovering`` 9 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+PrefetchHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/PrefetchHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Prefetching items 6 | 7 | - ``willPrefetch`` 8 | - ``didCancelPrefetching`` 9 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+ReorderingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/ReorderingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Reordering items 6 | 7 | - ``canReorder`` 8 | - ``willReorder`` 9 | - ``didReorder`` 10 | - ``animates`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource+SelectionHandlers.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource/SelectionHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Selecting items 6 | 7 | - ``shouldSelect`` 8 | - ``didSelect`` 9 | - ``shouldDeselect`` 10 | - ``didDeselect`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource-Protocol-Implementations.md: -------------------------------------------------------------------------------- 1 | # Protocol implementations 2 | 3 | Access the diffable data source’s implementations of protocol methods. 4 | 5 | ## Overview 6 | 7 | The diffable data source type conforms to `NSCollectionViewDataSource`. 8 | 9 | ## Topics 10 | 11 | ### Getting element and section metrics 12 | 13 | - ``CollectionViewDiffableDataSource/collectionView(_:numberOfItemsInSection:)`` 14 | - ``CollectionViewDiffableDataSource/numberOfSections(in:)`` 15 | 16 | ### Getting items for elements 17 | 18 | - ``CollectionViewDiffableDataSource/collectionView(_:itemForRepresentedObjectAt:)`` 19 | - ``CollectionViewDiffableDataSource/collectionView(_:viewForSupplementaryElementOfKind:at:)`` 20 | 21 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/CollectionViewDiffableData/CollectionViewDiffableDataSource.md: -------------------------------------------------------------------------------- 1 | # ``CollectionViewDiffableDataSource`` 2 | 3 | ## Topics 4 | 5 | ### Creating a diffable data source 6 | 7 | - ``init(collectionView:itemProvider:)`` 8 | - ``init(collectionView:itemRegistration:)`` 9 | - ``ItemProvider`` 10 | 11 | ### Creating supplementary views 12 | 13 | - ``supplementaryViewProvider-swift.property`` 14 | - ``SupplementaryViewProvider-swift.typealias`` 15 | - ``useSupplementaryRegistrations(_:)`` 16 | 17 | ### Identifying elements 18 | 19 | - ``elements`` 20 | - ``selectedElements`` 21 | - ``displayingElements`` 22 | - ``element(for:)`` 23 | - ``element(at:)`` 24 | - ``elements(for:)`` 25 | - ``indexPath(for:)`` 26 | - ``reloadElements(_:animated:)`` 27 | - ``reconfigureElements(_:)`` 28 | - ``selectElements(_:byExtendingSelection:scrollPosition:)`` 29 | - ``selectElements(in:scrollPosition:)`` 30 | - ``deselectElements(_:)`` 31 | - ``deselectElements(in:)`` 32 | - ``scrollToElements(_:scrollPosition:)`` 33 | 34 | ### Identifying sections 35 | 36 | - ``sections`` 37 | - ``section(at:)`` 38 | - ``index(for:)`` 39 | - ``scrollToSection(_:scrollPosition:)`` 40 | 41 | ### Updating data 42 | 43 | - ``snapshot()`` 44 | - ``emptySnapshot()`` 45 | - ``apply(_:_:completion:)`` 46 | 47 | ### Configurating user interaction 48 | 49 | - ``menuProvider`` 50 | - ``rightClickHandler`` 51 | 52 | ### Displaying empty view 53 | 54 | - ``emptyView`` 55 | - ``emptyContentConfiguration`` 56 | - ``emptyHandler`` 57 | 58 | ### Previewing elements 59 | 60 | - ``isQuicklookPreviewable`` 61 | - ``quicklookElements(_:current:)`` 62 | 63 | ### Supporting prefetching elements 64 | 65 | - ``prefetchHandlers-swift.property`` 66 | - ``PrefetchHandlers-swift.struct`` 67 | 68 | ### Supporting reordering elements 69 | 70 | - ``reorderingHandlers-swift.property`` 71 | - ``ReorderingHandlers-swift.struct`` 72 | 73 | ### Supporting deleting elements 74 | 75 | - ``deletingHandlers-swift.property`` 76 | - ``DeletingHandlers-swift.struct`` 77 | 78 | ### Handling selecting elements 79 | 80 | - ``selectionHandlers-swift.property`` 81 | - ``SelectionHandlers-swift.struct`` 82 | 83 | ### Handling displaying elements 84 | 85 | - ``displayHandlers-swift.property`` 86 | - ``DisplayHandlers-swift.struct`` 87 | 88 | ### Handling hovering elements 89 | 90 | - ``hoverHandlers-swift.property`` 91 | - ``HoverHandlers-swift.struct`` 92 | 93 | ### Handling highlighting elements 94 | 95 | - ``highlightHandlers-swift.property`` 96 | - ``HighlightHandlers-swift.struct`` 97 | 98 | ### Managing drag interactions 99 | 100 | - ``draggingHandlers-swift.property`` 101 | - ``DraggingHandlers-swift.struct`` 102 | 103 | ### Managing drop interactions 104 | 105 | - ``droppingHandlers-swift.property`` 106 | - ``DroppingHandlers-swift.struct`` 107 | 108 | ### Supporting protocol requirements 109 | 110 | - 111 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineNode.md: -------------------------------------------------------------------------------- 1 | # ``OutlineNode`` 2 | 3 | ## Topics 4 | 5 | ### Creating a outline node 6 | 7 | - ``init(_:)`` 8 | - ``init(_:_:)`` 9 | 10 | ### Accessing items 11 | 12 | - ``item`` 13 | - ``children`` 14 | 15 | ### Configurating expansion 16 | 17 | - ``isExpanded`` 18 | - ``isExpanded(_:)`` 19 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource+ColumnHandlers.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource/ColumnHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Resizing columns 6 | 7 | - ``didResize`` 8 | 9 | ### Reordering columns 10 | 11 | - ``shouldReorder`` 12 | - ``didReorder`` 13 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource+DeletingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource/DeletingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Deleting items 6 | 7 | - ``canDelete`` 8 | - ``willDelete`` 9 | - ``didDelete`` 10 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource+ExpansionHandlers.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource/ExpansionHandlers.md-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Expanding/Collapsing items 6 | 7 | - ``shouldExpand`` 8 | - ``didExpand`` 9 | - ``shouldCollapse`` 10 | - ``didCollapse`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource+HoverHandlers.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource/HoverHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Hovering items by mouse 6 | 7 | - ``isHovering`` 8 | - ``didEndHovering`` 9 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource+ReorderingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource/ReorderingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Reordering items 6 | 7 | - ``canReorder`` 8 | - ``willReorder`` 9 | - ``didReorder`` 10 | - ``animates`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource+SelectionHandlers.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource/SelectionHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Selecting items 6 | 7 | - ``shouldSelect`` 8 | - ``didSelect`` 9 | - ``shouldDeselect`` 10 | - ``didDeselect`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource-Protocol-Implementations.md: -------------------------------------------------------------------------------- 1 | # Protocol implementations 2 | 3 | Access the diffable data source’s implementations of protocol methods. 4 | 5 | ## Overview 6 | 7 | The diffable data source type conforms to `NSOutlineViewDataSource`. 8 | 9 | ## Topics 10 | 11 | ### Getting item metrics 12 | 13 | - ``OutlineViewDiffableDataSource/outlineView(_:child:ofItem:)`` 14 | - ``OutlineViewDiffableDataSource/outlineView(_:numberOfChildrenOfItem:)`` 15 | - ``OutlineViewDiffableDataSource/outlineView(_:isItemExpandable:)`` 16 | 17 | ### Reordering items 18 | 19 | - ``OutlineViewDiffableDataSource/outlineView(_:draggingSession:willBeginAt:forItems:)`` 20 | - ``OutlineViewDiffableDataSource/outlineView(_:draggingSession:endedAt:operation:)`` 21 | - ``OutlineViewDiffableDataSource/outlineView(_:validateDrop:proposedItem:proposedChildIndex:)`` 22 | - ``OutlineViewDiffableDataSource/outlineView(_:acceptDrop:item:childIndex:)`` 23 | - ``OutlineViewDiffableDataSource/outlineView(_:pasteboardWriterForItem:)`` 24 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSource.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSource`` 2 | 3 | ## Overview 4 | 5 | ## Topics 6 | 7 | ### Creating a diffable data source 8 | 9 | - ``init(outlineView:cellRegistration:)`` 10 | - ``init(outlineView:cellRegistrations:)`` 11 | - ``init(outlineView:cellProvider:)`` 12 | - ``CellProvider`` 13 | 14 | ### Creating row views 15 | 16 | - ``rowViewProvider-swift.property`` 17 | - ``RowViewProvider-swift.typealias`` 18 | - ``applyRowViewRegistration(_:)`` 19 | 20 | ### Creating group item cell views. 21 | 22 | - ``groupItemCellProvider-swift.property`` 23 | - ``GroupItemCellProvider-swift.typealias`` 24 | - ``applyGroupItemCellRegistration(_:)`` 25 | - ``groupItemsAreCollapsable`` 26 | 27 | ### Identifying items 28 | 29 | - ``items`` 30 | - ``selectedItems`` 31 | - ``visibleItems`` 32 | - ``item(forRow:)`` 33 | - ``row(for:)`` 34 | - ``item(at:)`` 35 | - ``reloadItems(_:reloadChildren:animated:)`` 36 | - ``reconfigureItems(_:)`` 37 | - ``selectItems(_:byExtendingSelection:)`` 38 | - ``deselectItems(_:)`` 39 | - ``scrollToItem(_:)`` 40 | 41 | ### Updating data 42 | 43 | - ``snapshot()`` 44 | - ``snapshot(for:)`` 45 | - ``emptySnapshot()`` 46 | - ``apply(_:_:completion:)`` 47 | - ``defaultRowAnimation`` 48 | 49 | ### Configurating user interaction 50 | 51 | - ``menuProvider`` 52 | - ``rightClickHandler`` 53 | 54 | ### Providing tint configurations 55 | 56 | ``tintConfigurationProvider`` 57 | 58 | ### Displaying empty view 59 | 60 | - ``emptyView`` 61 | - ``emptyContentConfiguration`` 62 | - ``emptyHandler`` 63 | 64 | ### Supporting reordering items 65 | 66 | - ``reorderingHandlers-swift.property`` 67 | - ``ReorderingHandlers-swift.struct`` 68 | 69 | ### Supporting deleting items 70 | 71 | - ``deletingHandlers-swift.property`` 72 | - ``DeletingHandlers-swift.struct`` 73 | 74 | ### Handling selecting items 75 | 76 | - ``selectionHandlers-swift.property`` 77 | - ``SelectionHandlers-swift.struct`` 78 | 79 | ### Handling expanding/collapsing items 80 | 81 | - ``expand(_:expandChildren:)-770uz`` 82 | - ``expand(_:expandChildren:)-1v68w`` 83 | - ``collapse(_:collapseChildren:)-82w6c`` 84 | - ``collapse(_:collapseChildren:)-5f5fu`` 85 | - ``expanionHandlers-swift.property`` 86 | - ``ExpanionHandlers-swift.struct`` 87 | 88 | ### Handling hovering items 89 | 90 | - ``hoverHandlers-swift.property`` 91 | - ``HoverHandlers-swift.struct`` 92 | 93 | ### Handling column changes 94 | 95 | - ``columnHandlers-swift.property`` 96 | - ``ColumnHandlers-swift.struct`` 97 | 98 | ### Sorting items 99 | 100 | - ``setSortComparator(_:forColumn:activate:)`` 101 | - ``setSortComparators(_:forColumn:activate:)`` 102 | 103 | ### Previewing items 104 | 105 | - ``isQuicklookPreviewable`` 106 | - ``quicklookItems(_:current:)`` 107 | 108 | ### Supporting protocol requirements 109 | 110 | - 111 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSourceSnapshot.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSourceSnapshot`` 2 | 3 | ## Overview 4 | 5 | ## Topics 6 | 7 | ### Creating a snapshot 8 | 9 | - ``init()`` 10 | - ``snapshot(of:includingParent:)`` 11 | - ``append(_:to:)`` 12 | 13 | ### Accessing items 14 | 15 | - ``items`` 16 | - ``rootItems`` 17 | - ``visibleItems`` 18 | - ``children(of:recursive:)`` 19 | 20 | ### Getting item metrics 21 | 22 | - ``index(of:)`` 23 | - ``level(of:)`` 24 | - ``parent(of:)`` 25 | - ``contains(_:)`` 26 | - ``isVisible(_:)`` 27 | - ``isDescendant(_:of:)`` 28 | 29 | ### Inserting items 30 | 31 | - ``insert(_:before:)-5psi5`` 32 | - ``insert(_:before:)-3vdz`` 33 | - ``insert(_:after:)-97sm4`` 34 | - ``insert(_:after:)-9at59`` 35 | 36 | ### Moving items 37 | 38 | - ``move(_:before:)`` 39 | - ``move(_:after:)`` 40 | - ``move(_:toIndex:of:)`` 41 | 42 | ### Removing items 43 | 44 | - ``delete(_:)`` 45 | - ``deleteAll()`` 46 | 47 | ### Replacing items 48 | 49 | - ``replace(childrenOf:using:)`` 50 | 51 | ### Expanding and collapsing items 52 | 53 | - ``isExpanded(_:)`` 54 | - ``expand(_:)`` 55 | - ``collapse(_:)`` 56 | 57 | ### Debugging snapshots 58 | 59 | - ``visualDescription()`` 60 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/OutlineViewDiffableDataSource/OutlineViewDiffableDataSourceTransaction.md: -------------------------------------------------------------------------------- 1 | # ``OutlineViewDiffableDataSourceTransaction`` 2 | 3 | ## Overview 4 | 5 | ## Topics 6 | 7 | ### Accessing a transaction's information 8 | 9 | - ``initialSnapshot`` 10 | - ``finalSnapshot`` 11 | - ``difference`` 12 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/Shared/NSDiffableDataSourceSnapshotApplyOption.md: -------------------------------------------------------------------------------- 1 | # ``NSDiffableDataSourceSnapshotApplyOption`` 2 | 3 | ## Topics 4 | 5 | ### Snapshot applying options 6 | 7 | - ``animated`` 8 | - ``animated(duration:)`` 9 | - ``withoutAnimation`` 10 | - ``usingReloadData`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/Shared/NSDiffableDataSourceTransaction.md: -------------------------------------------------------------------------------- 1 | # ``DiffableDataSourceTransaction`` 2 | 3 | ## Topics 4 | 5 | ### Accessing a transaction's information 6 | 7 | - ``initialSnapshot`` 8 | - ``finalSnapshot`` 9 | - ``difference`` 10 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource+ColumnHandlers.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource/ColumnHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Resizing columns 6 | 7 | - ``didResize`` 8 | 9 | ### Reordering columns 10 | 11 | - ``shouldReorder`` 12 | - ``didReorder`` 13 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource+DeletingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource/DeletingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Deleting items 6 | 7 | - ``canDelete`` 8 | - ``willDelete`` 9 | - ``didDelete`` 10 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource+DragDropHandlers.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource/DragDropHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Drag and drop items 6 | 7 | - ``canDragOutside`` 8 | - ``didDragOutside`` 9 | - ``pasteboardValue`` 10 | - ``canDragInside`` 11 | - ``didDragInside`` 12 | - ``draggingImage`` 13 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource+HoverHandlers.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource/HoverHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Hovering items by mouse 6 | 7 | - ``isHovering`` 8 | - ``didEndHovering`` 9 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource+ReorderingHandlers.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource/ReorderingHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Reordering items 6 | 7 | - ``canReorder`` 8 | - ``willReorder`` 9 | - ``didReorder`` 10 | - ``animates`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource+SelectionHandlers.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource/SelectionHandlers-swift.struct`` 2 | 3 | ## Topics 4 | 5 | ### Selecting items 6 | 7 | - ``shouldSelect`` 8 | - ``didSelect`` 9 | - ``shouldDeselect`` 10 | - ``didDeselect`` 11 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource-Protocol Implementations.md: -------------------------------------------------------------------------------- 1 | # Protocol implementations 2 | 3 | Access the diffable data source’s implementations of protocol methods. 4 | 5 | ## Overview 6 | 7 | The diffable data source type conforms to `NSTableViewDataSource`. 8 | 9 | ## Topics 10 | 11 | ### Getting item metrics 12 | 13 | - ``TableViewDiffableDataSource/numberOfRows(in:)`` 14 | 15 | ### Reordering items 16 | 17 | - ``TableViewDiffableDataSource/tableView(_:pasteboardWriterForRow:)`` 18 | - ``TableViewDiffableDataSource/tableView(_:validateDrop:proposedRow:proposedDropOperation:)`` 19 | - ``TableViewDiffableDataSource/tableView(_:draggingSession:willBeginAt:forRowIndexes:)`` 20 | - ``TableViewDiffableDataSource/tableView(_:acceptDrop:row:dropOperation:)`` 21 | - ``TableViewDiffableDataSource/tableView(_:draggingSession:endedAt:operation:)`` 22 | - ``TableViewDiffableDataSource/tableView(_:updateDraggingItemsForDrag:)`` 23 | 24 | ### Sorting items 25 | 26 | - ``TableViewDiffableDataSource/tableView(_:sortDescriptorsDidChange:)`` 27 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/DiffableDataSource/TableViewDiffableDataSource/TableViewDiffableDataSource.md: -------------------------------------------------------------------------------- 1 | # ``TableViewDiffableDataSource`` 2 | 3 | ## Overview 4 | 5 | ## Topics 6 | 7 | ### Creating a diffable data source 8 | 9 | - ``init(tableView:cellRegistration:)`` 10 | - ``init(tableView:cellRegistrations:)`` 11 | - ``init(tableView:cellProvider:)`` 12 | - ``CellProvider`` 13 | 14 | ### Creating section header views 15 | 16 | - ``sectionHeaderCellProvider-swift.property`` 17 | - ``SectionHeaderCellProvider-swift.typealias`` 18 | - ``applySectionHeaderRegistration(_:)`` 19 | 20 | ### Creating row views 21 | 22 | - ``rowViewProvider-swift.property`` 23 | - ``RowViewProvider-swift.typealias`` 24 | - ``applyRowViewRegistration(_:)`` 25 | 26 | ### Identifying items 27 | 28 | - ``items`` 29 | - ``selectedItems`` 30 | - ``visibleItems`` 31 | - ``item(forRow:)`` 32 | - ``row(for:)-3ouhk`` 33 | - ``item(at:)`` 34 | - ``reloadItems(_:animated:)`` 35 | - ``reconfigureItems(_:)`` 36 | - ``selectItems(_:byExtendingSelection:)`` 37 | - ``selectItems(in:byExtendingSelection:)`` 38 | - ``deselectItems(_:)`` 39 | - ``deselectItems(in:)`` 40 | - ``scrollToItem(_:)`` 41 | 42 | ### Identifying sections 43 | 44 | - ``sections`` 45 | - ``row(for:)-3rckc`` 46 | - ``scrollToSection(_:)`` 47 | 48 | ### Updating data 49 | 50 | - ``snapshot()`` 51 | - ``emptySnapshot()`` 52 | - ``apply(_:_:completion:)`` 53 | - ``defaultRowAnimation`` 54 | 55 | ### Configurating user interaction 56 | 57 | - ``menuProvider`` 58 | - ``rightClickHandler`` 59 | - ``rowActionProvider`` 60 | 61 | ### Displaying empty view 62 | 63 | - ``emptyView`` 64 | - ``emptyContentConfiguration`` 65 | - ``emptyHandler`` 66 | 67 | ### Supporting reordering items 68 | 69 | - ``reorderingHandlers-swift.property`` 70 | - ``ReorderingHandlers-swift.struct`` 71 | 72 | ### Supporting deleting items 73 | 74 | - ``deletingHandlers-swift.property`` 75 | - ``DeletingHandlers-swift.struct`` 76 | 77 | ### Handling selecting items 78 | 79 | - ``selectionHandlers-swift.property`` 80 | - ``SelectionHandlers-swift.struct`` 81 | 82 | ### Handling hovering items 83 | 84 | - ``hoverHandlers-swift.property`` 85 | - ``HoverHandlers-swift.struct`` 86 | 87 | ### Handling column changes 88 | 89 | - ``columnHandlers-swift.property`` 90 | - ``ColumnHandlers-swift.struct`` 91 | 92 | ### Sorting items 93 | 94 | - ``setSortComparator(_:forColumn:activate:)`` 95 | - ``setSortComparators(_:forColumn:activate:)`` 96 | 97 | ### Previewing items 98 | 99 | - ``isQuicklookPreviewable`` 100 | - ``quicklookItems(_:current:)`` 101 | 102 | ### Managing drag interactions 103 | 104 | - ``draggingHandlers-swift.property`` 105 | - ``DraggingHandlers-swift.struct`` 106 | 107 | ### Managing drop interactions 108 | 109 | - ``droppingHandlers-swift.property`` 110 | - ``DroppingHandlers-swift.struct`` 111 | 112 | ### Supporting protocol requirements 113 | 114 | - 115 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Registration/NSCollectionView/ItemRegistration.md: -------------------------------------------------------------------------------- 1 | # ``AppKit/NSCollectionView/ItemRegistration`` 2 | 3 | @Metadata { 4 | @DisplayName("ItemRegistration") 5 | } 6 | 7 | ## Topics 8 | 9 | ### Creating an item registration 10 | 11 | - ``init(handler:)`` 12 | - ``init(nib:handler:)`` 13 | - ``Handler`` 14 | 15 | ### Creating an item 16 | 17 | - ``AppKit/NSCollectionView/makeItem(using:for:element:)`` 18 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Registration/NSCollectionView/SupplementaryRegistration.md: -------------------------------------------------------------------------------- 1 | # ``AppKit/NSCollectionView/SupplementaryRegistration`` 2 | 3 | @Metadata { 4 | @DisplayName("SupplementaryRegistration") 5 | } 6 | 7 | ## Topics 8 | 9 | ### Creating a supplementary registration 10 | 11 | - ``init(elementKind:handler:)`` 12 | - ``init(nib:elementKind:handler:)`` 13 | - ``elementKind`` 14 | - ``Handler`` 15 | 16 | ### Creating a supplementary view 17 | 18 | - ``AppKit/NSCollectionView/makeSupplementaryView(using:for:)`` 19 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Registration/NSTableView/CellRegistration.md: -------------------------------------------------------------------------------- 1 | # ``AppKit/NSTableView/CellRegistration`` 2 | 3 | @Metadata { 4 | @DisplayName("CellRegistration") 5 | } 6 | 7 | ## Topics 8 | 9 | ### Creating a table cell registration 10 | 11 | - ``init(columnIdentifiers:handler:)`` 12 | - ``init(nib:columnIdentifiers:handler:)`` 13 | - ``Handler`` 14 | 15 | ### Configurating columns 16 | 17 | - ``columnIdentifiers`` 18 | 19 | ### Creating a table cell view 20 | 21 | - ``AppKit/NSTableView/makeCellView(using:forColumn:row:item:)`` 22 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Registration/NSTableView/NSTableSectionHeaderView.md: -------------------------------------------------------------------------------- 1 | # ``NSTableSectionHeaderView`` 2 | 3 | ## Topics 4 | 5 | ### Managing the content 6 | 7 | - ``defaultContentConfiguration()`` 8 | - ``contentConfiguration`` 9 | - ``automaticallyUpdatesContentConfiguration`` 10 | 11 | ### Managing the state 12 | 13 | - ``configurationState`` 14 | - ``setNeedsUpdateConfiguration()`` 15 | - ``updateConfiguration(using:)`` 16 | - ``configurationUpdateHandler`` 17 | - ``ConfigurationUpdateHandler`` 18 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Extensions/Registration/NSTableView/RowViewRegistration.md: -------------------------------------------------------------------------------- 1 | # ``AppKit/NSTableView/RowRegistration`` 2 | 3 | @Metadata { 4 | @DisplayName("RowRegistration") 5 | } 6 | 7 | ## Topics 8 | 9 | ### Creating a table row registration 10 | 11 | - ``init(handler:)`` 12 | - ``init(nib:handler:)`` 13 | - ``Handler`` 14 | 15 | ### Creating a table row view 16 | 17 | - ``AppKit/NSTableView/makeRowView(using:forRow:item:)`` 18 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Resources/Extension.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Extension 4 | 5 | 6 | 7 | 8 | E 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Resources/NSItemContentConfiguration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Resources/NSItemContentConfiguration.png -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Resources/NSListContentConfiguration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flocked/AdvancedCollectionTableView/094735ce186ccf19d26eb50cd0eb0d72fa44bcce/Sources/AdvancedCollectionTableView/Documentation/AdvancedCollectionTableView.docc/Resources/NSListContentConfiguration.png -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSCollectionView/NSCollectionView+DragSessionMove.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionView+DragSessionMove.swift 3 | // 4 | // 5 | // Created by Florian Zand on 02.03.25. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | extension NSCollectionView { 12 | var draggingSessionMoveHandler: ((NSDraggingSession, CGPoint)->())? { 13 | get { getAssociatedValue("draggingSessionMoveHandler") } 14 | set { 15 | setAssociatedValue(newValue, key: "draggingSessionMoveHandler") 16 | let selector = #selector(NSCollectionView.draggingSession(_:movedTo:)) 17 | if newValue != nil { 18 | guard !isMethodHooked(selector) else { return } 19 | do { 20 | try hook(selector, closure: { original, object, sel, session, point in 21 | (object as? NSCollectionView)?.draggingSessionMoveHandler?(session, point) 22 | original(object, sel, session, point) 23 | } as @convention(block) ( 24 | (AnyObject, Selector, NSDraggingSession, CGPoint) -> Void, 25 | AnyObject, Selector, NSDraggingSession, CGPoint) -> Void) 26 | } catch { 27 | debugPrint(error) 28 | } 29 | } else { 30 | revertHooks(for: selector) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSCollectionView/NSCollectionView+ReconfigureItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionView+ReconfigurateItem.swift 3 | // 4 | // 5 | // Created by Florian Zand on 18.05.22. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | extension NSCollectionView { 12 | /** 13 | Updates the data for the items at the index paths you specify, preserving existing items. 14 | 15 | To update the contents of existing (including prefetched) items without replacing them with new items, use this method instead of `reloadItems(at:)`. For optimal performance, choose to reconfigure items instead of reloading items unless you have an explicit need to replace the existing item with a new item. 16 | 17 | Your item provider must dequeue the same type of item for the provided index path, and must return the same existing item for a given index path. Because this method reconfigures existing items, the collection view doesn’t item `prepareForReuse()` for each item dequeued. If you need to return a different type of item for an index path, use reloadItems(at:) instead. 18 | 19 | - Parameters: 20 | - indexPaths: An array of `IndexPath` objects identifying the items you want to update. 21 | */ 22 | public func reconfigureItems(at indexPaths: [IndexPath]) { 23 | Self.swizzleMakeItem() 24 | guard let dataSource = dataSource else { return } 25 | isReconfiguratingItems = true 26 | let visibleIndexPaths = indexPathsForVisibleItems() 27 | let indexPaths = indexPaths.filter({visibleIndexPaths.contains($0)}) 28 | for indexPath in indexPaths { 29 | dataSource.collectionView(self, itemForRepresentedObjectAt: indexPath) 30 | } 31 | isReconfiguratingItems = false 32 | } 33 | 34 | var isReconfiguratingItems: Bool { 35 | get { getAssociatedValue("isReconfiguratingItems", initialValue: false) } 36 | set { setAssociatedValue(newValue, key: "isReconfiguratingItems") 37 | } 38 | } 39 | 40 | static var didSwizzleMakeItem: Bool { 41 | get { getAssociatedValue("didSwizzleMakeItem", initialValue: false) } 42 | set { setAssociatedValue(newValue, key: "didSwizzleMakeItem") } 43 | } 44 | 45 | @objc static func swizzleMakeItem() { 46 | guard didSwizzleMakeItem == false else { return } 47 | do { 48 | try Swizzle(NSCollectionView.self) { 49 | #selector(makeItem(withIdentifier:for:)) <-> #selector(swizzled_makeItem(withIdentifier:for:)) 50 | } 51 | didSwizzleMakeItem = true 52 | } catch { 53 | Swift.debugPrint(error) 54 | } 55 | } 56 | 57 | @objc func swizzled_makeItem(withIdentifier identifier: NSUserInterfaceItemIdentifier, for indexPath: IndexPath) -> NSCollectionViewItem { 58 | if isReconfiguratingItems, let item = self.item(at: indexPath) { 59 | return item 60 | } 61 | return self.swizzled_makeItem(withIdentifier: identifier, for: indexPath) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSCollectionView/NSCollectionViewDiffableDataSource+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionViewDiffableDataSource+.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.03.25. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | extension NSCollectionViewDiffableDataSource { 12 | /// The item provider of the datasource. 13 | public var itemProvider: ItemProvider { 14 | typealias itemProviderBlock = @convention(block) (NSCollectionView, IndexPath, Any) -> NSCollectionViewItem? 15 | guard let object: NSObject = getIvarValue(for: "_impl"), let cellProvider: itemProviderBlock = object.getIvarValue(for: "_collectionViewItemProvider") else { return { _,_,_ in return nil } } 16 | return cellProvider 17 | } 18 | 19 | /// Creates a new collection view item for the specified item identifier using the item provider. 20 | public func createItem(for itemIdentifier: ItemIdentifierType) -> NSCollectionViewItem? { 21 | itemProvider(collectionView, IndexPath(item: 0, section: 0), itemIdentifier) 22 | } 23 | 24 | /// Returns a preview image of the collection view item for the specified item. 25 | public func previewImage(for item: ItemIdentifierType) -> NSImage? { 26 | _previewImage(for: item, size: nil) 27 | } 28 | 29 | /// Returns a preview image of the collection view item for the specified item and item size. 30 | public func previewImage(for item: ItemIdentifierType, size: CGSize) -> NSImage? { 31 | _previewImage(for: item, size: size) 32 | } 33 | 34 | private func _previewImage(for item: ItemIdentifierType, size: CGSize? = nil, width: CGFloat? = nil, height: CGFloat? = nil) -> NSImage? { 35 | guard let item = createItem(for: item) else { return nil } 36 | if width != nil || height != nil { 37 | item.view.frame.size = item.view.systemLayoutSizeFitting(width: width, height: height) 38 | item.view.frame.size.width = width ?? item.view.frame.size.width 39 | item.view.frame.size.height = height ?? item.view.frame.size.height 40 | } else { 41 | item.view.frame.size = size ?? collectionView.frameForItem(at: IndexPath(item: 0, section: 0))?.size ?? CGSize(512, 512) 42 | } 43 | return item.view.renderedImage 44 | } 45 | 46 | private var collectionView: NSCollectionView { 47 | guard let object: NSObject = getIvarValue(for: "_impl"), let collectionView: NSCollectionView = object.getIvarValue(for: "_nsCollectionView") else { return NSCollectionView() } 48 | return collectionView 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSCollectionView/NSCollectionViewDiffableDataSource+Apply.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionViewDiffableDataSource+Apply.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.12.22. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSCollectionViewDiffableDataSource { 11 | /** 12 | Updates the UI to reflect the state of the data in the specified snapshot, optionally animating the UI changes and executing a completion handler. 13 | 14 | It’s safe to call this method from a background queue, but you must do so consistently in your app. Always call this method exclusively from the main queue or from a background queue. 15 | 16 | - Parameters: 17 | - snapshot: The snapshot reflecting the new state of the data in the collection view. 18 | - option: Option how to apply the snapshot to the collection view. The default value is `animated`. 19 | - completion: An optional closure to be executed when the animations are complete. The system calls this closure from the main queue. 20 | */ 21 | public func apply(_ snapshot: NSDiffableDataSourceSnapshot, _ option: NSDiffableDataSourceSnapshotApplyOption = .animated, completion: (() -> Void)? = nil) { 22 | switch option { 23 | case .usingReloadData: 24 | applySnapshotUsingReloadData(snapshot, completion: completion) 25 | case .animated: 26 | apply(snapshot, animated: true, animationDuration: option.animationDuration, completion: completion) 27 | case .withoutAnimation: 28 | apply(snapshot, animated: false, completion: completion) 29 | } 30 | } 31 | 32 | private func applySnapshotUsingReloadData(_ snapshot: NSDiffableDataSourceSnapshot, completion: (() -> Void)? = nil) { 33 | apply(snapshot, animatingDifferences: false, completion: completion) 34 | } 35 | 36 | private func apply(_ snapshot: NSDiffableDataSourceSnapshot, animated: Bool = true, animationDuration: TimeInterval? = nil, completion: (() -> Void)? = nil) { 37 | if animated, animationDuration == nil { 38 | apply(snapshot, animatingDifferences: true, completion: completion) 39 | } else { 40 | NSAnimationContext.beginGrouping() 41 | NSAnimationContext.current.duration = animationDuration ?? 0 42 | apply(snapshot, animatingDifferences: true, completion: completion) 43 | NSAnimationContext.endGrouping() 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSCollectionView/NSCollectionViewDiffableDataSource+Registration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionViewDiffableDataSource+Registration.swift 3 | // 4 | // 5 | // Created by Florian Zand on 02.09.22. 6 | // 7 | 8 | import AppKit 9 | 10 | public extension NSCollectionViewDiffableDataSource { 11 | /** 12 | Creates a diffable data source with the specified item provider, and connects it to the specified collection view. 13 | 14 | - Parameters: 15 | - collectionView: The initialized collection view object to connect to the diffable data source. 16 | - itemRegistration: A item registration that creates, configurate and returns each of the items for the collection view from the data the diffable data source provides. 17 | */ 18 | convenience init(collectionView: NSCollectionView, itemRegistration: NSCollectionView.ItemRegistration) { 19 | self.init(collectionView: collectionView, itemProvider: { 20 | tCollectionView, indexPath, element in 21 | tCollectionView.makeItem(using: itemRegistration, for: indexPath, element: element) 22 | }) 23 | } 24 | 25 | /** 26 | Creates a diffable data source with the specified item provider, and connects it to the specified collection view. 27 | 28 | - Parameters: 29 | - collectionView: The initialized collection view object to connect to the diffable data source. 30 | - itemRegistration: A item registration that creates, configurate and returns each of the items for the collection view from the data the diffable data source provides. 31 | - supplementaryRegistrations: An array of collection view’s SupplementaryRegistration that provides supplementary views, such as headers and footers. 32 | */ 33 | convenience init(collectionView: NSCollectionView, itemRegistration: NSCollectionView.ItemRegistration, supplementaryRegistrations: [NSCollectionViewSupplementaryRegistration]) { 34 | self.init(collectionView: collectionView, itemRegistration: itemRegistration) 35 | useSupplementaryRegistrations(supplementaryRegistrations) 36 | } 37 | 38 | /// Uses the supplementary registrations to return supplementary views to `supplementaryViewProvider`. 39 | func useSupplementaryRegistrations(_ registrations: [NSCollectionViewSupplementaryRegistration]) { 40 | guard !registrations.isEmpty else { return } 41 | supplementaryViewProvider = { collectionView, elementKind, indexPath in 42 | (registrations.first(where: { $0.elementKind == elementKind }) as? _NSCollectionViewSupplementaryRegistration)?.makeSupplementaryView(collectionView, indexPath) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSCollectionView/unused/NSCollectionView+SelfSizing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSCollectionView+SelfSizing.swift 3 | // 4 | // 5 | // Created by Florian Zand on 23.07.23. 6 | // 7 | 8 | /* 9 | import AppKit 10 | import FZSwiftUtils 11 | import FZUIKit 12 | 13 | // Currently not implemented 14 | extension NSCollectionView { 15 | /** 16 | Constants that describe modes for invalidating the size of self-sizing collection view items. 17 | 18 | Use these constants with the `selfSizingInvalidation` property. 19 | */ 20 | enum SelfSizingInvalidation: Int { 21 | /// A mode that disables self-sizing invalidation. 22 | case disabled = 0 23 | /// A mode that enables manual self-sizing invalidation. 24 | case enabled = 1 25 | /// A mode that enables automatic self-sizing invalidation after Auto Layout changes. 26 | case enabledIncludingConstraints = 2 27 | } 28 | 29 | /// The mode that the collection view uses for invalidating the size of self-sizing items. 30 | var selfSizingInvalidation: SelfSizingInvalidation { 31 | get { getAssociatedValue("selfSizingInvalidation", initialValue: SelfSizingInvalidation.disabled) } 32 | set { 33 | setAssociatedValue(newValue, key: "selfSizingInvalidation") 34 | if newValue != .disabled { 35 | NSCollectionViewItem.swizzleCollectionViewItemIfNeeded() 36 | } 37 | } 38 | } 39 | } 40 | 41 | extension NSCollectionViewItem { 42 | static var didSwizzleCollectionViewItem: Bool { 43 | get { getAssociatedValue("didSwizzleCollectionViewItemLayoutAttributes", initialValue: false) } 44 | set { setAssociatedValue(newValue, key: "didSwizzleCollectionViewItemLayoutAttributes") } 45 | } 46 | 47 | static func swizzleCollectionViewItemIfNeeded() { 48 | if didSwizzleCollectionViewItem == false { 49 | do { 50 | _ = try Swizzle(NSCollectionViewItem.self) { 51 | #selector(viewDidLayout) <-> #selector(swizzled_viewDidLayout) 52 | #selector(apply(_:)) <-> #selector(swizzled_apply(_:)) 53 | #selector(preferredLayoutAttributesFitting(_:)) <-> #selector(swizzled_preferredLayoutAttributesFitting(_:)) 54 | } 55 | didSwizzleCollectionViewItem = true 56 | } catch { 57 | Swift.debugPrint(error) 58 | } 59 | } 60 | } 61 | 62 | @objc func swizzled_apply(_ layoutAttributes: NSCollectionViewLayoutAttributes) { 63 | cachedLayoutAttributes = layoutAttributes 64 | } 65 | 66 | @objc func swizzled_preferredLayoutAttributesFitting(_ layoutAttributes: NSCollectionViewLayoutAttributes) -> NSCollectionViewLayoutAttributes { 67 | if backgroundConfiguration != nil || contentConfiguration != nil { 68 | let width = layoutAttributes.size.width 69 | var fittingSize = view.sizeThatFits(CGSize(width: width, height: .infinity)) 70 | fittingSize.width = width 71 | layoutAttributes.size = fittingSize 72 | return layoutAttributes 73 | } 74 | return swizzled_preferredLayoutAttributesFitting(layoutAttributes) 75 | } 76 | 77 | @objc func swizzled_viewDidLayout() { 78 | switch collectionView?.selfSizingInvalidation { 79 | case .enabled: 80 | if let cachedLayoutAttributes = cachedLayoutAttributes { 81 | if view.frame != cachedLayoutAttributes.frame { 82 | Swift.debugPrint("Not the same. InvalidateSelfSizing") 83 | invalidateSelfSizing() 84 | } 85 | } 86 | case .enabledIncludingConstraints: 87 | break 88 | default: 89 | break 90 | } 91 | } 92 | 93 | var cachedLayoutAttributes: NSCollectionViewLayoutAttributes? { 94 | get { getAssociatedValue("cachedLayoutAttributes") } 95 | set { setAssociatedValue(newValue, key: "cachedLayoutAttributes") } 96 | } 97 | 98 | var layoutInvalidationContext: NSCollectionViewLayoutInvalidationContext? { 99 | guard let collectionView = collectionView, let indexPath = collectionView.indexPath(for: self) else { return nil } 100 | 101 | let context = InvalidationContext(invalidateEverything: false) 102 | context.invalidateItems(at: [indexPath]) 103 | return context 104 | } 105 | 106 | func invalidateSelfSizing() { 107 | guard let invalidationContext = layoutInvalidationContext, let collectionView = collectionView, let collectionViewLayout = collectionView.collectionViewLayout else { return } 108 | 109 | view.invalidateIntrinsicContentSize() 110 | 111 | collectionViewLayout.invalidateLayout(with: invalidationContext) 112 | collectionView.layoutSubtreeIfNeeded() 113 | } 114 | 115 | /// Invalidation of collection view items. 116 | class InvalidationContext: NSCollectionViewLayoutInvalidationContext { 117 | override public var invalidateEverything: Bool { 118 | _invalidateEverything 119 | } 120 | 121 | var _invalidateEverything: Bool 122 | 123 | public init(invalidateEverything: Bool) { 124 | _invalidateEverything = invalidateEverything 125 | } 126 | } 127 | } 128 | */ 129 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSTableView/NSTableView+DragSessionMove.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+DragSessionMove.swift 3 | // 4 | // 5 | // Created by Florian Zand on 02.03.25. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | /* 12 | extension NSTableView { 13 | var draggingSessionMovedHandler: ((NSDraggingSession, CGPoint)->())? { 14 | get { getAssociatedValue("draggingSessionMoveHandler") } 15 | set { 16 | setAssociatedValue(newValue, key: "draggingSessionMoveHandler") 17 | let selector = #selector(NSTableView.draggingSession(_:movedTo:)) 18 | if newValue != nil, !isMethodReplaced(selector), !isMethodHooked(selector) { 19 | do { 20 | if responds(to: selector) { 21 | try hook(selector, closure: { original, object, sel, session, point in 22 | (object as? NSTableView)?.draggingSessionMovedHandler?(session, point) 23 | original(object, sel, session, point) 24 | } as @convention(block) ( 25 | (AnyObject, Selector, NSDraggingSession, CGPoint) -> Void, 26 | AnyObject, Selector, NSDraggingSession, CGPoint) -> Void) 27 | } else { 28 | try addMethod(selector, 29 | methodSignature: (@convention(block) (AnyObject, NSDraggingSession, CGPoint) -> ()).self) { object, session, point in 30 | (object as? NSTableView)?.draggingSessionMovedHandler?(session, point) 31 | } 32 | } 33 | } catch { 34 | debugPrint(error) 35 | } 36 | } else if newValue == nil { 37 | resetMethod(selector) 38 | revertHooks(for: selector) 39 | } 40 | } 41 | } 42 | } 43 | */ 44 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSTableView/NSTableView+ReconfigureRows.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+ReconfigurateRows.swift 3 | // 4 | // 5 | // Created by Florian Zand on 18.05.22. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | extension NSTableView { 12 | /** 13 | Updates the data for the rows at the indexes you specify, preserving the existing row views and cells for the rows. 14 | 15 | To update the contents of existing (including prefetched) cells without replacing them with new row views nad cells, use this method instead of `reloadData(forRowIndexes:columnIndexes:)`. For optimal performance, choose to reconfigure rows instead of reloading rows unless you have an explicit need to replace the existing row view or cells with new. 16 | 17 | Your cell provider must dequeue the same type of cell for the provided index path, and must return the same existing cell for a given index path. Because this method reconfigures existing cells, the table view doesn’t call `prepareForReuse()` for each cell dequeued. If you need to return a different type of cell for an index path, use `reloadData(forRowIndexes:columnIndexes:)` instead. 18 | 19 | The same applies to your row view provider. 20 | 21 | - Parameters: 22 | - indexes: The indexes you want to update. 23 | */ 24 | public func reconfigureRows(at indexes: IndexSet) { 25 | Self.swizzleViewRegistration() 26 | guard let delegate = delegate else { return } 27 | let indexes = indexes.filter({$0 < numberOfRows}) 28 | let columns = tableColumns 29 | 30 | for row in indexes { 31 | if delegate.tableView?(self, isGroupRow: row) ?? false { 32 | if rowView(atRow: row, makeIfNecessary: false) != nil { 33 | reconfigureIndexPath = IndexPath(item: row, section: 0) 34 | _ = delegate.tableView?(self, viewFor: nil, row: row) 35 | } 36 | } else { 37 | for (index, column) in columns.enumerated() { 38 | if view(atColumn: index, row: row, makeIfNecessary: false) != nil { 39 | reconfigureIndexPath = IndexPath(item: row, section: index) 40 | _ = delegate.tableView?(self, viewFor: column, row: row) 41 | } 42 | if rowView(atRow: row, makeIfNecessary: false) != nil { 43 | reconfigureIndexPath = IndexPath(item: row, section: -1) 44 | _ = delegate.tableView?(self, rowViewForRow: row) 45 | } 46 | } 47 | } 48 | } 49 | reconfigureIndexPath = nil 50 | } 51 | 52 | var reconfigureIndexPath: IndexPath? { 53 | get { getAssociatedValue("reconfigureIndexPath") } 54 | set { setAssociatedValue(newValue, key: "reconfigureIndexPath") 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSTableView/NSTableView+Register.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+Nibless.swift 3 | // 4 | // 5 | // Created by Florian Zand on 10.12.22. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | 12 | extension NSTableView { 13 | /** 14 | Registers a view class for the specified identifier, so that view-based table views can use it to instantiate views. 15 | 16 | Use this method to associate the view class with the specified identifier. When you request a view using ``makeView(for:)``, the table view recycles an existing view with the same class or creates a new one by instantiating your class. 17 | 18 | - Parameter viewClass: The view class to register. 19 | */ 20 | public func register(_ viewClass: NSView.Type) { 21 | register(viewClass, forIdentifier: .init(viewClass)) 22 | } 23 | 24 | func register(_ viewClass: NSView.Type, forIdentifier identifier: NSUserInterfaceItemIdentifier) { 25 | Self.swizzleViewRegistration() 26 | registeredClassesByIdentifier[identifier] = viewClass 27 | registeredClassesByIdentifier = registeredClassesByIdentifier 28 | } 29 | 30 | /** 31 | Returns a new or existing view with the specified view class. 32 | 33 | To be able to create a reusable view using this method, you have to register it first via ``register(_:)``. 34 | 35 | When this method is called, the table view automatically instantiates the cell view with the specified owner, which is usually the table view’s delegate. (The owner is useful in setting up outlets and target/actions from the view.). 36 | 37 | This method may return a reused view with the same class that is no longer available on screen. 38 | 39 | Note that `awakeFromNib()` is called each time this method is called. 40 | 41 | - Parameter viewClass: The class of the view. 42 | 43 | - Returns:The view, or `nil` if the view class isn't registered or the view couldn't be created. 44 | */ 45 | public func makeView(for viewClass: View.Type) -> View? { 46 | makeView(for: viewClass, withIdentifier: .init(viewClass)) 47 | } 48 | 49 | func makeView(for _: View.Type, withIdentifier identifier: NSUserInterfaceItemIdentifier) -> View? { 50 | makeView(withIdentifier: identifier, owner: nil) as? View 51 | } 52 | 53 | /** 54 | The dictionary of all registered classes for view-based table view identifiers. 55 | 56 | Each key in the dictionary is the identifier used to register the view class in the ``register(_:)``. The value of each key is the corresponding view class. 57 | */ 58 | public private(set) var registeredClassesByIdentifier: [NSUserInterfaceItemIdentifier: NSView.Type] { 59 | get { getAssociatedValue("registeredClassesByIdentifier", initialValue: [:]) } 60 | set { setAssociatedValue(newValue, key: "registeredClassesByIdentifier") } 61 | } 62 | 63 | @objc private func swizzled_register(_ nib: NSNib?, forIdentifier identifier: NSUserInterfaceItemIdentifier) { 64 | if nib == nil { 65 | registeredClassesByIdentifier[identifier] = nil 66 | } 67 | swizzled_register(nib, forIdentifier: identifier) 68 | } 69 | 70 | @objc private func swizzled_makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?) -> NSView? { 71 | if isEnablingAutomaticRowHeights { 72 | isEnablingAutomaticRowHeights = false 73 | return nil 74 | } 75 | if let reconfigureIndexPath = reconfigureIndexPath { 76 | if reconfigureIndexPath.section != -1, let cell = view(atColumn: reconfigureIndexPath.section, row: reconfigureIndexPath.item, makeIfNecessary: false) { 77 | return cell 78 | } else if reconfigureIndexPath.section == -1, let rowView = rowView(atRow: reconfigureIndexPath.item, makeIfNecessary: false) { 79 | return rowView 80 | } 81 | } 82 | if let registeredViewClass = registeredClassesByIdentifier[identifier] { 83 | if let view = swizzled_makeView(withIdentifier: identifier, owner: owner) { 84 | return view 85 | } else { 86 | let view = registeredViewClass.init(frame: .zero) 87 | view.identifier = identifier 88 | return view 89 | } 90 | } 91 | let view = swizzled_makeView(withIdentifier: identifier, owner: owner) 92 | return view 93 | } 94 | 95 | private static var didSwizzleViewRegistration: Bool { 96 | get { getAssociatedValue("didSwizzleViewRegistration") ?? false } 97 | set { setAssociatedValue(newValue, key: "didSwizzleViewRegistration") } 98 | } 99 | 100 | static func swizzleViewRegistration() { 101 | guard !didSwizzleViewRegistration else { return } 102 | do { 103 | try Swizzle(NSTableView.self) { 104 | #selector(makeView(withIdentifier:owner:)) <-> #selector(swizzled_makeView(withIdentifier:owner:)) 105 | #selector((register(_:forIdentifier:)) as (NSTableView) -> (NSNib?, NSUserInterfaceItemIdentifier) -> Void) <-> #selector(swizzled_register(_:forIdentifier:)) 106 | } 107 | didSwizzleViewRegistration = true 108 | } catch { 109 | Swift.debugPrint(error) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSTableView/NSTableViewDiffableDataSource+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableViewDiffableDataSource+.swift 3 | // 4 | // 5 | // Created by Florian Zand on 23.07.23. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | 11 | // NSTableViewDiffableDataSource hides these tableview delegate functions 12 | public extension NSTableViewDiffableDataSource { 13 | /** 14 | Asks the datasource for a view to display the specified row and column. 15 | 16 | - Parameters: 17 | - tableView: The table view that sent the message. 18 | - tableColumn: The table column. If the row is a group row, `tableColumn` is `nil`. 19 | - row: The row index. 20 | 21 | - Returns: The view to display the specified column and row. 22 | */ 23 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 24 | let selector = NSSelectorFromString("_tableView:viewForTableColumn:row:") 25 | if let meth = class_getInstanceMethod(object_getClass(self), selector) { 26 | let imp = method_getImplementation(meth) 27 | typealias ClosureType = @convention(c) (AnyObject, Selector, NSTableView, NSTableColumn?, Int) -> NSView? 28 | let method: ClosureType = unsafeBitCast(imp, to: ClosureType.self) 29 | let view = method(self, selector, tableView, tableColumn, row) 30 | return view 31 | } 32 | return nil 33 | } 34 | 35 | /** 36 | Asks the delegate for a view to display the specified row. 37 | 38 | - Parameters: 39 | - tableView: The table view that sent the message. 40 | - row: The row index. 41 | 42 | - Returns: An instance or subclass of `NSTableRowView`. If `nil` is returned, an `NSTableRowView` instance will be created and used. 43 | */ 44 | func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { 45 | let selector = NSSelectorFromString("_tableView:rowViewForRow:") 46 | if let meth = class_getInstanceMethod(object_getClass(self), selector) { 47 | let imp = method_getImplementation(meth) 48 | typealias ClosureType = @convention(c) (AnyObject, Selector, NSTableView, Int) -> NSTableRowView? 49 | let method: ClosureType = unsafeBitCast(imp, to: ClosureType.self) 50 | let view = method(self, selector, tableView, row) 51 | return view 52 | } 53 | return nil 54 | } 55 | 56 | /** 57 | Returns whether the specified row is a group row. 58 | 59 | - Parameters: 60 | - tableView: The table view that sent the message. 61 | - row: The row index. 62 | 63 | - Returns: `true` if the specified row should have the group row style drawn, `false` otherwise. 64 | */ 65 | func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { 66 | let selector = NSSelectorFromString("_tableView:isGroupRow:") 67 | if let meth = class_getInstanceMethod(object_getClass(self), selector) { 68 | let imp = method_getImplementation(meth) 69 | typealias ClosureType = @convention(c) (AnyObject, Selector, NSTableView, Int) -> Bool 70 | let method: ClosureType = unsafeBitCast(imp, to: ClosureType.self) 71 | let value = method(self, selector, tableView, row) 72 | return value 73 | } 74 | return false 75 | } 76 | 77 | /// The cell provider of the datasource. 78 | var cellProvider: ((NSTableView, NSTableColumn, Int, ItemIdentifierType)->(NSView)) { 79 | typealias CellProviderBlock = @convention(block) (_ tableView: NSTableView, _ tableColumn: NSTableColumn, _ row: Int, _ identifier: Any) -> NSView 80 | guard let cellProvider: CellProviderBlock = getIvarValue(for: "_cellProvider") else { return { _,_,_,_ in return NSTableCellView() } } 81 | return cellProvider 82 | } 83 | 84 | /// Creates a new table cell view for the specified item using the cell provider. 85 | func createCellView(for item: ItemIdentifierType, tableColumn: NSTableColumn? = nil, tableView: NSTableView) -> NSView? { 86 | guard let tableColumn = tableColumn ?? tableView.tableColumns.first, tableView.tableColumns.contains(tableColumn) else { return nil } 87 | return cellProvider(tableView, tableColumn, 0, item) 88 | } 89 | 90 | /// Returns a preview image of the table cell for the specified item and table column. 91 | func previewImage(for item: ItemIdentifierType, tableView: NSTableView) -> NSImage? { 92 | let columns = tableView.tableColumns 93 | guard !columns.isEmpty else { return nil } 94 | return NSImage(combineHorizontal: columns.compactMap({ _previewImage(for: item, tableColumn: $0, tableView: tableView, useColumnWidth: $0 !== columns.last!) }), alignment: .top) 95 | } 96 | 97 | /// Returns a preview image of the table row for the specified item. 98 | func previewImage(for item: ItemIdentifierType, tableColumn: NSTableColumn, tableView: NSTableView) -> NSImage? { 99 | _previewImage(for: item, tableColumn: tableColumn, tableView: tableView) 100 | } 101 | 102 | /// Returns a preview image of the table rows for the specified items. 103 | func previewImage(for items: [ItemIdentifierType], tableView: NSTableView) -> NSImage? { 104 | return NSImage(combineVertical: items.compactMap({ previewImage(for: $0, tableView: tableView)}).reversed(), alignment: .left) 105 | } 106 | 107 | private func _previewImage(for item: ItemIdentifierType, tableColumn: NSTableColumn, tableView: NSTableView, useColumnWidth: Bool = true) -> NSImage? { 108 | guard tableView.tableColumns.contains(tableColumn), let view = createCellView(for: item, tableColumn: tableColumn, tableView: tableView) else { return nil } 109 | view.frame.size = view.systemLayoutSizeFitting(width: tableColumn.width) 110 | view.frame.size.width = useColumnWidth ? tableColumn.width : view.frame.size.width 111 | return view.renderedImage 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSTableView/NSTableViewDiffableDataSource+Apply.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableViewDiffableDataSource+Apply.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.12.22. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSTableViewDiffableDataSource { 11 | /** 12 | Updates the UI to reflect the state of the data in the specified snapshot, optionally animating the UI changes and executing a completion handler. 13 | 14 | It’s safe to call this method from a background queue, but you must do so consistently in your app. Always call this method exclusively from the main queue or from a background queue. 15 | 16 | - Parameters: 17 | - snapshot: The snapshot reflecting the new state of the data in the table view. 18 | - option: Option how to apply the snapshot to the table view. The default value is `animated`. 19 | - completion: An optional closure to be executed when the animations are complete. The system calls this closure from the main queue. 20 | */ 21 | public func apply(_ snapshot: NSDiffableDataSourceSnapshot, _ option: NSDiffableDataSourceSnapshotApplyOption = .animated, completion: (() -> Void)? = nil) { 22 | switch option { 23 | case .usingReloadData: 24 | applySnapshotUsingReloadData(snapshot, completion: completion) 25 | case .animated: 26 | apply(snapshot, animated: true, animationDuration: option.animationDuration, completion: completion) 27 | case .withoutAnimation: 28 | apply(snapshot, animated: false, completion: completion) 29 | } 30 | } 31 | 32 | private func applySnapshotUsingReloadData(_ snapshot: NSDiffableDataSourceSnapshot, completion: (() -> Void)? = nil) { 33 | apply(snapshot, animatingDifferences: false, completion: completion) 34 | } 35 | 36 | private func apply(_ snapshot: NSDiffableDataSourceSnapshot, animated: Bool = true, animationDuration: TimeInterval? = nil, completion: (() -> Void)? = nil) { 37 | if animated, animationDuration == nil { 38 | apply(snapshot, animatingDifferences: true, completion: completion) 39 | } else { 40 | NSAnimationContext.beginGrouping() 41 | NSAnimationContext.current.duration = animationDuration ?? 0 42 | apply(snapshot, animatingDifferences: true, completion: completion) 43 | NSAnimationContext.endGrouping() 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/NSTableView/NSTableViewDiffableDataSource+Registration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableViewDiffableDataSource+Registration.swift 3 | // 4 | // 5 | // Created by Florian Zand on 10.12.22. 6 | // 7 | 8 | import AppKit 9 | 10 | public extension NSTableViewDiffableDataSource { 11 | /** 12 | Creates a diffable data source with the specified cell registration, and connects it to the specified table view. 13 | 14 | - Parameters: 15 | - tableView: The initialized table view object to connect to the diffable data source. 16 | - cellRegistration: A cell registration that creates, configurates and returns each of the cells for the table view from the data the diffable data source provides. 17 | */ 18 | convenience init(tableView: NSTableView, cellRegistration: NSTableView.CellRegistration) { 19 | self.init(tableView: tableView, cellProvider: { 20 | _tableView, column, row, item in 21 | _tableView.makeCellView(using: cellRegistration, forColumn: column, row: row, item: item)! 22 | }) 23 | } 24 | 25 | /** 26 | Creates a diffable data source with the specified cell registrations, and connects it to the specified table view. 27 | 28 | - Parameters: 29 | - tableView: The initialized table view object to connect to the diffable data source. 30 | - cellRegistrations: Cell registrations that create, configurate and return each of the cells for the table view from the data the diffable data source provides. 31 | 32 | - Important: Each of the cell registrations need to have a column identifier. 33 | */ 34 | convenience init(tableView: NSTableView, cellRegistrations: [NSTableViewCellRegistration]) { 35 | self.init(tableView: tableView, cellProvider: { 36 | _, column, row, element in 37 | if let cellRegistration = (cellRegistrations.first(where: { $0.columnIdentifiers.contains(column.identifier) }) ?? cellRegistrations.first(where: { $0.columnIdentifiers.isEmpty })) as? _NSTableViewCellRegistration { 38 | return cellRegistration.makeView(tableView, column, row, element) ?? NSTableCellView() 39 | } 40 | return NSTableCellView() 41 | }) 42 | } 43 | 44 | /** 45 | Creates a diffable data source with the specified cell and section view registration, and connects it to the specified table view. 46 | 47 | - Parameters: 48 | - tableView: The initialized table view object to connect to the diffable data source. 49 | - cellRegistration: A cell registration that creates, configurates and returns each of the cells for the table view from the data the diffable data source provides. 50 | - sectionHeaderRegistration: A section view registration that creates, configurates and returns each of the section header views for the table view from the data the diffable data source provides. 51 | */ 52 | convenience init(tableView: NSTableView, cellRegistration: NSTableView.CellRegistration, sectionHeaderRegistration: NSTableView.CellRegistration) { 53 | self.init(tableView: tableView, cellProvider: { 54 | _tableView, column, row, item in 55 | _tableView.makeCellView(using: cellRegistration, forColumn: column, row: row, item: item)! 56 | }) 57 | applySectionHeaderViewRegistration(sectionHeaderRegistration) 58 | } 59 | 60 | /** 61 | Creates a diffable data source with the specified cell registrations, and connects it to the specified table view. 62 | 63 | - Parameters: 64 | - tableView: The initialized table view object to connect to the diffable data source. 65 | - cellRegistrations: Cell registrations that create, configurate and return each of the cells for the table view from the data the diffable data source provides. 66 | - sectionHeaderRegistration: A section view registration that creates, configurates and returns each of the section header views for the table view from the data the diffable data source provides. 67 | 68 | - Important: Each of the cell registrations need to have a column identifier. 69 | */ 70 | convenience init(tableView: NSTableView, cellRegistrations: [NSTableViewCellRegistration], sectionHeaderRegistration: NSTableView.CellRegistration) { 71 | self.init(tableView: tableView, cellProvider: { 72 | _, column, row, element in 73 | if let cellRegistration = cellRegistrations.first(where: { $0.columnIdentifiers.contains(column.identifier) }) ?? cellRegistrations.first(where: { $0.columnIdentifiers.isEmpty }) { 74 | return (cellRegistration as! _NSTableViewCellRegistration).makeView(tableView, column, row, element)! 75 | } 76 | return NSTableCellView() 77 | }) 78 | applySectionHeaderViewRegistration(sectionHeaderRegistration) 79 | } 80 | 81 | /// Uses the specified row view registration to configure and return row views. 82 | func useRowViewRegistration(_ registration: NSTableView.RowRegistration) { 83 | self.rowViewProvider = { tableview, row, item in 84 | if let item = item as? ItemIdentifierType { 85 | return tableview.makeRowView(using: registration, forRow: row, item: item) 86 | } 87 | return NSTableRowView() 88 | } 89 | } 90 | 91 | /// Uses the specified cell registration to configure and return section header views. 92 | func applySectionHeaderViewRegistration(_ registration: NSTableView.CellRegistration) { 93 | sectionHeaderViewProvider = { tableView, row, section in 94 | if let column = tableView.tableColumns.first, let cellView = tableView.makeCellView(using: registration, forColumn: column, row: row, item: section) { 95 | return cellView 96 | } 97 | return NSTableCellView() 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/Shared/NSDiffableDataSourceSnapshot+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDiffableDataSourceSnapshot+.swift 3 | // 4 | // 5 | // Created by Florian Zand on 28.12.23. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSDiffableDataSourceSnapshot where ItemIdentifierType: Identifiable, SectionIdentifierType: Identifiable { 11 | /// A snapshot from the section and item identifiers. 12 | typealias IdentifiableSnapshot = NSDiffableDataSourceSnapshot 13 | typealias Transaction = DiffableDataSourceTransaction 14 | 15 | /// Creates a snapshot from the section and item identifiers. 16 | func toIdentifiableSnapshot() -> IdentifiableSnapshot { 17 | var identifiableSnapshot = IdentifiableSnapshot() 18 | let sections = sectionIdentifiers 19 | identifiableSnapshot.appendSections(sections.ids) 20 | for section in sections { 21 | let items = itemIdentifiers(inSection: section) 22 | identifiableSnapshot.appendItems(items.ids, toSection: section.id) 23 | } 24 | return identifiableSnapshot 25 | } 26 | 27 | func nextItemForDeleting(_ items: [ItemIdentifierType]) -> ItemIdentifierType? { 28 | guard let delete = items.last, let index = indexOfItem(delete), let item = itemIdentifiers[safe: index+1] ?? itemIdentifiers[safe: index-1] else { return nil } 29 | if sectionIdentifier(containingItem: item) != sectionIdentifier(containingItem: delete), let section = sectionIdentifier(containingItem: delete) { 30 | if let item = itemIdentifiers(inSection: section).reversed().first(where: { !items.contains($0) }) { 31 | return item 32 | } else if let index = indexOfSection(section), let section = sectionIdentifiers[safe: index-1], let item = itemIdentifiers(inSection: section).reversed().first(where: { !items.contains($0) }) { 33 | return item 34 | } 35 | } 36 | return item 37 | } 38 | 39 | func moveTransaction(_ section: SectionIdentifierType, after afterSection: SectionIdentifierType) -> Transaction { 40 | var snapshot = self 41 | snapshot.moveSection(section, afterSection: afterSection) 42 | return Transaction(initial: self, final: snapshot) 43 | } 44 | 45 | func moveTransaction(_ section: SectionIdentifierType, before beforeSection: SectionIdentifierType) -> Transaction { 46 | var snapshot = self 47 | snapshot.moveSection(section, beforeSection: beforeSection) 48 | return Transaction(initial: self, final: snapshot) 49 | } 50 | 51 | func deleteTransaction(_ items: [ItemIdentifierType]) -> Transaction { 52 | var finalSnapshot = self 53 | finalSnapshot.deleteItems(items) 54 | return DiffableDataSourceTransaction(initial: self, final: finalSnapshot) 55 | } 56 | 57 | mutating func insertItemsSaftly(_ identifiers: [ItemIdentifierType], afterItem: ItemIdentifierType) { 58 | if identifiers.contains(afterItem) { 59 | if let section = sectionIdentifier(containingItem: afterItem) { 60 | let identifiers = identifiers.filter({ sectionIdentifier(containingItem: $0) != section }) 61 | insertItems(identifiers, afterItem: afterItem) 62 | } 63 | } else { 64 | insertItems(identifiers, afterItem: afterItem) 65 | } 66 | } 67 | 68 | mutating func insertItemsSaftly(_ identifiers: [ItemIdentifierType], beforeItem: ItemIdentifierType) { 69 | if identifiers.contains(beforeItem) { 70 | if let section = sectionIdentifier(containingItem: beforeItem) { 71 | let identifiers = identifiers.filter({ sectionIdentifier(containingItem: $0) != section }) 72 | insertItems(identifiers, beforeItem: beforeItem) 73 | } 74 | } else { 75 | insertItems(identifiers, beforeItem: beforeItem) 76 | } 77 | } 78 | 79 | /* 80 | func movingTransaction(_ items: [ItemIdentifierType], item: ItemIdentifierType?, section: SectionIdentifierType?) -> DiffableDataSourceTransaction? { 81 | var newSnapshot = self 82 | if let item = item { 83 | newSnapshot.insertItems(items, beforeItem: item) 84 | } else if let section = section { 85 | item 86 | if let item = item(forRow: row - 1) { 87 | newSnapshot.insertItems(newItems, afterItem: item) 88 | } else { 89 | newSnapshot.appendItems(newItems, toSection: section) 90 | } 91 | } else if let section = sections.last { 92 | newSnapshot.appendItems(newItems, toSection: section) 93 | } 94 | return DiffableDataSourceTransaction(initial: currentSnapshot, final: newSnapshot) 95 | 96 | } 97 | */ 98 | 99 | /* 100 | func nextItemForDeleting(_ items: [ItemIdentifierType]) -> ItemIdentifierType? { 101 | guard let delete = items.last, let index = indexOfItem(delete), let item = itemIdentifiers[safe: index-1] else { return nil } 102 | if sectionIdentifier(containingItem: item) != sectionIdentifier(containingItem: delete), let section = sectionIdentifier(containingItem: delete), let item = itemIdentifiers(inSection: section).first(where: { !items.contains($0) }) { 103 | return item 104 | } 105 | return item 106 | } 107 | */ 108 | } 109 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/Shared/NSDiffableDataSourceSnapshot+ApplyOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDiffableDataSourceSnapshot+ApplyOption.swift 3 | // 4 | // 5 | // Created by Florian Zand on 23.07.23. 6 | // 7 | 8 | import AppKit 9 | 10 | /** 11 | Options for applying a snapshot to a diffable data source. 12 | 13 | Apple's `apply(_:animatingDifferences:completion:)` provides two options for applying snapshots to a diffable data source depending on `animatingDifferences`: 14 | - `true` applies a diff of the old and new state and applies the updates to the receiver animated. 15 | - `false` is equivalent to calling `reloadData()`. It reloads every item/cell of the receiver. 16 | 17 | **Non-animated diff** 18 | 19 | `NSDiffableDataSourceSnapshotApplyOption` lets you perform a diff even without animations using `withoutAnimation` for much better performance compared to using Apple's `reloadData()`. 20 | 21 | ```swift 22 | diffableDatasource.apply(snapshot, .withoutAnimation) 23 | ``` 24 | 25 | **Animation duration** 26 | 27 | When you want to apply the snapshot animated, you can also change the animation duration using `animated(duration:)`. 28 | 29 | ```swift 30 | diffableDatasource.apply(snapshot, .animated(duration: 1.0)) 31 | ``` 32 | */ 33 | public enum NSDiffableDataSourceSnapshotApplyOption: Hashable, Sendable { 34 | /** 35 | The snapshot gets applied animated. 36 | 37 | The data source computes a diff of the previous and new state and applies the updates to the receiver animated with a default animation duration. 38 | */ 39 | public static var animated: Self { .animated(duration: noAnimationDuration) } 40 | 41 | /** 42 | The snapshot gets applied animiated with the specified animation duration. 43 | 44 | The data source computes a diff of the previous and new state and applies the updates to the receiver animated with the specified animation duration. 45 | */ 46 | case animated(duration: TimeInterval) 47 | 48 | /** 49 | The snapshot gets applied using `reloadData()`. 50 | 51 | The system resets the UI to reflect the state of the data in the snapshot without computing a diff or animating the changes. 52 | */ 53 | case usingReloadData 54 | /** 55 | The snapshot gets applied without any animation. 56 | 57 | The data source computes a diff of the previous and new state and applies the updates to the receiver without any animation. 58 | */ 59 | case withoutAnimation 60 | 61 | static var noAnimationDuration: TimeInterval { 2_344_235 } 62 | 63 | var animationDuration: TimeInterval? { 64 | switch self { 65 | case let .animated(duration): 66 | guard duration != Self.noAnimationDuration else { return nil } 67 | guard let currentEvent = NSApplication.shared.currentEvent else { return duration } 68 | let flags = currentEvent.modifierFlags.intersection([.shift, .option, .control, .command]) 69 | return duration * (flags == .shift ? 10 : 1) 70 | default: 71 | return nil 72 | } 73 | } 74 | 75 | var isReloadData: Bool { 76 | switch self { 77 | case .usingReloadData: return true 78 | default: return false 79 | } 80 | } 81 | 82 | var isWithoutAbinatuib: Bool { 83 | switch self { 84 | case .withoutAnimation: return true 85 | default: return false 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/Shared/NSPasteboardItem+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSPasteboardItem+.swift 3 | // 4 | // 5 | // Created by Florian Zand on 16.03.25. 6 | // 7 | 8 | import AppKit 9 | import FZUIKit 10 | 11 | 12 | extension NSPasteboardItem { 13 | convenience init(for element: Element, content: [PasteboardWriting]? = nil) { 14 | self.init(content: content ?? []) 15 | setString(String(element.id.hashValue), forType: .itemID) 16 | } 17 | 18 | convenience init(forItem item: Item, content: [PasteboardWriting]? = nil) { 19 | self.init(content: content ?? []) 20 | setString(String(describing: item), forType: .itemID) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Extensions/Shared/NSView+Transform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSView+Transform.swift 3 | // 4 | // 5 | // Created by Florian Zand on 03.03.25. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | 12 | extension NSView { 13 | var _scaleTransform: Scale { 14 | get { getAssociatedValue("_scaleTransform") ?? .none } 15 | set { 16 | guard newValue != _scaleTransform else { return } 17 | setAssociatedValue(newValue, key: "_scaleTransform") 18 | anchorPoint = .center 19 | animatorIfNeeded().scale = newValue 20 | } 21 | } 22 | 23 | var _rotation: Rotation { 24 | get { getAssociatedValue("_rotation") ?? .zero } 25 | set { 26 | guard newValue != _rotation else { return } 27 | setAssociatedValue(newValue, key: "_rotation") 28 | anchorPoint = .center 29 | animatorIfNeeded().rotation = newValue 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/AdvancedCollectionTableView/Registrations/NSTableView/RowRegistration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RowRegistration.swift 3 | // 4 | // 5 | // Created by Florian Zand on 27.04.22. 6 | // 7 | 8 | import AppKit 9 | import FZSwiftUtils 10 | import FZUIKit 11 | 12 | public extension NSTableView { 13 | /** 14 | A registration for the table view’s row views. 15 | 16 | Use a row registration to register row views with your table view and configure each row for display. You create a row registration with your row type and data row type as the registration’s generic parameters, passing in a registration handler to configure the row. In the registration handler, you specify how to configure the content and appearance of that type of row. 17 | 18 | The following example creates a row registration for row views of type [NSTableRowView](https://developer.apple.com/documentation/appkit/nstablerowview). 19 | 20 | ```swift 21 | let rowRegistration = NSTableView.RowRegistration { row, indexPath, string in 22 | 23 | } 24 | ``` 25 | 26 | After you create a row registration, you pass it in to ``makeRowView(using:forRow:item:)``, which you call from your data source’s row provider. 27 | 28 | ```swift 29 | dataSource.rowProvider = { tableView, row, item in 30 | return tableView.makeRowView(using: rowRegistration, forRow: row, item) 31 | } 32 | ``` 33 | */ 34 | struct RowRegistration where RowView: NSTableRowView { 35 | let identifier: NSUserInterfaceItemIdentifier = .init(UUID().uuidString) 36 | let nib: NSNib? 37 | let handler: Handler 38 | 39 | // MARK: Creating a row registration 40 | 41 | /** 42 | Creates a row registration with the specified registration handler. 43 | 44 | - Parameters: 45 | - handler: The handler to configurate the row view. 46 | */ 47 | public init(handler: @escaping Handler) { 48 | self.handler = handler 49 | self.nib = nil 50 | } 51 | 52 | /** 53 | Creates a row registration with the specified registration handler and nib file. 54 | 55 | - Parameters: 56 | - nib: The nib of the row view. 57 | - handler: The handler to configurate the row view. 58 | */ 59 | public init(nib: NSNib, handler: @escaping Handler) { 60 | self.handler = handler 61 | self.nib = nib 62 | } 63 | 64 | /// A closure that handles the row registration and configuration. 65 | public typealias Handler = (_ rowView: RowView, _ row: Int, _ item: Item) -> Void 66 | 67 | func makeView(_ tableView: NSTableView, _ row: Int, _ item: Item) -> RowView { 68 | let rowView = (tableView.makeView(withIdentifier: identifier, owner: self) as? RowView) ?? RowView(frame: .zero) 69 | rowView.identifier = identifier 70 | handler(rowView, row, item) 71 | return rowView 72 | } 73 | } 74 | } 75 | 76 | extension NSTableView { 77 | /** 78 | Dequeues a configured reusable row view object. 79 | 80 | - Parameters: 81 | - registration: The row view registration for configuring the rowview object. See ``RowRegistration``. 82 | - row: The index path specifying the row of the row. The data source receives this information when it is asked for the row and should just pass it along. This method uses the row to perform additional configuration based on the row’s position in the table view. 83 | - item: The item that provides data for the row. 84 | 85 | - Returns:A configured reusable row view object. 86 | */ 87 | public func makeRowView(using registration: RowRegistration, forRow row: Int, item: Item) -> RowView where RowView: NSTableRowView { 88 | registration.makeView(self, row, item) 89 | } 90 | } 91 | --------------------------------------------------------------------------------