From 2b8d944994fdcf746d47168e236e5c555d64f0fa Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Tue, 15 Mar 2022 12:02:18 -0700 Subject: [PATCH 01/16] begin replacing leading view with supplementary view --- CollectionView/CollectionView.swift | 30 +- CollectionView/Constants.swift | 7 + CollectionView/Info.plist | 2 +- .../Layouts/CollectionViewColumnLayout.swift | 443 +++++++++++------- .../Layouts/CollectionViewFlowLayout.swift | 46 +- .../CollectionViewHorizontalLayout.swift | 55 ++- .../Layouts/CollectionViewLayout.swift | 173 +++---- .../Layouts/CollectionViewListLayout.swift | 50 +- .../Preview/CollectionViewPreviewLayout.swift | 36 +- 9 files changed, 495 insertions(+), 347 deletions(-) diff --git a/CollectionView/CollectionView.swift b/CollectionView/CollectionView.swift index 64b0014..e361f1f 100644 --- a/CollectionView/CollectionView.swift +++ b/CollectionView/CollectionView.swift @@ -127,13 +127,15 @@ open class CollectionView: ScrollView, NSDraggingSource { self.layer?.backgroundColor = self.drawsBackground ? self.backgroundColor.cgColor : nil } + @available(*, deprecated, message: "Use a leading supplementary view instead") public var leadingView: NSView? { didSet { if oldValue == leadingView { return } oldValue?.removeFromSuperview() + self.needsLayoutReload = true + self.needsLayout = true if let v = leadingView { self.contentDocumentView.addSubview(v) - self.contentDocumentView.addConstraints([ NSLayoutConstraint(item: self.contentDocumentView, attribute: .left, relatedBy: .equal, toItem: v, attribute: .left, multiplier: 1, constant: 0), @@ -141,6 +143,8 @@ open class CollectionView: ScrollView, NSDraggingSource { toItem: v, attribute: .top, multiplier: 1, constant: 0), NSLayoutConstraint(item: self.contentDocumentView, attribute: .right, relatedBy: .equal, toItem: v, attribute: .right, multiplier: 1, constant: 0) +// NSLayoutConstraint(item: self.contentDocumentView.contentView, attribute: .top, relatedBy: .equal, +// toItem: v, attribute: .bottom, multiplier: 1, constant: 0) ]) v.translatesAutoresizingMaskIntoConstraints = false v.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 1000), for: .vertical) @@ -389,7 +393,7 @@ open class CollectionView: ScrollView, NSDraggingSource { /// The layout used to organize the collected view’s items. /// /// - Note: Assigning a new layout object to this property does **NOT** apply the layout to the collection view. Call `reloadData()` or `reloadLayout(_:)` to do so. - public var collectionViewLayout: CollectionViewLayout = CollectionViewLayout() { + public var collectionViewLayout: CollectionViewLayout = CollectionViewListLayout() { didSet { collectionViewLayout.collectionView = self self.hasHorizontalScroller = collectionViewLayout.scrollDirection == .horizontal @@ -520,19 +524,21 @@ open class CollectionView: ScrollView, NSDraggingSource { self.reloadLayout(animated, scrollPosition: .none, completion: nil) } } - } open override func layout() { self._floatingSupplementaryView.frame = self.bounds -// self.layoutLeadingViews() + + let leadingViewHeight = self.leadingView?.bounds.size.height ?? 0 + super.layout() if needsLayoutReload || self.collectionViewLayout.shouldInvalidateLayout(forBoundsChange: self.contentVisibleRect) { - setContentViewSize() - - prepareLayout(reloadData: reloadDataOnBoundsChange) - setContentViewSize() + func performLayout() { + prepareLayout(reloadData: reloadDataOnBoundsChange) + setContentViewSize() + } + performLayout() // Don't pin when implicitly reloading if !self.needsLayoutReload, let ip = _topIP { @@ -581,14 +587,6 @@ open class CollectionView: ScrollView, NSDraggingSource { self.collectionViewLayout.prepare() } -// private func layoutLeadingViews() { -// if let v = self.leadingView { -// v.frame.size.width = self.bounds.size.width - (self.contentInsets.left + self.contentInsets.right) -// v.frame.origin.x = 0 -// v.needsLayout = true -// } -// } - private func _reloadLayout(_ animated: Bool, scrollPosition: CollectionViewScrollPosition = .nearest, completion: AnimationCompletion?, needsRecalculation: Bool) { self._layoutRequested = false // self.layoutLeadingViews() diff --git a/CollectionView/Constants.swift b/CollectionView/Constants.swift index ed458df..d682a25 100644 --- a/CollectionView/Constants.swift +++ b/CollectionView/Constants.swift @@ -67,3 +67,10 @@ public enum CollectionViewDirection { case up case down } + +/// CollectionViewLayoutElementKind +public struct CollectionViewLayoutElementKind { + public static let LeadingView: String = "CollectionElementKindLeadingView" + public static let SectionHeader: String = "CollectionElementKindSectionHeader" + public static let SectionFooter: String = "CollectionElementKindSectionFooter" +} diff --git a/CollectionView/Info.plist b/CollectionView/Info.plist index 5247fc4..f86190b 100644 --- a/CollectionView/Info.plist +++ b/CollectionView/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion diff --git a/CollectionView/Layouts/CollectionViewColumnLayout.swift b/CollectionView/Layouts/CollectionViewColumnLayout.swift index 2fb1132..e7c792e 100644 --- a/CollectionView/Layouts/CollectionViewColumnLayout.swift +++ b/CollectionView/Layouts/CollectionViewColumnLayout.swift @@ -9,7 +9,7 @@ import Foundation /// The delegate for CollectionViewColumnLayout to dynamically customize the layout -@objc public protocol CollectionViewDelegateColumnLayout: CollectionViewDelegate { +public protocol CollectionViewDelegateColumnLayout { // MARK: - Spacing & Insets /*-------------------------------------------------------------------------------*/ @@ -21,8 +21,9 @@ import Foundation /// - Parameter section: A section index /// /// - Returns: The desired number of columns in the section - @objc optional func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - numberOfColumnsInSection section: Int) -> Int + func collectionView(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + numberOfColumnsInSection section: Int) -> Int /// Asks the delegate for insets to be applied to content of a given section /// @@ -31,8 +32,9 @@ import Foundation /// - Parameter section: A section index /// /// - Returns: Insets for the section - @objc optional func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - insetForSectionAt section: NSInteger) -> NSEdgeInsets + func collectionView(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + insetForSectionAt section: NSInteger) -> NSEdgeInsets // Between to items in the same column @@ -43,8 +45,9 @@ import Foundation /// - Parameter section: A section index /// /// - Returns: The desired spacing between items in the same column - @objc optional func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - interitemSpacingForSectionAt section: Int) -> CGFloat + func collectionView(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + interitemSpacingForSectionAt section: Int) -> CGFloat /// Asks the delegate for the column spacing to applied to items in a given section /// @@ -53,8 +56,9 @@ import Foundation /// - Parameter section: A section index /// /// - Returns: The desired spacing between columns in the section - @objc optional func collectionview(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - columnSpacingForSectionAt section: Int) -> CGFloat + func collectionview(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + columnSpacingForSectionAt section: Int) -> CGFloat // MARK: - Item Size /*-------------------------------------------------------------------------------*/ @@ -66,8 +70,9 @@ import Foundation /// - parameter indexPath: The indexPath for the item /// /// - returns: The height for the item - @objc optional func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - heightForItemAt indexPath: IndexPath) -> CGFloat + func collectionView(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + heightForItemAt indexPath: IndexPath) -> CGFloat /// The aspect ration for the item at the given indexPath (Priority 1). Width and height must be greater than 0. /// @@ -76,20 +81,29 @@ import Foundation /// - parameter indexPath: The indexPath for the item /// /// - returns: The aspect ration for the item - @objc optional func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - aspectRatioForItemAt indexPath: IndexPath) -> CGSize + func collectionView(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + aspectRatioForItemAt indexPath: IndexPath) -> CGSize // MARK: - Header & Footer Size /*-------------------------------------------------------------------------------*/ + /// Asks the delegate for the height of the leading view + /// + /// - Parameter collectionView: The collection view + /// - Parameter collectionViewLayout: The layout + /// - Returns: The desired leading view height or 0 for no view + func collectionViewLeadingViewHeight(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout) -> CGFloat + /// Asks the delegate for the height of the header in the given section /// /// - Parameter collectionView: The collection view /// - Parameter collectionViewLayout: The layout /// - Parameter section: A section index /// - Returns: The desired header height or 0 for no header - @objc optional func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - heightForHeaderInSection section: Int) -> CGFloat + func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewColumnLayout, + heightForHeaderInSection section: Int) -> CGFloat /// Asks the delegate for the height of the footer in the given section /// @@ -97,27 +111,65 @@ import Foundation /// - Parameter collectionViewLayout: The layout /// - Parameter section: A section index /// - Returns: The desired footer height or 0 for no footer - @objc optional func collectionView (_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, - heightForFooterInSection section: Int) -> CGFloat - + func collectionView (_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + heightForFooterInSection section: Int) -> CGFloat } -/// CollectionViewLayoutElementKind -public struct CollectionViewLayoutElementKind { - public static let SectionHeader: String = "CollectionElementKindSectionHeader" - public static let SectionFooter: String = "CollectionElementKindSectionFooter" -} - -extension CollectionViewColumnLayout { - @available(*, deprecated, renamed: "LayoutStrategy") - public typealias ItemRenderDirection = LayoutStrategy - - @available(*, deprecated, renamed: "layoutStrategy") - open var itemRenderDirection: LayoutStrategy { - get { return layoutStrategy } - set { self.layoutStrategy = newValue } - } -} +//extension CollectionViewDelegateColumnLayout { +// +// public func collectionView(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// numberOfColumnsInSection section: Int) -> Int { +// return collectionViewLayout.columnCount +// } +// +// public func collectionView(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// insetForSectionAt section: NSInteger) -> NSEdgeInsets { +// return collectionViewLayout.sectionInset +// } +// +// public func collectionView(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// interitemSpacingForSectionAt section: Int) -> CGFloat { +// return collectionViewLayout.interitemSpacing +// } +// +// public func collectionview(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// columnSpacingForSectionAt section: Int) -> CGFloat { +// return collectionViewLayout.columnSpacing +// } +// +// public func collectionView(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// heightForItemAt indexPath: IndexPath) -> CGFloat { +// return collectionViewLayout.itemHeight +// } +// +// public func collectionView(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// aspectRatioForItemAt indexPath: IndexPath) -> CGSize { +// return .zero +// } +// +// public func collectionViewLeadingViewHeight(_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout) -> CGFloat { +// return 0 +// } +// +// public func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewColumnLayout, +// heightForHeaderInSection section: Int) -> CGFloat { +// return collectionViewLayout.headerHeight +// } +// +// public func collectionView (_ collectionView: CollectionView, +// layout collectionViewLayout: CollectionViewColumnLayout, +// heightForFooterInSection section: Int) -> CGFloat { +// return collectionViewLayout.footerHeight +// } +//} /** This layout is column based which means you provide the number of columns and cells are placed in the appropriate one. It can be display items all the same size or as a "Pinterest" style layout. @@ -136,9 +188,15 @@ extension CollectionViewColumnLayout { Mixed use of ratios and heights is also supported. Returning CGSize.zero for a ratio will fall back to the hight. If a valid ratio and height are provided, the height will be appended to the height to respect the ratio. For example, if the column width comes out to 100, a ratio of 2 will determine a height of 200. If a height is also provided by the delegate for the same item, say 20 it will be added, totalling 220. -*/ + */ open class CollectionViewColumnLayout: CollectionViewLayout { + public var collectionView: CollectionView? + + public var scrollDirection: CollectionViewScrollDirection { return .vertical} + + public var allIndexPaths = OrderedSet() + /// The method to use when directing items into columns /// /// - shortestFirst: Use the current column @@ -151,150 +209,61 @@ open class CollectionViewColumnLayout: CollectionViewLayout { } // MARK: - Default layout values + /// If supporting views should be pinned to the top of the view + open var pinHeadersToTop: Bool = true /// The default column count open var columnCount: NSInteger = 2 { didSet { invalidate() }} - + /// The spacing between each column open var columnSpacing: CGFloat = 8 { didSet { invalidate() }} /// The vertical spacing between items in the same column open var interitemSpacing: CGFloat = 8 { didSet { invalidate() }} - + /// The height of section header views open var headerHeight: CGFloat = 0.0 { didSet { invalidate() }} - + /// The height of section footer views open var footerHeight: CGFloat = 0.0 { didSet { invalidate() }} - + /// The default height to apply to all items open var itemHeight: CGFloat = 50 { didSet { invalidate() }} - + /// If supplementary views should respect section insets or fill the CollectionView width open var insetSupplementaryViews: Bool = false { didSet { invalidate() }} /// If set to true, the layout will invalidate on all bounds changes, if false only on width changes open var invalidateOnBoundsChange: Bool = false { didSet { invalidate() }} - + /// Default insets for all sections open var sectionInset: NSEdgeInsets = NSEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) { didSet { invalidate() }} // MARK: - Render Options /// A hint as to how to render items when deciding which column to place them in open var layoutStrategy: LayoutStrategy = .leftToRight { didSet { invalidate() }} - + // private property and method above. - private weak var delegate: CollectionViewDelegateColumnLayout? { return self.collectionView!.delegate as? CollectionViewDelegateColumnLayout } + private var delegate: CollectionViewDelegateColumnLayout? { return self.collectionView!.delegate as? CollectionViewDelegateColumnLayout } + private var leadingViewAttributes: CollectionViewLayoutAttributes? private var sections: [SectionAttributes] = [] - private class Column { - var frame: CGRect - var height: CGFloat { return items.last?.frame.maxY ?? 0 } - var items: [CollectionViewLayoutAttributes] = [] - init(frame: CGRect) { - self.frame = frame - } - func append(item: CollectionViewLayoutAttributes) { - self.items.append(item) - self.frame = self.frame.union(item.frame) - } - } - - private class SectionAttributes: CustomStringConvertible { - var frame = CGRect.zero - var contentFrame = CGRect.zero - let insets: NSEdgeInsets - var header: CollectionViewLayoutAttributes? - var footer: CollectionViewLayoutAttributes? - - var columns = [Column]() - var items = [CollectionViewLayoutAttributes]() - - init(frame: CGRect, insets: NSEdgeInsets) { - self.frame = frame - self.insets = insets - } - - func prepareColumns(_ count: Int, spacing: CGFloat, in rect: CGRect) { - self.contentFrame = rect - let y = rect.minY - let gapCount = CGFloat(count-1) - let width = round((rect.width - (gapCount * spacing)) / CGFloat(count)) - var x = rect.minX - spacing - width - - self.columns = (0.. Column in - x += (spacing + width) - return Column(frame: CGRect(x: x, y: y, width: width, height: 0)) - }) - } - - var description: String { - return "Section Attributes : \(frame) content: \(contentFrame) Items: \(items.count)" - } - - func addItem(for indexPath: IndexPath, aspectRatio ratio: CGSize?, variableHeight: CGFloat?, defaultHeight: CGFloat, spacing: CGFloat, strategy: LayoutStrategy) { - - let column = self.nextColumnIndexForItem(indexPath, strategy: strategy) - let width = column.frame.size.width - - var itemHeight: CGFloat = 0 - if let ratio = ratio, ratio.width != 0 && ratio.height != 0 { - let h = ratio.height * (width/ratio.width) - itemHeight = floor(h) - - if let addHeight = variableHeight { - itemHeight += addHeight - } - } else { - itemHeight = variableHeight ?? defaultHeight - } - - let item = CollectionViewLayoutAttributes(forCellWith: indexPath) - let y = column.frame.maxY + spacing - item.frame = CGRect(x: column.frame.minX, y: y, - width: width, height: itemHeight) - - self.items.append(item) - column.append(item: item) - } - - func finalizeColumns() { - let cBounds = columns.reduce(CGRect.null) { return $0.union($1.frame) } - self.contentFrame = self.contentFrame.union(cBounds) - self.frame = self.frame.union(self.contentFrame) - } - - private func nextColumnIndexForItem(_ indexPath: IndexPath, strategy: LayoutStrategy) -> Column { - switch strategy { - case .shortestFirst : - return columns.min(by: { (c1, c2) -> Bool in - return c1.frame.size.height < c2.frame.size.height - })! - case .leftToRight : - let colCount = self.columns.count - let index = (indexPath._item % colCount) - return self.columns[index] - case .rightToLeft: - let colCount = self.columns.count - let index = (colCount - 1) - (indexPath._item % colCount) - return self.columns[index] - } - } - } - - override public init() { - super.init() - } + public init() { } private var _lastSize = CGSize.zero - override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return _lastSize != newBounds.size } - override open func prepare() { + public func invalidate() { + + } + + open func prepare() { self.allIndexPaths.removeAll() self.sections.removeAll() + self.leadingViewAttributes = nil guard let cv = self.collectionView, cv.numberOfSections > 0 else { return @@ -303,20 +272,29 @@ open class CollectionViewColumnLayout: CollectionViewLayout { let numberOfSections = cv.numberOfSections let contentInsets = cv.contentInsets - var top: CGFloat = self.collectionView?.leadingView?.bounds.size.height ?? 0 + var top: CGFloat = 0 + + if let leadingHeight = self.delegate?.collectionViewLeadingViewHeight(cv, layout: self), leadingHeight > 0 { + let attrs = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.LeadingView, with: .zero) + attrs.frame = CGRect(x: contentInsets.left, y: top, + width: cv.frame.size.width - contentInsets.width, + height: leadingHeight).integral + self.leadingViewAttributes = attrs + top = attrs.frame.maxY + } for sectionIdx in 0.. 0 { let attributes = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.SectionHeader, with: IndexPath.for(section: sectionIdx)) attributes.frame = insetSupplementaryViews - ? CGRect(x: sectionInsets.left, y: top, width: contentWidth, height: heightHeader).integral - : CGRect(x: contentInsets.left, y: top, width: cv.frame.size.width - contentInsets.width, height: heightHeader).integral + ? CGRect(x: sectionInsets.left, y: top, width: contentWidth, height: heightHeader).integral + : CGRect(x: contentInsets.left, y: top, width: cv.frame.size.width - contentInsets.width, height: heightHeader).integral section.header = attributes top = attributes.frame.maxY } top += sectionInsets.top - + section.prepareColumns(colCount, spacing: columnSpacing, in: CGRect(x: sectionInsets.left, y: top, width: contentWidth, height: 0)) // 3. Section items - + let itemCount = cv.numberOfItems(in: sectionIdx) // Item will be put into shortest column. @@ -350,15 +328,15 @@ open class CollectionViewColumnLayout: CollectionViewLayout { let indexPath = IndexPath.for(item: idx, section: sectionIdx) allIndexPaths.append(indexPath) - let ratio = self.delegate?.collectionView?(cv, layout: self, aspectRatioForItemAt: indexPath) - let height = self.delegate?.collectionView?(cv, layout: self, heightForItemAt: indexPath) + let ratio = self.delegate?.collectionView(cv, layout: self, aspectRatioForItemAt: indexPath) + let height = self.delegate?.collectionView(cv, layout: self, heightForItemAt: indexPath) section.addItem(for: indexPath, aspectRatio: ratio, variableHeight: height, defaultHeight: self.itemHeight, spacing: itemSpacing, strategy: self.layoutStrategy) - + } // 4. Section footer @@ -366,13 +344,13 @@ open class CollectionViewColumnLayout: CollectionViewLayout { section.finalizeColumns() top = section.frame.maxY - let footerHeight = self.delegate?.collectionView?(cv, layout: self, heightForFooterInSection: sectionIdx) ?? self.footerHeight + let footerHeight = self.delegate?.collectionView(cv, layout: self, heightForFooterInSection: sectionIdx) ?? self.footerHeight if footerHeight > 0 { let attributes = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.SectionFooter, with: IndexPath.for(item: 0, section: sectionIdx)) attributes.frame = insetSupplementaryViews ? - CGRect(x: sectionInsets.left, y: top, width: cv.contentVisibleRect.size.width - sectionInsets.width, height: footerHeight) - : CGRect(x: 0, y: top, width: cv.contentVisibleRect.size.width, height: footerHeight) + CGRect(x: sectionInsets.left, y: top, width: cv.contentVisibleRect.size.width - sectionInsets.width, height: footerHeight) + : CGRect(x: 0, y: top, width: cv.contentVisibleRect.size.width, height: footerHeight) section.footer = attributes section.frame.size.height += attributes.frame.size.height @@ -384,7 +362,7 @@ open class CollectionViewColumnLayout: CollectionViewLayout { } } - override open var collectionViewContentSize: CGSize { + open var collectionViewContentSize: CGSize { guard let cv = collectionView else { return CGSize.zero } var contentSize = cv.contentVisibleRect.size @@ -395,18 +373,18 @@ open class CollectionViewColumnLayout: CollectionViewLayout { return contentSize } - open override func rectForSection(_ section: Int) -> CGRect { + open func rectForSection(_ section: Int) -> CGRect { return self.sections[section].frame } - open override func contentRectForSection(_ section: Int) -> CGRect { + open func contentRectForSection(_ section: Int) -> CGRect { return self.sections[section].contentFrame } - open override func indexPathsForItems(in rect: CGRect) -> [IndexPath] { + open func indexPathsForItems(in rect: CGRect) -> [IndexPath] { return itemAttributes(in: rect) { return $0.indexPath } } - open override func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { + open func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { return itemAttributes(in: rect) { return $0.copy() } } @@ -441,14 +419,17 @@ open class CollectionViewColumnLayout: CollectionViewLayout { return results } - open override func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + open func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { return self.sections.object(at: indexPath._section)?.items.object(at: indexPath._item)?.copy() } - open override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { let section = self.sections[indexPath._section] - if elementKind == CollectionViewLayoutElementKind.SectionHeader { + switch elementKind { + case CollectionViewLayoutElementKind.LeadingView: + return leadingViewAttributes + case CollectionViewLayoutElementKind.SectionHeader: guard let attrs = section.header?.copy() else { return nil } if pinHeadersToTop, let cv = self.collectionView { let contentOffset = cv.contentOffset @@ -463,19 +444,19 @@ open class CollectionViewColumnLayout: CollectionViewLayout { attrs.floating = indexPath._section == 0 || attrs.frame.origin.y > frame.origin.y } return attrs - } else if elementKind == CollectionViewLayoutElementKind.SectionFooter { + case CollectionViewLayoutElementKind.SectionFooter: return section.footer?.copy() + default: return nil } - return nil } - open override func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { + open func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { guard var frame = self.layoutAttributesForItem(at: indexPath)?.frame else { return nil } let inset = (self.collectionView?.contentInsets.top ?? 0) if self.pinHeadersToTop, - let attrs = self.layoutAttributesForSupplementaryView(ofKind: CollectionViewLayoutElementKind.SectionHeader, - at: IndexPath.for(item: 0, section: indexPath._section)) { + let attrs = self.layoutAttributesForSupplementaryView(ofKind: CollectionViewLayoutElementKind.SectionHeader, + at: IndexPath.for(item: 0, section: indexPath._section)) { let y = (frame.origin.y - attrs.frame.size.height) // + inset let height = frame.size.height + attrs.frame.size.height @@ -487,7 +468,7 @@ open class CollectionViewColumnLayout: CollectionViewLayout { return frame } - open override func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { + open func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { guard let collectionView = self.collectionView else { fatalError() } var index = currentIndexPath._item @@ -501,7 +482,7 @@ open class CollectionViewColumnLayout: CollectionViewLayout { switch direction { case .up: guard let cAttrs = collectionView.layoutAttributesForItem(at: currentIndexPath), - let columns = self.sections.object(at: section)?.columns else { return nil } + let columns = self.sections.object(at: section)?.columns else { return nil } let cFlat = CGRect(x: cAttrs.frame.origin.x, y: 0, width: cAttrs.frame.size.width, height: 50) @@ -537,7 +518,7 @@ open class CollectionViewColumnLayout: CollectionViewLayout { case .down: guard let cAttrs = collectionView.layoutAttributesForItem(at: currentIndexPath), - let columns = self.sections.object(at: section)?.columns else { return nil } + let columns = self.sections.object(at: section)?.columns else { return nil } let cFlat = CGRect(x: cAttrs.frame.origin.x, y: 0, width: cAttrs.frame.size.width, height: 50) @@ -582,7 +563,7 @@ open class CollectionViewColumnLayout: CollectionViewLayout { index = collectionView.numberOfItems(in: currentIndexPath._section - 1) - 1 } return IndexPath.for(item: index, section: section) - case .right : + case .right: if section == numberOfSections - 1 && index == numberOfItemsInSection - 1 { return currentIndexPath } @@ -596,3 +577,111 @@ open class CollectionViewColumnLayout: CollectionViewLayout { } } } + +extension CollectionViewColumnLayout { + @available(*, deprecated, renamed: "LayoutStrategy") + public typealias ItemRenderDirection = LayoutStrategy + + @available(*, deprecated, renamed: "layoutStrategy") + open var itemRenderDirection: LayoutStrategy { + get { return layoutStrategy } + set { self.layoutStrategy = newValue } + } +} + +extension CollectionViewColumnLayout { + private class Column { + var frame: CGRect + var height: CGFloat { return items.last?.frame.maxY ?? 0 } + var items: [CollectionViewLayoutAttributes] = [] + init(frame: CGRect) { + self.frame = frame + } + func append(item: CollectionViewLayoutAttributes) { + self.items.append(item) + self.frame = self.frame.union(item.frame) + } + } + + private class SectionAttributes: CustomStringConvertible { + var frame = CGRect.zero + var contentFrame = CGRect.zero + let insets: NSEdgeInsets + var header: CollectionViewLayoutAttributes? + var footer: CollectionViewLayoutAttributes? + + var columns = [Column]() + var items = [CollectionViewLayoutAttributes]() + + init(frame: CGRect, insets: NSEdgeInsets) { + self.frame = frame + self.insets = insets + } + + func prepareColumns(_ count: Int, spacing: CGFloat, in rect: CGRect) { + self.contentFrame = rect + let y = rect.minY + let gapCount = CGFloat(count-1) + let width = round((rect.width - (gapCount * spacing)) / CGFloat(count)) + var x = rect.minX - spacing - width + + self.columns = (0.. Column in + x += (spacing + width) + return Column(frame: CGRect(x: x, y: y, width: width, height: 0)) + }) + } + + var description: String { + return "Section Attributes : \(frame) content: \(contentFrame) Items: \(items.count)" + } + + func addItem(for indexPath: IndexPath, aspectRatio ratio: CGSize?, variableHeight: CGFloat?, defaultHeight: CGFloat, spacing: CGFloat, strategy: LayoutStrategy) { + + let column = self.nextColumnIndexForItem(indexPath, strategy: strategy) + let width = column.frame.size.width + + var itemHeight: CGFloat = 0 + if let ratio = ratio, ratio.width != 0 && ratio.height != 0 { + let h = ratio.height * (width/ratio.width) + itemHeight = floor(h) + + if let addHeight = variableHeight { + itemHeight += addHeight + } + } else { + itemHeight = variableHeight ?? defaultHeight + } + + let item = CollectionViewLayoutAttributes(forCellWith: indexPath) + let y = column.frame.maxY + spacing + item.frame = CGRect(x: column.frame.minX, y: y, + width: width, height: itemHeight) + + self.items.append(item) + column.append(item: item) + } + + func finalizeColumns() { + let cBounds = columns.reduce(CGRect.null) { return $0.union($1.frame) } + self.contentFrame = self.contentFrame.union(cBounds) + self.frame = self.frame.union(self.contentFrame) + } + + private func nextColumnIndexForItem(_ indexPath: IndexPath, strategy: LayoutStrategy) -> Column { + switch strategy { + case .shortestFirst : + return columns.min(by: { (c1, c2) -> Bool in + return c1.frame.size.height < c2.frame.size.height + })! + case .leftToRight : + let colCount = self.columns.count + let index = (indexPath._item % colCount) + return self.columns[index] + case .rightToLeft: + let colCount = self.columns.count + let index = (colCount - 1) - (indexPath._item % colCount) + return self.columns[index] + } + } + } +} diff --git a/CollectionView/Layouts/CollectionViewFlowLayout.swift b/CollectionView/Layouts/CollectionViewFlowLayout.swift index 1362cc7..668901b 100644 --- a/CollectionView/Layouts/CollectionViewFlowLayout.swift +++ b/CollectionView/Layouts/CollectionViewFlowLayout.swift @@ -184,8 +184,16 @@ extension CollectionViewDelegateFlowLayout { */ open class CollectionViewFlowLayout: CollectionViewLayout { + public var collectionView: CollectionView? + + public var scrollDirection: CollectionViewScrollDirection { return .vertical} + + public var allIndexPaths = OrderedSet() + // MARK: - Options /*-------------------------------------------------------------------------------*/ + /// If supporting views should be pinned to the top of the view + open var pinHeadersToTop: Bool = true /// Spacing between flow elements public var interitemSpacing: CGFloat = 8 @@ -216,6 +224,12 @@ open class CollectionViewFlowLayout: CollectionViewLayout { /// Only used during layout preparation to reference the width of the previously inserted row private(set) public var widthOfLastRow: CGFloat? + private var delegate: CollectionViewDelegateFlowLayout? { + return self.collectionView?.delegate as? CollectionViewDelegateFlowLayout + } + + private var sectionAttributes = [SectionAttributes]() + /// Row transforms can be applied to flow elements that fall within the same row /// /// - none: No transform @@ -363,21 +377,21 @@ open class CollectionViewFlowLayout: CollectionViewLayout { } } - private var delegate: CollectionViewDelegateFlowLayout? { - return self.collectionView?.delegate as? CollectionViewDelegateFlowLayout - } - - private var sectionAttributes = [SectionAttributes]() + public init() { } // MARK: - Layout Overrides /*-------------------------------------------------------------------------------*/ private var _lastSize = CGSize.zero - open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return _lastSize != newBounds.size } - override open func prepare() { + public func invalidate() { + + } + + open func prepare() { self.allIndexPaths.removeAll() self.sectionAttributes.removeAll() @@ -542,11 +556,11 @@ open class CollectionViewFlowLayout: CollectionViewLayout { // MARK: - Query Content /*-------------------------------------------------------------------------------*/ - override open func indexPathsForItems(in rect: CGRect) -> [IndexPath] { + open func indexPathsForItems(in rect: CGRect) -> [IndexPath] { return itemAttributes(in: rect) { return $0.indexPath } } - override open func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { + open func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { return itemAttributes(in: rect) { return $0.copy() } } @@ -582,11 +596,11 @@ open class CollectionViewFlowLayout: CollectionViewLayout { return results } - override open func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + open func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { return self.sectionAttributes.object(at: indexPath._section)?.items.object(at: indexPath._item)?.copy() } - override open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { if elementKind == CollectionViewLayoutElementKind.SectionHeader { let attrs = self.sectionAttributes[indexPath._section].header?.copy() @@ -617,15 +631,15 @@ open class CollectionViewFlowLayout: CollectionViewLayout { return nil } - open override func rectForSection(_ section: Int) -> CGRect { + open func rectForSection(_ section: Int) -> CGRect { return sectionAttributes[section].frame } - open override func contentRectForSection(_ section: Int) -> CGRect { + open func contentRectForSection(_ section: Int) -> CGRect { return sectionAttributes[section].contentFrame } - override open var collectionViewContentSize: CGSize { + open var collectionViewContentSize: CGSize { guard let cv = collectionView else { return CGSize.zero } let numberOfSections = cv.numberOfSections if numberOfSections == 0 { return CGSize.zero } @@ -637,7 +651,7 @@ open class CollectionViewFlowLayout: CollectionViewLayout { return contentSize } - open override func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { + open func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { guard var frame = self.layoutAttributesForItem(at: indexPath)?.frame else { return nil } let section = self.sectionAttributes[indexPath._section] @@ -658,7 +672,7 @@ open class CollectionViewFlowLayout: CollectionViewLayout { return frame } - open override func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { + open func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { guard let collectionView = self.collectionView else { fatalError() } // var index = currentIndexPath._item diff --git a/CollectionView/Layouts/CollectionViewHorizontalLayout.swift b/CollectionView/Layouts/CollectionViewHorizontalLayout.swift index 64ad8fa..6151517 100644 --- a/CollectionView/Layouts/CollectionViewHorizontalLayout.swift +++ b/CollectionView/Layouts/CollectionViewHorizontalLayout.swift @@ -9,7 +9,7 @@ import Foundation /// The delegate for CollectionViewHorizontalListLayout -@objc public protocol CollectionViewDelegateHorizontalListLayout: CollectionViewDelegate { +public protocol CollectionViewDelegateHorizontalListLayout: CollectionViewDelegate { /// Asks the delegate for the width of the item at a given index path /// @@ -18,15 +18,26 @@ import Foundation /// - Parameter indexPath: The index path for the item /// /// - Returns: The desired width of the item at indexPath - @objc optional func collectionView (_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewLayout, - widthForItemAt indexPath: IndexPath) -> CGFloat + func collectionView (_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewHorizontalListLayout, + widthForItemAt indexPath: IndexPath) -> CGFloat +} + +public extension CollectionViewDelegateHorizontalListLayout { + func collectionView (_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewHorizontalListLayout, + widthForItemAt indexPath: IndexPath) -> CGFloat { + return collectionViewLayout.itemWidth + } } /// A full height horizontal scrolling layout open class CollectionViewHorizontalListLayout: CollectionViewLayout { + public var collectionView: CollectionView? - override open var scrollDirection: CollectionViewScrollDirection { + public var allIndexPaths = OrderedSet() + + open var scrollDirection: CollectionViewScrollDirection { return CollectionViewScrollDirection.horizontal } @@ -43,7 +54,13 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { var cache = [[CGRect]]() var contentWidth: CGFloat = 0 - open override func prepare() { + public init() { } + + public func invalidate() { + + } + + open func prepare() { cache = [] self.allIndexPaths.removeAll() @@ -63,7 +80,7 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { var height = cv.bounds.height height -= sectionInsets.height - let width = self.delegate?.collectionView?(cv, layout: self, widthForItemAt: ip) ?? itemWidth + let width = self.delegate?.collectionView(cv, layout: self, widthForItemAt: ip) ?? itemWidth var x = xPos if !items.isEmpty { @@ -91,7 +108,7 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { } var _size = CGSize.zero - open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { if !newBounds.size.equalTo(_size) { self._size = newBounds.size return true @@ -99,7 +116,7 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { return false } - open override var collectionViewContentSize: CGSize { + open var collectionViewContentSize: CGSize { let numberOfSections = self.collectionView!.numberOfSections if numberOfSections == 0 { return CGSize.zero @@ -109,21 +126,21 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { return contentSize } - open override func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { + open func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { return layoutAttributesForItem(at: indexPath)?.frame } - open override func rectForSection(_ section: Int) -> CGRect { + open func rectForSection(_ section: Int) -> CGRect { guard let sectionItems = self.cache.object(at: section), !sectionItems.isEmpty else { return CGRect.zero } return sectionItems.reduce(CGRect.null) { partialResult, rect in return partialResult.union(rect) } } - open override func contentRectForSection(_ section: Int) -> CGRect { + open func contentRectForSection(_ section: Int) -> CGRect { return rectForSection(section) } - open override func indexPathsForItems(in rect: CGRect) -> [IndexPath] { + open func indexPathsForItems(in rect: CGRect) -> [IndexPath] { var ips = [IndexPath]() for (sectionIdx, section) in cache.enumerated() { @@ -137,7 +154,7 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { return ips } - open override func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + open func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { let attrs = CollectionViewLayoutAttributes(forCellWith: indexPath) attrs.alpha = 1 attrs.zIndex = 1000 @@ -146,11 +163,19 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { attrs.frame = frame return attrs } + + public func layoutAttributesForSupplementaryView(ofKind kind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + return nil + } + + public func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { + return nil + } } open class HorizontalCollectionView: CollectionView { - override public init() { + public override init() { super.init() self.hasVerticalScroller = false self.hasHorizontalScroller = false diff --git a/CollectionView/Layouts/CollectionViewLayout.swift b/CollectionView/Layouts/CollectionViewLayout.swift index 4b088fb..c222ea0 100644 --- a/CollectionView/Layouts/CollectionViewLayout.swift +++ b/CollectionView/Layouts/CollectionViewLayout.swift @@ -9,77 +9,56 @@ import Foundation /// The CollectionViewLayout class is an abstract base class that you subclass and use to generate layout information for a collection view. The job of a layout object is to determine the placement of cells, supplementary views inside the collection view’s bounds and to report that information to the collection view when asked. The collection view then applies the provided layout information to the corresponding views so that they can be presented onscreen. -open class CollectionViewLayout: NSObject { +public protocol CollectionViewLayout: AnyObject { /// The collection view this layout has been applied to /// /// Set when the layout is given to a collection view's collectionViewLayout property - open internal(set) weak var collectionView: CollectionView? { didSet { invalidate() }} + var collectionView: CollectionView? { get set } /// The direction that the collection view should scroll - open var scrollDirection: CollectionViewScrollDirection { return .vertical } - - private func overrideWarning(_ function: String = #function) { - Swift.print("WARNING: CollectionViewLayout \(function) should be overridden in a subclass. Missing in \(self). Make sure super is not called too.") - } + var scrollDirection: CollectionViewScrollDirection { get } /// The size that encapsulates all views within the collection view /// /// Note: Subclasses must override this method and use it to return the width and height of the collection view’s content. These values represent the width and height of all the content, not just the content that is currently visible. The collection view uses this information to configure its own content size to facilitate scrolling. - open var collectionViewContentSize: CGSize { - overrideWarning() - return CGSize.zero - } + var collectionViewContentSize: CGSize { get } +// overrideWarning() +// return CGSize.zero +// } - /// If supporting views should be pinned to the top of the view - open var pinHeadersToTop: Bool = true + /// All the index paths to be displayed by the collection view + /// + /// Becuase the layout likely needs to process all items in the data, setting this during prepare() can cut out the overhead of the collection view having to do so itself. + var allIndexPaths: OrderedSet { get set } // MARK: - Layout Validation /*-------------------------------------------------------------------------------*/ /// Currently this is only called when the layout is applied to a collection view. - open func invalidate() { } + func invalidate() /// Asks the layout if it should be invalidated due to a bounds change on the collection view /// /// - Parameter newBounds: The new bounds of the collection view /// /// - Returns: If the layout should be invalidated - open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { - overrideWarning() - return true // Default to YES to force the layout to update. - } - - @available(*, unavailable, renamed: "prepare()") - open func prepareLayout() { } + func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool /// Tells the layout object to update the current layout. /// /// ## Discussion /// Layout updates occur the first time the collection view presents its content and whenever the layout is invalidated explicitly or implicitly because of a change to the view. During each layout update, the collection view calls this method first to give your layout object a chance to prepare for the upcoming layout operation. /// The default implementation of this method does nothing. Subclasses can override it and use it to set up data structures or perform any initial computations needed to perform the layout later. - open func prepare() { - overrideWarning() - } + func prepare() // MARK: - Index Paths /*-------------------------------------------------------------------------------*/ - /// All the index paths to be displayed by the collection view - /// - /// Becuase the layout likely needs to process all items in the data, setting this during prepare() can cut out the overhead of the collection view having to do so itself. - public var allIndexPaths = OrderedSet() - - open func indexPathsForItems(in rect: CGRect) -> [IndexPath] { - overrideWarning() - var indexPaths = [IndexPath]() - for ip in self.allIndexPaths { - if let attr = self.layoutAttributesForItem(at: ip), attr.frame.intersects(rect) { - indexPaths.append(attr.indexPath) - } - } - return indexPaths - } + /// Returns index paths for items in a given rect + /// - Parameter rect: The rect in which to look for elements + /// - Returns: an array of index paths + func indexPathsForItems(in rect: CGRect) -> [IndexPath] // MARK: - Layout Attributes /*-------------------------------------------------------------------------------*/ @@ -87,16 +66,7 @@ open class CollectionViewLayout: NSObject { /// Returns the layout attributes for all views in a given rect /// /// - Parameter rect: The rect in which to look for elements - open func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { - overrideWarning() - var attrs = [CollectionViewLayoutAttributes]() - for ip in self.allIndexPaths { - if let attr = self.layoutAttributesForItem(at: ip), attr.frame.intersects(rect) { - attrs.append(attr) - } - } - return attrs - } + func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] /// Returns the layout attributes for an item at the given index path /// @@ -104,13 +74,7 @@ open class CollectionViewLayout: NSObject { /// /// # Important /// This must be overridden by subclasses - open func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { - overrideWarning() - return nil - } - - @available(*, unavailable, renamed: "layoutAttributesForSupplementaryView(ofKind:at:)") - open func layoutAttributesForSupplementaryView(ofKind elementKind: String, atIndexPath indexPath: IndexPath) -> CollectionViewLayoutAttributes? { return nil } + func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? /// Returns the layout attributes for the supplementary view of the given kind and the given index path /// @@ -119,10 +83,7 @@ open class CollectionViewLayout: NSObject { /// /// # Important /// This must be override by a subclass - open func layoutAttributesForSupplementaryView(ofKind kind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { - overrideWarning() - return nil - } + func layoutAttributesForSupplementaryView(ofKind kind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? // MARK: - Section Frames /*-------------------------------------------------------------------------------*/ @@ -132,7 +93,64 @@ open class CollectionViewLayout: NSObject { /// - Parameter section: The section to get the frame for /// /// - Returns: The rect containing all the views - open func rectForSection(_ section: Int) -> CGRect { + func rectForSection(_ section: Int) -> CGRect + + /// Returns the rect that encapsulates just the items of a section + /// + /// - Parameter section: The section to get the content frame for + /// + /// - Returns: The rect containing all the items + func contentRectForSection(_ section: Int) -> CGRect + + // MARK: - Scroll Frames + /*-------------------------------------------------------------------------------*/ + + /// Provides he layout a chance to adjust the frame to which the collection view should scroll to show an item + /// + /// - Parameter indexPath: The item to scroll to + /// - Parameter atPosition: The position at which to scroll the item to + /// + /// The default implementation returns the value from layoutAttributesForItem(at:) + func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? + + // MARK: - Item Direction + /*-------------------------------------------------------------------------------*/ + + /// Returns the index path for the next item in a given direction + /// + /// - Parameter direction: The direction in which to look for the next items (up, down, left, right) + /// - Parameter currentIndexPath: The current index path to seek from + func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? +} + +extension CollectionViewLayout { + private func overrideWarning(_ function: String = #function) { + Swift.print("WARNING: CollectionViewLayout \(function) should be overridden in a subclass. Missing in \(self). Make sure super is not called too.") + } + + public func indexPathsForItems(in rect: CGRect) -> [IndexPath] { + overrideWarning() + var indexPaths = [IndexPath]() + for ip in self.allIndexPaths { + if let attr = self.layoutAttributesForItem(at: ip), attr.frame.intersects(rect) { + indexPaths.append(attr.indexPath) + } + } + return indexPaths + } + + public func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { + overrideWarning() + var attrs = [CollectionViewLayoutAttributes]() + for ip in self.allIndexPaths { + if let attr = self.layoutAttributesForItem(at: ip), attr.frame.intersects(rect) { + attrs.append(attr) + } + } + return attrs + } + + public func rectForSection(_ section: Int) -> CGRect { overrideWarning() var rect = self.contentRectForSection(section) guard let cv = self.collectionView else { return rect } @@ -144,12 +162,7 @@ open class CollectionViewLayout: NSObject { return rect } - /// Returns the rect that encapsulates just the items of a section - /// - /// - Parameter section: The section to get the content frame for - /// - /// - Returns: The rect containing all the items - open func contentRectForSection(_ section: Int) -> CGRect { + public func contentRectForSection(_ section: Int) -> CGRect { overrideWarning() var rect = CGRect.null guard let cv = self.collectionView else { return rect } @@ -164,25 +177,15 @@ open class CollectionViewLayout: NSObject { return rect } - // MARK: - Scroll Frames - /*-------------------------------------------------------------------------------*/ - - /// Provides he layout a chance to adjust the frame to which the collection view should scroll to show an item - /// - /// - Parameter indexPath: The item to scroll to - /// - Parameter atPosition: The position at which to scroll the item to - /// - /// The default implementation returns the value from layoutAttributesForItem(at:) - open func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { + public func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { return self.layoutAttributesForItem(at: indexPath)?.frame } - - // MARK: - Item Direction - /*-------------------------------------------------------------------------------*/ +} - /// Returns the index path for the next item in a given direction - /// - /// - Parameter direction: The direction in which to look for the next items (up, down, left, right) - /// - Parameter currentIndexPath: The current index path to seek from - open func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { return currentIndexPath } +extension CollectionViewLayout { + @available(*, unavailable, renamed: "layoutAttributesForSupplementaryView(ofKind:at:)") + public func layoutAttributesForSupplementaryView(ofKind elementKind: String, atIndexPath indexPath: IndexPath) -> CollectionViewLayoutAttributes? { return nil } + + @available(*, unavailable, renamed: "prepare()") + func prepareLayout() { } } diff --git a/CollectionView/Layouts/CollectionViewListLayout.swift b/CollectionView/Layouts/CollectionViewListLayout.swift index eac3288..f35025a 100644 --- a/CollectionView/Layouts/CollectionViewListLayout.swift +++ b/CollectionView/Layouts/CollectionViewListLayout.swift @@ -21,7 +21,7 @@ import Foundation /// /// - Returns: The height for the item @objc optional func collectionView(_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewLayout, + layout collectionViewLayout: CollectionViewListLayout, heightForItemAt indexPath: IndexPath) -> CGFloat /// Asks the delegate for the height of the header in a given section @@ -32,7 +32,7 @@ import Foundation /// /// - Returns: The desired height of section header or 0 for no header @objc optional func collectionView(_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewLayout, + layout collectionViewLayout: CollectionViewListLayout, heightForHeaderInSection section: Int) -> CGFloat /// Asks the delegate for the height of the footer in a given section. @@ -43,7 +43,7 @@ import Foundation /// /// - Returns: The desired height of the section footer or 0 for no footer @objc optional func collectionView(_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewLayout, + layout collectionViewLayout: CollectionViewListLayout, heightForFooterInSection section: Int) -> CGFloat // MARK: - Spacing & Insets @@ -57,7 +57,7 @@ import Foundation /// /// - Returns: The desired item spacing to be applied between items in the given section @objc optional func collectionView(_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewLayout, + layout collectionViewLayout: CollectionViewListLayout, interitemSpacingForItemsInSection section: Int) -> CGFloat /// Asks the delegate for insets to use when laying out items in a given section @@ -68,15 +68,23 @@ import Foundation /// /// - Returns: The edge insets for the section @objc optional func collectionView(_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewLayout, + layout collectionViewLayout: CollectionViewListLayout, insetForSectionAt section: Int) -> NSEdgeInsets } /// A list layout that makes CollectionView a perfect alternative to NSTableView -public final class CollectionViewListLayout: CollectionViewLayout { +public final class CollectionViewListLayout: NSObject, CollectionViewLayout { + + public var collectionView: CollectionView? + + public var scrollDirection = CollectionViewScrollDirection.vertical + + public var allIndexPaths = OrderedSet() // MARK: - Default layout values + /// If supporting views should be pinned to the top of the view + public var pinHeadersToTop: Bool = true /// The vertical spacing between items in the same column public final var interitemSpacing: CGFloat = 0 { didSet { invalidate() }} @@ -123,19 +131,19 @@ public final class CollectionViewListLayout: CollectionViewLayout { } } - override public init() { - super.init() - } - private var _cvWidth: CGFloat = 0 - override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { defer { self._cvWidth = newBounds.size.width } return _cvWidth != newBounds.size.width } fileprivate var numSections: Int { return self.collectionView?.numberOfSections ?? 0 } - override public func prepare() { + public func invalidate() { + + } + + public func prepare() { self.allIndexPaths.removeAll() self.sections.removeAll() @@ -225,7 +233,7 @@ public final class CollectionViewListLayout: CollectionViewLayout { } } - override public var collectionViewContentSize: CGSize { + public var collectionViewContentSize: CGSize { guard let cv = collectionView else { return CGSize.zero } var size = cv.contentVisibleRect.size @@ -236,19 +244,19 @@ public final class CollectionViewListLayout: CollectionViewLayout { return size } - public override func rectForSection(_ section: Int) -> CGRect { + public func rectForSection(_ section: Int) -> CGRect { return sections[section].frame } - public override func contentRectForSection(_ section: Int) -> CGRect { + public func contentRectForSection(_ section: Int) -> CGRect { return sections[section].contentFrame } - public override func indexPathsForItems(in rect: CGRect) -> [IndexPath] { + public func indexPathsForItems(in rect: CGRect) -> [IndexPath] { return itemAttributes(in: rect) { return $0.indexPath } } - public override func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { + public func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { return itemAttributes(in: rect) { return $0.copy() } } @@ -279,11 +287,11 @@ public final class CollectionViewListLayout: CollectionViewLayout { return results } - public override func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + public func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { return sections.object(at: indexPath._section)?.items.object(at: indexPath._item)?.copy() } - public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { let section = self.sections[indexPath._section] if elementKind == CollectionViewLayoutElementKind.SectionHeader { @@ -307,7 +315,7 @@ public final class CollectionViewListLayout: CollectionViewLayout { return nil } - public override func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { + public func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { guard var frame = self.layoutAttributesForItem(at: indexPath)?.frame else { return nil } if self.pinHeadersToTop, let attrs = self.layoutAttributesForSupplementaryView(ofKind: CollectionViewLayoutElementKind.SectionHeader, at: IndexPath.for(item: 0, section: indexPath._section)) { @@ -319,7 +327,7 @@ public final class CollectionViewListLayout: CollectionViewLayout { return frame } - public override func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { + public func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { guard let collectionView = self.collectionView else { fatalError() } guard collectionView.rectForItem(at: currentIndexPath) != nil else { return nil } diff --git a/CollectionView/Preview/CollectionViewPreviewLayout.swift b/CollectionView/Preview/CollectionViewPreviewLayout.swift index d8f3322..3e3e94d 100644 --- a/CollectionView/Preview/CollectionViewPreviewLayout.swift +++ b/CollectionView/Preview/CollectionViewPreviewLayout.swift @@ -12,15 +12,18 @@ protocol CollectionViewDelegatePreviewLayout: AnyObject { func previewLayout(_ layout: CollectionViewPreviewLayout, canPreviewItemAt indexPath: IndexPath) -> Bool } -public final class CollectionViewPreviewLayout: CollectionViewLayout { +public final class CollectionViewPreviewLayout: NSObject, CollectionViewLayout { + public var collectionView: CollectionView? + + public var allIndexPaths: OrderedSet = [] + + public var scrollDirection: CollectionViewScrollDirection { return .horizontal } // MARK: - Default layout values /// The vertical spacing between items in the same column public var interItemSpacing: CGFloat = 8 { didSet { invalidate() }} - public override var scrollDirection: CollectionViewScrollDirection { return .horizontal } - private var numSections: Int { return self.collectionView?.numberOfSections ?? 0 } private var sections = [Section]() @@ -31,7 +34,7 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { var usableIndexPaths = OrderedSet() - public override func invalidate() { + public func invalidate() { _cvSize = collectionView?.bounds.size ?? CGSize.zero } @@ -40,11 +43,8 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { } var contentWidth: CGFloat = 0 - override public init() { - super.init() - } - override public func prepare() { + public func prepare() { self.allIndexPaths.removeAll() self.sections.removeAll() @@ -97,7 +97,7 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { contentWidth = left } - public override var collectionViewContentSize: CGSize { + public var collectionViewContentSize: CGSize { guard let cv = collectionView else { return CGSize.zero } let numberOfSections = self.numSections if numberOfSections == 0 { return CGSize.zero } @@ -108,11 +108,11 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { return size } - public override func rectForSection(_ section: Int) -> CGRect { + public func rectForSection(_ section: Int) -> CGRect { return sections[section].frame } - public override func indexPathsForItems(in rect: CGRect) -> [IndexPath] { + public func indexPathsForItems(in rect: CGRect) -> [IndexPath] { guard !rect.isEmpty && !sections.isEmpty else { return [] } @@ -136,7 +136,7 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { return indexPaths } - public override func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { + public func layoutAttributesForItems(in rect: CGRect) -> [CollectionViewLayoutAttributes] { guard !rect.isEmpty && !sections.isEmpty else { return [] } @@ -161,13 +161,13 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { return result } - public override func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + public func layoutAttributesForItem(at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { let a = self.sections.object(at: indexPath._section)?.itemAttributes.object(at: indexPath._item) return a! } fileprivate var _cvSize = CGSize.zero - public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { if !newBounds.size.equalTo(self._cvSize) { self._cvSize = newBounds.size return true @@ -175,11 +175,11 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { return false } - public override func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { + public func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { return self.layoutAttributesForItem(at: indexPath)?.frame } - public override func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { + public func indexPathForNextItem(moving direction: CollectionViewDirection, from currentIndexPath: IndexPath) -> IndexPath? { switch direction { case .up, .left: @@ -191,4 +191,8 @@ public final class CollectionViewPreviewLayout: CollectionViewLayout { } + public func layoutAttributesForSupplementaryView(ofKind kind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + return nil + } + } From 55566bea7272341067c1b38cf62247f03a695f62 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Tue, 15 Mar 2022 12:03:42 -0700 Subject: [PATCH 02/16] version --- CollectionView.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index 1a799a3..72c3509 100644 --- a/CollectionView.xcodeproj/project.pbxproj +++ b/CollectionView.xcodeproj/project.pbxproj @@ -608,6 +608,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -636,6 +637,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 5222f72fea5b20236d92e4f7aea79dca9b44294d Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Tue, 15 Mar 2022 13:17:38 -0700 Subject: [PATCH 03/16] cleanup and fix misuse of CollectionViewLayout --- CollectionView/CollectionView.swift | 10 +--- .../Layouts/CollectionViewColumnLayout.swift | 55 ------------------- CollectionViewTests/CVColumnLayoutTests.swift | 6 +- CollectionViewTests/CVListLayoutTests.swift | 4 +- 4 files changed, 7 insertions(+), 68 deletions(-) diff --git a/CollectionView/CollectionView.swift b/CollectionView/CollectionView.swift index e361f1f..3a7972d 100644 --- a/CollectionView/CollectionView.swift +++ b/CollectionView/CollectionView.swift @@ -529,16 +529,10 @@ open class CollectionView: ScrollView, NSDraggingSource { open override func layout() { self._floatingSupplementaryView.frame = self.bounds - let leadingViewHeight = self.leadingView?.bounds.size.height ?? 0 - super.layout() if needsLayoutReload || self.collectionViewLayout.shouldInvalidateLayout(forBoundsChange: self.contentVisibleRect) { - - func performLayout() { - prepareLayout(reloadData: reloadDataOnBoundsChange) - setContentViewSize() - } - performLayout() + prepareLayout(reloadData: reloadDataOnBoundsChange) + setContentViewSize() // Don't pin when implicitly reloading if !self.needsLayoutReload, let ip = _topIP { diff --git a/CollectionView/Layouts/CollectionViewColumnLayout.swift b/CollectionView/Layouts/CollectionViewColumnLayout.swift index e7c792e..6356e54 100644 --- a/CollectionView/Layouts/CollectionViewColumnLayout.swift +++ b/CollectionView/Layouts/CollectionViewColumnLayout.swift @@ -116,61 +116,6 @@ public protocol CollectionViewDelegateColumnLayout { heightForFooterInSection section: Int) -> CGFloat } -//extension CollectionViewDelegateColumnLayout { -// -// public func collectionView(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// numberOfColumnsInSection section: Int) -> Int { -// return collectionViewLayout.columnCount -// } -// -// public func collectionView(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// insetForSectionAt section: NSInteger) -> NSEdgeInsets { -// return collectionViewLayout.sectionInset -// } -// -// public func collectionView(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// interitemSpacingForSectionAt section: Int) -> CGFloat { -// return collectionViewLayout.interitemSpacing -// } -// -// public func collectionview(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// columnSpacingForSectionAt section: Int) -> CGFloat { -// return collectionViewLayout.columnSpacing -// } -// -// public func collectionView(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// heightForItemAt indexPath: IndexPath) -> CGFloat { -// return collectionViewLayout.itemHeight -// } -// -// public func collectionView(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// aspectRatioForItemAt indexPath: IndexPath) -> CGSize { -// return .zero -// } -// -// public func collectionViewLeadingViewHeight(_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout) -> CGFloat { -// return 0 -// } -// -// public func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewColumnLayout, -// heightForHeaderInSection section: Int) -> CGFloat { -// return collectionViewLayout.headerHeight -// } -// -// public func collectionView (_ collectionView: CollectionView, -// layout collectionViewLayout: CollectionViewColumnLayout, -// heightForFooterInSection section: Int) -> CGFloat { -// return collectionViewLayout.footerHeight -// } -//} - /** This layout is column based which means you provide the number of columns and cells are placed in the appropriate one. It can be display items all the same size or as a "Pinterest" style layout. diff --git a/CollectionViewTests/CVColumnLayoutTests.swift b/CollectionViewTests/CVColumnLayoutTests.swift index 3e7ce98..ba308e8 100644 --- a/CollectionViewTests/CVColumnLayoutTests.swift +++ b/CollectionViewTests/CVColumnLayoutTests.swift @@ -200,15 +200,15 @@ fileprivate class LayoutTester: CollectionViewDataSource, CollectionViewDelegate // Layout Delegate - func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, heightForHeaderInSection section: Int) -> CGFloat { + func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewColumnLayout, heightForHeaderInSection section: Int) -> CGFloat { return self.headerHeight } - func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, heightForItemAt indexPath: IndexPath) -> CGFloat { + func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewColumnLayout, heightForItemAt indexPath: IndexPath) -> CGFloat { return self.heightProvider?(indexPath) ?? self.defaultHeight } - func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, aspectRatioForItemAt indexPath: IndexPath) -> CGSize { + func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewColumnLayout, aspectRatioForItemAt indexPath: IndexPath) -> CGSize { return self.ratioProvider?(indexPath) ?? CGSize.zero } } diff --git a/CollectionViewTests/CVListLayoutTests.swift b/CollectionViewTests/CVListLayoutTests.swift index 6f60286..57d7ee7 100644 --- a/CollectionViewTests/CVListLayoutTests.swift +++ b/CollectionViewTests/CVListLayoutTests.swift @@ -177,11 +177,11 @@ fileprivate class LayoutTester: CollectionViewDataSource, CollectionViewDelegate return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath) } - func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, heightForHeaderInSection section: Int) -> CGFloat { + func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewListLayout, heightForHeaderInSection section: Int) -> CGFloat { return self.headerHeight } - func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewLayout, heightForItemAt indexPath: IndexPath) -> CGFloat { + func collectionView(_ collectionView: CollectionView, layout collectionViewLayout: CollectionViewListLayout, heightForItemAt indexPath: IndexPath) -> CGFloat { return self.heightProvider?(indexPath) ?? self.defaultHeight } } From 9d5650f5f9b23e5a85b181cbe170d0797e39c614 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Wed, 16 Mar 2022 07:20:02 -0700 Subject: [PATCH 04/16] Extend sectino frames to include leading view --- CollectionView/Layouts/CollectionViewColumnLayout.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CollectionView/Layouts/CollectionViewColumnLayout.swift b/CollectionView/Layouts/CollectionViewColumnLayout.swift index 6356e54..63142b9 100644 --- a/CollectionView/Layouts/CollectionViewColumnLayout.swift +++ b/CollectionView/Layouts/CollectionViewColumnLayout.swift @@ -301,6 +301,12 @@ open class CollectionViewColumnLayout: CollectionViewLayout { section.frame.size.height += attributes.frame.size.height top = attributes.frame.maxY } + + if sectionIdx == 0, let leading = leadingViewAttributes { + section.frame.origin.y = leading.frame.minY + section.frame.size.height = top - leading.frame.minY + } + section.frame.size.height += sectionInsets.bottom top = section.frame.maxY sections.append(section) From c9e57420971d26701b25b24705d6ef649190c115 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Wed, 16 Mar 2022 16:47:50 -0700 Subject: [PATCH 05/16] Add leading view support to flow layout --- .../Layouts/CollectionViewColumnLayout.swift | 3 +- .../Layouts/CollectionViewFlowLayout.swift | 98 +++++++++++-------- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/CollectionView/Layouts/CollectionViewColumnLayout.swift b/CollectionView/Layouts/CollectionViewColumnLayout.swift index 63142b9..ccc40fd 100644 --- a/CollectionView/Layouts/CollectionViewColumnLayout.swift +++ b/CollectionView/Layouts/CollectionViewColumnLayout.swift @@ -303,8 +303,7 @@ open class CollectionViewColumnLayout: CollectionViewLayout { } if sectionIdx == 0, let leading = leadingViewAttributes { - section.frame.origin.y = leading.frame.minY - section.frame.size.height = top - leading.frame.minY + section.frame = section.frame.union(leading.frame) } section.frame.size.height += sectionInsets.bottom diff --git a/CollectionView/Layouts/CollectionViewFlowLayout.swift b/CollectionView/Layouts/CollectionViewFlowLayout.swift index 668901b..5ca31f2 100644 --- a/CollectionView/Layouts/CollectionViewFlowLayout.swift +++ b/CollectionView/Layouts/CollectionViewFlowLayout.swift @@ -25,6 +25,14 @@ public protocol CollectionViewDelegateFlowLayout { flowLayout: CollectionViewFlowLayout, styleForItemAt indexPath: IndexPath) -> CollectionViewFlowLayout.ItemStyle + /// Asks the delegate for the height of the leading view + /// + /// - Parameter collectionView: The collection view + /// - Parameter collectionViewLayout: The layout + /// - Returns: The desired leading view height or 0 for no view + func collectionViewLeadingViewHeight(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewFlowLayout) -> CGFloat + /// Asks the delegate for the height of the header view in a specified section /// /// Return 0 for no header view @@ -92,11 +100,14 @@ public protocol CollectionViewDelegateFlowLayout { extension CollectionViewDelegateFlowLayout { - public func collectionView(_ collectionView: CollectionView, - flowLayout: CollectionViewFlowLayout, - styleForItemAt indexPath: IndexPath) -> CollectionViewFlowLayout.ItemStyle { - return flowLayout.defaultItemStyle - } +// public func collectionView(_ collectionView: CollectionView, +// flowLayout: CollectionViewFlowLayout, +// styleForItemAt indexPath: IndexPath) -> CollectionViewFlowLayout.ItemStyle { +// return flowLayout.defaultItemStyle +// } + + public func collectionViewLeadingViewHeight(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewFlowLayout) -> CGFloat { return 0 } public func collectionView (_ collectionView: CollectionView, flowLayout collectionViewLayout: CollectionViewFlowLayout, @@ -228,6 +239,7 @@ open class CollectionViewFlowLayout: CollectionViewLayout { return self.collectionView?.delegate as? CollectionViewDelegateFlowLayout } + private var leadingViewAttributes: CollectionViewLayoutAttributes? private var sectionAttributes = [SectionAttributes]() /// Row transforms can be applied to flow elements that fall within the same row @@ -392,43 +404,52 @@ open class CollectionViewFlowLayout: CollectionViewLayout { } open func prepare() { - self.allIndexPaths.removeAll() self.sectionAttributes.removeAll() + guard let cv = self.collectionView else { return } self._lastSize = cv.frame.size - let numSections = cv.numberOfSections - guard numSections > 0 else { return } - + let numberOfSections = cv.numberOfSections + let contentInsets = cv.contentInsets var top: CGFloat = self.collectionView?.leadingView?.bounds.size.height ?? 0 - let contentInsets = cv.contentInsets + if let leadingHeight = self.delegate?.collectionViewLeadingViewHeight(cv, layout: self), leadingHeight > 0 { + let attrs = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.LeadingView, with: .zero) + attrs.frame = CGRect(x: contentInsets.left, y: top, + width: cv.frame.size.width - contentInsets.width, + height: leadingHeight).integral + self.leadingViewAttributes = attrs + top = attrs.frame.maxY + } - for sec in 0.. 0 { + let headerHeight: CGFloat = self.delegate?.collectionView(cv, flowLayout: self, + heightForHeaderInSection: sectionIdx) ?? self.defaultHeaderHeight + if headerHeight > 0 { let attrs = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.SectionHeader, - with: IndexPath.for(section: sec)) + with: IndexPath.for(section: sectionIdx)) attrs.frame = insetSupplementaryViews - ? CGRect(x: insets.left, y: top, width: contentWidth, height: heightHeader) - : CGRect(x: contentInsets.left, y: top, width: cv.frame.size.width - contentInsets.width, height: heightHeader) + ? CGRect(x: insets.left, y: top, width: contentWidth, height: headerHeight) + : CGRect(x: contentInsets.left, y: top, width: cv.frame.size.width - contentInsets.width, height: headerHeight) sectionAttrs.header = attrs sectionAttrs.frame = attrs.frame top = attrs.frame.maxY @@ -449,7 +470,7 @@ open class CollectionViewFlowLayout: CollectionViewLayout { var forceBreak: Bool = false for item in 0.. 0 { + if sectionIdx == 0, let leading = leadingViewAttributes { + sectionAttrs.frame = sectionAttrs.frame.union(leading.frame) + } + + let footerHeight: CGFloat = self.delegate?.collectionView(cv, flowLayout: self, heightForFooterInSection: sectionIdx) ?? 0 + if footerHeight > 0 { let attrs = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.SectionFooter, - with: IndexPath.for(section: sec)) + with: IndexPath.for(section: sectionIdx)) attrs.frame = insetSupplementaryViews - ? CGRect(x: insets.left + contentInsets.left, y: top, width: contentWidth, height: heightHeader) + ? CGRect(x: insets.left + contentInsets.left, y: top, width: contentWidth, height: headerHeight) : CGRect(x: contentInsets.left, y: top, - width: cv.contentVisibleRect.size.width - contentInsets.left - contentInsets.right, height: heightHeader) + width: cv.contentVisibleRect.size.width - contentInsets.left - contentInsets.right, height: headerHeight) sectionAttrs.footer = attrs sectionAttrs.frame = sectionAttrs.frame.union(attrs.frame) top = attrs.frame.maxY } sectionAttributes.append(sectionAttrs) - } } @@ -601,20 +625,15 @@ open class CollectionViewFlowLayout: CollectionViewLayout { } open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { - - if elementKind == CollectionViewLayoutElementKind.SectionHeader { + switch elementKind { + case CollectionViewLayoutElementKind.LeadingView: + return leadingViewAttributes + case CollectionViewLayoutElementKind.SectionHeader: let attrs = self.sectionAttributes[indexPath._section].header?.copy() if pinHeadersToTop, let currentAttrs = attrs, let cv = self.collectionView { - let contentOffset = cv.contentOffset let frame = currentAttrs.frame - // let lead = cv.leadingView?.bounds.size.height ?? 0 - // if indexPath._section == 0 && contentOffset.y < cv.contentInsets.top { - // currentAttrs.frame.origin.y = lead - // currentAttrs.floating = false - // } - // else { var nextHeaderOrigin = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: CGFloat.greatestFiniteMagnitude) if let nextHeader = self.sectionAttributes.object(at: indexPath._section + 1)?.header { nextHeaderOrigin = nextHeader.frame.origin @@ -622,13 +641,12 @@ open class CollectionViewFlowLayout: CollectionViewLayout { let topInset = cv.contentInsets.top currentAttrs.frame.origin.y = min(max(contentOffset.y + topInset, frame.origin.y), nextHeaderOrigin.y - frame.height) currentAttrs.floating = indexPath._section == 0 || currentAttrs.frame.origin.y > frame.origin.y - // } } return attrs - } else if elementKind == CollectionViewLayoutElementKind.SectionFooter { + case CollectionViewLayoutElementKind.SectionFooter: return self.sectionAttributes[indexPath._section].footer?.copy() + default: return nil } - return nil } open func rectForSection(_ section: Int) -> CGRect { From f9eff5e3591ac213719491344c27609986019b0f Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Fri, 25 Mar 2022 09:10:36 -0700 Subject: [PATCH 06/16] Call shouldDeselectItems before deselecting others to better enforce allowsEmptySelection --- CollectionView/CollectionView.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CollectionView/CollectionView.swift b/CollectionView/CollectionView.swift index 3a7972d..d62a4a5 100644 --- a/CollectionView/CollectionView.swift +++ b/CollectionView/CollectionView.swift @@ -1693,7 +1693,7 @@ open class CollectionView: ScrollView, NSDraggingSource { : needApproval } - if clear { + if clear && (allowsEmptySelection || !approved.isEmpty) { let deselect = self._selectedIndexPaths.removing(indexPaths) self._deselectItems(at: deselect, animated: true, notify: notify) } @@ -1771,12 +1771,12 @@ open class CollectionView: ScrollView, NSDraggingSource { // Standard selection else { - var de = self._selectedIndexPaths - de.remove(ip) - self._deselectItems(at: de, animated: true, notify: true) +// var de = self._selectedIndexPaths +// de.remove(ip) +// self._deselectItems(at: de, animated: true, notify: true) self._extendingStart = ip - self._selectItems(at: Set([ip]), animated: true, notify: true) + self._selectItems(at: Set([ip]), animated: true, clear: true, notify: true) } } From 53117a6eef035cf4ba1707191cee9b126291fb8e Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Thu, 31 Mar 2022 13:24:24 -0700 Subject: [PATCH 07/16] Add leadingView support to list layout --- .../Layouts/CollectionViewFlowLayout.swift | 2 +- .../Layouts/CollectionViewListLayout.swift | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CollectionView/Layouts/CollectionViewFlowLayout.swift b/CollectionView/Layouts/CollectionViewFlowLayout.swift index 5ca31f2..95c73bf 100644 --- a/CollectionView/Layouts/CollectionViewFlowLayout.swift +++ b/CollectionView/Layouts/CollectionViewFlowLayout.swift @@ -627,7 +627,7 @@ open class CollectionViewFlowLayout: CollectionViewLayout { open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { switch elementKind { case CollectionViewLayoutElementKind.LeadingView: - return leadingViewAttributes + return leadingViewAttributes?.copy() case CollectionViewLayoutElementKind.SectionHeader: let attrs = self.sectionAttributes[indexPath._section].header?.copy() if pinHeadersToTop, let currentAttrs = attrs, let cv = self.collectionView { diff --git a/CollectionView/Layouts/CollectionViewListLayout.swift b/CollectionView/Layouts/CollectionViewListLayout.swift index f35025a..ab5a10f 100644 --- a/CollectionView/Layouts/CollectionViewListLayout.swift +++ b/CollectionView/Layouts/CollectionViewListLayout.swift @@ -24,6 +24,14 @@ import Foundation layout collectionViewLayout: CollectionViewListLayout, heightForItemAt indexPath: IndexPath) -> CGFloat + /// Asks the delegate for the height of the leading view + /// + /// - Parameter collectionView: The collection view + /// - Parameter collectionViewLayout: The layout + /// - Returns: The desired leading view height or 0 for no view + @objc optional func collectionViewLeadingViewHeight(_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewListLayout) -> CGFloat + /// Asks the delegate for the height of the header in a given section /// /// - Parameter collectionView: The asking collection view @@ -113,6 +121,7 @@ public final class CollectionViewListLayout: NSObject, CollectionViewLayout { /// smaller than the size of the collection view itself public var hugContents: Bool = false + private var leadingViewAttributes: CollectionViewLayoutAttributes? private var sections: [SectionAttributes] = [] private struct SectionAttributes: CustomStringConvertible { @@ -156,6 +165,15 @@ public final class CollectionViewListLayout: NSObject, CollectionViewLayout { var top: CGFloat = self.collectionView?.leadingView?.bounds.size.height ?? 0 let contentInsets = cv.contentInsets + if let leadingHeight = self.delegate?.collectionViewLeadingViewHeight?(cv, layout: self), leadingHeight > 0 { + let attrs = CollectionViewLayoutAttributes(forSupplementaryViewOfKind: CollectionViewLayoutElementKind.LeadingView, with: .zero) + attrs.frame = CGRect(x: contentInsets.left, y: top, + width: cv.frame.size.width - contentInsets.width, + height: leadingHeight).integral + self.leadingViewAttributes = attrs + top = attrs.frame.maxY + } + for sectionIdx in 0.. 0 { @@ -294,7 +316,10 @@ public final class CollectionViewListLayout: NSObject, CollectionViewLayout { public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { let section = self.sections[indexPath._section] - if elementKind == CollectionViewLayoutElementKind.SectionHeader { + switch elementKind { + case CollectionViewLayoutElementKind.LeadingView: + return leadingViewAttributes?.copy() + case CollectionViewLayoutElementKind.SectionHeader: guard let attrs = section.header?.copy() else { return nil } if pinHeadersToTop, let cv = self.collectionView { let contentOffset = cv.contentOffset @@ -309,10 +334,10 @@ public final class CollectionViewListLayout: NSObject, CollectionViewLayout { attrs.floating = attrs.frame.origin.y > frame.origin.y } return attrs - } else if elementKind == CollectionViewLayoutElementKind.SectionFooter { + case CollectionViewLayoutElementKind.SectionFooter: return section.footer?.copy() + default: return nil } - return nil } public func scrollRectForItem(at indexPath: IndexPath, atPosition: CollectionViewScrollPosition) -> CGRect? { From 8c12b17de0b5c2d91652625c94c1b674fde15219 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Thu, 31 Mar 2022 13:30:19 -0700 Subject: [PATCH 08/16] Format --- CollectionView/Layouts/CollectionViewListLayout.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CollectionView/Layouts/CollectionViewListLayout.swift b/CollectionView/Layouts/CollectionViewListLayout.swift index ab5a10f..975658c 100644 --- a/CollectionView/Layouts/CollectionViewListLayout.swift +++ b/CollectionView/Layouts/CollectionViewListLayout.swift @@ -30,7 +30,7 @@ import Foundation /// - Parameter collectionViewLayout: The layout /// - Returns: The desired leading view height or 0 for no view @objc optional func collectionViewLeadingViewHeight(_ collectionView: CollectionView, - layout collectionViewLayout: CollectionViewListLayout) -> CGFloat + layout collectionViewLayout: CollectionViewListLayout) -> CGFloat /// Asks the delegate for the height of the header in a given section /// From d1fa5b5372f02643d404951cff062102ec353e9a Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Mon, 11 Apr 2022 14:45:00 -0700 Subject: [PATCH 09/16] Add safegaurd to avoid crash --- CollectionView/CollectionView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CollectionView/CollectionView.swift b/CollectionView/CollectionView.swift index d62a4a5..3809f82 100644 --- a/CollectionView/CollectionView.swift +++ b/CollectionView/CollectionView.swift @@ -1078,7 +1078,10 @@ open class CollectionView: ScrollView, NSDraggingSource { } mutating func index(of previousIndex: Int) -> Int? { - return final[previousIndex] + if previousIndex < final.count { + return final[previousIndex] + } + return nil } } From ad9653bf77b8c3e3f409657d32c5b559471a9d22 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Fri, 2 Dec 2022 08:45:00 -0800 Subject: [PATCH 10/16] Recommended settings --- CollectionView.xcodeproj/project.pbxproj | 49 ++++++++++++++----- .../xcschemes/CollectionView.xcscheme | 2 +- .../xcschemes/CollectionViewTests.xcscheme | 2 +- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index 72c3509..3973d4b 100644 --- a/CollectionView.xcodeproj/project.pbxproj +++ b/CollectionView.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -333,7 +333,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1410; ORGANIZATIONNAME = "Noun Project"; TargetAttributes = { DE9071E81CAC7FD800AD0E37 = { @@ -387,6 +387,7 @@ /* Begin PBXShellScriptBuildPhase section */ 1CB6A657211D6BAC00907CEF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -510,6 +511,7 @@ CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -568,6 +570,7 @@ CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -597,6 +600,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -606,8 +610,12 @@ GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = CollectionView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -626,6 +634,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -635,14 +644,19 @@ GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = CollectionView/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = Release; @@ -657,11 +671,16 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = CollectionViewTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.13; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -680,14 +699,20 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_OPTIMIZATION_LEVEL = s; INFOPLIST_FILE = CollectionViewTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.13; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 4.2; }; name = Release; diff --git a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme index 042c435..f3a2f18 100644 --- a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme +++ b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 19 Jan 2023 14:54:48 -0800 Subject: [PATCH 11/16] Update swiftlint --- CollectionView.xcodeproj/project.pbxproj | 6 ++++-- .../xcschemes/CollectionView.xcscheme | 2 +- .../xcschemes/CollectionViewTests.xcscheme | 2 +- .../EditDistance/EditDistance.swift | 4 ++-- .../DataStructures/OrderedSet.swift | 8 +++----- .../Layouts/CollectionViewColumnLayout.swift | 6 +++--- .../Layouts/CollectionViewFlowLayout.swift | 2 +- .../Layouts/CollectionViewGridLayout.swift | 14 +++++-------- .../CollectionViewHorizontalLayout.swift | 8 +++----- .../CollectionViewPreviewController.swift | 2 +- .../FetchedSetController.swift | 2 +- ...dObjectContextObservationCoordinator.swift | 20 +++++++------------ .../MergedFetchedResultsController.swift | 2 +- .../MutableResultsController.swift | 4 ++-- CollectionView/ScrollView.swift | 6 ++---- CollectionViewTests/CVColumnLayoutTests.swift | 4 ++-- CollectionViewTests/CVFlowLayoutTests.swift | 4 ++-- CollectionViewTests/CVListLayoutTests.swift | 4 ++-- 18 files changed, 43 insertions(+), 57 deletions(-) diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index 3973d4b..c7144d5 100644 --- a/CollectionView.xcodeproj/project.pbxproj +++ b/CollectionView.xcodeproj/project.pbxproj @@ -669,6 +669,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; @@ -680,7 +681,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -697,6 +698,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; @@ -708,7 +710,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme index f3a2f18..c8f727d 100644 --- a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme +++ b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme @@ -1,6 +1,6 @@ { deletes.insert(e, for: e.index) case .substitution: substitutions.insert(e, for: e.index) - case .move(origin: _): + case .move: moves.insert(e, for: e.index) } } @@ -161,7 +161,7 @@ public struct EditOperationIndex { case .deletion: self.deletes.remove(edit) case .insertion: self.inserts.remove(edit) case .substitution: self.substitutions.remove(edit) - case .move(origin: _): self.moves.remove(edit) + case .move: self.moves.remove(edit) } } diff --git a/CollectionView/DataStructures/OrderedSet.swift b/CollectionView/DataStructures/OrderedSet.swift index 3ffe824..ed11766 100644 --- a/CollectionView/DataStructures/OrderedSet.swift +++ b/CollectionView/DataStructures/OrderedSet.swift @@ -168,11 +168,9 @@ public struct OrderedSet: ExpressibleByArrayLiteral, Collecti public mutating func insert(contentsOf newElements: C, at index: Int) -> Set where C.Iterator.Element == Element { var inserted = Set() - for (idx, e) in newElements.enumerated() { - if !self.contains(e) { - self._data.insert(e, at: index + idx) - inserted.insert(e) - } + for (idx, e) in newElements.enumerated() where !self.contains(e) { + self._data.insert(e, at: index + idx) + inserted.insert(e) } if !inserted.isEmpty { self._remap(startingAt: index) diff --git a/CollectionView/Layouts/CollectionViewColumnLayout.swift b/CollectionView/Layouts/CollectionViewColumnLayout.swift index ccc40fd..a02e27e 100644 --- a/CollectionView/Layouts/CollectionViewColumnLayout.swift +++ b/CollectionView/Layouts/CollectionViewColumnLayout.swift @@ -533,7 +533,7 @@ extension CollectionViewColumnLayout { public typealias ItemRenderDirection = LayoutStrategy @available(*, deprecated, renamed: "layoutStrategy") - open var itemRenderDirection: LayoutStrategy { + public var itemRenderDirection: LayoutStrategy { get { return layoutStrategy } set { self.layoutStrategy = newValue } } @@ -619,11 +619,11 @@ extension CollectionViewColumnLayout { private func nextColumnIndexForItem(_ indexPath: IndexPath, strategy: LayoutStrategy) -> Column { switch strategy { - case .shortestFirst : + case .shortestFirst: return columns.min(by: { (c1, c2) -> Bool in return c1.frame.size.height < c2.frame.size.height })! - case .leftToRight : + case .leftToRight: let colCount = self.columns.count let index = (indexPath._item % colCount) return self.columns[index] diff --git a/CollectionView/Layouts/CollectionViewFlowLayout.swift b/CollectionView/Layouts/CollectionViewFlowLayout.swift index 95c73bf..33d71fe 100644 --- a/CollectionView/Layouts/CollectionViewFlowLayout.swift +++ b/CollectionView/Layouts/CollectionViewFlowLayout.swift @@ -768,7 +768,7 @@ open class CollectionViewFlowLayout: CollectionViewLayout { startingIP = ip fallthrough - case .right : + case .right: var ip = startingIP while true { diff --git a/CollectionView/Layouts/CollectionViewGridLayout.swift b/CollectionView/Layouts/CollectionViewGridLayout.swift index 4a38132..fd24b6e 100644 --- a/CollectionView/Layouts/CollectionViewGridLayout.swift +++ b/CollectionView/Layouts/CollectionViewGridLayout.swift @@ -341,10 +341,8 @@ public final class CollectionViewGridLayout: CollectionViewLayout { let rowFrame = sec.frameForRow(rowIdx) if !rowFrame.intersects(rect) { continue } - for attr in rowAttrs[rowIdx] { - if attr.frame.intersects(rect) { - indexPaths.insert(attr.indexPath as IndexPath) - } + for attr in rowAttrs[rowIdx] where attr.frame.intersects(rect) { + indexPaths.insert(attr.indexPath as IndexPath) } } } @@ -370,10 +368,8 @@ public final class CollectionViewGridLayout: CollectionViewLayout { let rowFrame = sec.frameForRow(rowIdx) if rowFrame.intersects(rect) { continue } - for attr in rowAttrs[rowIdx] { - if attr.frame.intersects(rect) { - attrs.append(attr) - } + for attr in rowAttrs[rowIdx] where attr.frame.intersects(rect) { + attrs.append(attr) } } } @@ -542,7 +538,7 @@ public final class CollectionViewGridLayout: CollectionViewLayout { index = collectionView.numberOfItems(in: currentIndexPath._section - 1) - 1 } return IndexPath.for(item: index, section: section) - case .right : + case .right: if section == numberOfSections - 1 && index == numberOfItemsInSection - 1 { return currentIndexPath } diff --git a/CollectionView/Layouts/CollectionViewHorizontalLayout.swift b/CollectionView/Layouts/CollectionViewHorizontalLayout.swift index 6151517..9ff75af 100644 --- a/CollectionView/Layouts/CollectionViewHorizontalLayout.swift +++ b/CollectionView/Layouts/CollectionViewHorizontalLayout.swift @@ -144,11 +144,9 @@ open class CollectionViewHorizontalListLayout: CollectionViewLayout { var ips = [IndexPath]() for (sectionIdx, section) in cache.enumerated() { - for (idx, item) in section.enumerated() { - if rect.intersects(item) { - let ip = IndexPath.for(item: idx, section: sectionIdx) - ips.append(ip) - } + for (idx, item) in section.enumerated() where rect.intersects(item) { + let ip = IndexPath.for(item: idx, section: sectionIdx) + ips.append(ip) } } return ips diff --git a/CollectionView/Preview/CollectionViewPreviewController.swift b/CollectionView/Preview/CollectionViewPreviewController.swift index 554ac74..dd18502 100644 --- a/CollectionView/Preview/CollectionViewPreviewController.swift +++ b/CollectionView/Preview/CollectionViewPreviewController.swift @@ -262,7 +262,7 @@ open class CollectionViewPreviewController: CollectionViewController, Collection // MARK: - Transitions /*-------------------------------------------------------------------------------*/ - public var layoutConstraintConfiguration : ((_ container: NSViewController, _ controller: CollectionViewPreviewController) -> Void)? + public var layoutConstraintConfiguration: ((_ container: NSViewController, _ controller: CollectionViewPreviewController) -> Void)? /// The duration of present/dismiss transitions open var transitionDuration: TimeInterval = 0.25 diff --git a/CollectionView/ResultsController/FetchedSetController.swift b/CollectionView/ResultsController/FetchedSetController.swift index 6a683ae..2dab0ff 100644 --- a/CollectionView/ResultsController/FetchedSetController.swift +++ b/CollectionView/ResultsController/FetchedSetController.swift @@ -139,7 +139,7 @@ public class FetchedSetController: ContextObserver { return self.delegate != nil } - public override func process(_ changes: [NSEntityDescription : (inserted: Set, deleted: Set, updated: Set)]) { + public override func process(_ changes: [NSEntityDescription: (inserted: Set, deleted: Set, updated: Set)]) { guard let changes = changes[self.fetchRequest.entity!] else { return } diff --git a/CollectionView/ResultsController/ManagedObjectContextObservationCoordinator.swift b/CollectionView/ResultsController/ManagedObjectContextObservationCoordinator.swift index 771dba1..f6fbc15 100644 --- a/CollectionView/ResultsController/ManagedObjectContextObservationCoordinator.swift +++ b/CollectionView/ResultsController/ManagedObjectContextObservationCoordinator.swift @@ -10,7 +10,7 @@ import Foundation fileprivate let nilKeyHash = UUID().hashValue -fileprivate struct RefKeyTable : Sequence, ExpressibleByDictionaryLiteral { +fileprivate struct RefKeyTable: Sequence, ExpressibleByDictionaryLiteral { private struct KeyRef: Hashable { @@ -166,17 +166,13 @@ class ManagedObjectContextObservationCoordinator { if let invalidated = info[NSInvalidatedObjectsKey] as? Set { deleted = deleted.union(invalidated) } - for obj in deleted { - if changeSets[obj.entity]?.deleted(obj) == nil { - changeSets[obj.entity] = EntityChangeSet(deleted: obj) - } + for obj in deleted where changeSets[obj.entity]?.deleted(obj) == nil { + changeSets[obj.entity] = EntityChangeSet(deleted: obj) } if let inserted = info[NSInsertedObjectsKey] as? Set { - for obj in inserted { - if changeSets[obj.entity]?.inserted(obj) == nil { - changeSets[obj.entity] = EntityChangeSet(inserted: obj) - } + for obj in inserted where changeSets[obj.entity]?.inserted(obj) == nil { + changeSets[obj.entity] = EntityChangeSet(inserted: obj) } } @@ -184,10 +180,8 @@ class ManagedObjectContextObservationCoordinator { if let invalidated = info[NSRefreshedObjectsKey] as? Set { updated = updated.union(invalidated) } - for obj in updated { - if changeSets[obj.entity]?.updated(obj) == nil { - changeSets[obj.entity] = EntityChangeSet(updated: obj) - } + for obj in updated where changeSets[obj.entity]?.updated(obj) == nil { + changeSets[obj.entity] = EntityChangeSet(updated: obj) } NotificationCenter.default.post(name: Notification.name, object: notification.object, userInfo: [ diff --git a/CollectionView/ResultsController/MergedFetchedResultsController.swift b/CollectionView/ResultsController/MergedFetchedResultsController.swift index 7a41567..99d67f8 100644 --- a/CollectionView/ResultsController/MergedFetchedResultsController.swift +++ b/CollectionView/ResultsController/MergedFetchedResultsController.swift @@ -531,7 +531,7 @@ public class MergedFetchedResultsController for e in sourceEdits { switch e.operation { case .substitution: _affected = _affected ?? (e.value, e.index) - case .move(origin: _): _affected = (e.value, e.index) + case .move: _affected = (e.value, e.index) default: break } processedSections[sourceSectionIndex]!.operationIndex.remove(edit: e) @@ -591,7 +591,7 @@ public class MutableResultsController for edit in changes { switch edit.operation { - case .move(origin: _): + case .move: // Get the source and target guard let source = self._editingContext.objectChanges.updated.index(of: edit.value), let dest = self.indexPath(of: edit.value) else { diff --git a/CollectionView/ScrollView.swift b/CollectionView/ScrollView.swift index d153f88..6bb64c6 100644 --- a/CollectionView/ScrollView.swift +++ b/CollectionView/ScrollView.swift @@ -36,10 +36,8 @@ open class ScrollView: NSScrollView { class FloatingSupplementaryView: NSView { override var isFlipped: Bool { return true } internal override func hitTest(_ aPoint: NSPoint) -> NSView? { - for view in self.subviews { - if view.frame.contains(aPoint) { - return super.hitTest(aPoint) - } + for view in self.subviews where view.frame.contains(aPoint) { + return super.hitTest(aPoint) } return nil } diff --git a/CollectionViewTests/CVColumnLayoutTests.swift b/CollectionViewTests/CVColumnLayoutTests.swift index ba308e8..052b32a 100644 --- a/CollectionViewTests/CVColumnLayoutTests.swift +++ b/CollectionViewTests/CVColumnLayoutTests.swift @@ -35,7 +35,7 @@ class CVColumnLayoutTests: XCTestCase { XCTAssertEqual(items.count, 42) } - private let _prepareCounts = (sections: 100, items: 300) + private let _prepareCounts = (sections: 100, items: 300) func testTesterPerformance_bigSection() { self.measure { _ = LayoutTester(sections: 1, itemsPerSection: _prepareCounts.sections * _prepareCounts.items) @@ -86,7 +86,7 @@ class CVColumnLayoutTests: XCTestCase { // MARK: - Multi Section indexPathsForItems(in rect) /*-------------------------------------------------------------------------------*/ - private let _counts = (sections: 20, items: 2000) + private let _counts = (sections: 20, items: 2000) func testIndexPathsInRectPerformance_multiSection_top() { let test = LayoutTester(sections: _counts.sections, itemsPerSection: _counts.items) diff --git a/CollectionViewTests/CVFlowLayoutTests.swift b/CollectionViewTests/CVFlowLayoutTests.swift index ce81e35..ab61b82 100644 --- a/CollectionViewTests/CVFlowLayoutTests.swift +++ b/CollectionViewTests/CVFlowLayoutTests.swift @@ -27,7 +27,7 @@ class CVFlowLayoutTests: XCTestCase { XCTAssertFalse(test.layout.shouldInvalidateLayout(forBoundsChange: test.frame.offsetBy(dx: 4, dy: 5))) } - private let _prepareCounts = (sections: 100, items: 300) + private let _prepareCounts = (sections: 100, items: 300) func testTesterPerformance_bigSection() { self.measure { _ = LayoutTester(sections: 1, itemsPerSection: _prepareCounts.sections * _prepareCounts.items) @@ -86,7 +86,7 @@ class CVFlowLayoutTests: XCTestCase { // MARK: - Multi Section indexPathsForItems(in rect) /*-------------------------------------------------------------------------------*/ - private let _counts = (sections: 100, items: 5000) + private let _counts = (sections: 100, items: 5000) func testIndexPathsInRectPerformance_multiSection_top() { let test = LayoutTester(sections: _counts.sections, itemsPerSection: _counts.items) diff --git a/CollectionViewTests/CVListLayoutTests.swift b/CollectionViewTests/CVListLayoutTests.swift index 57d7ee7..9d77752 100644 --- a/CollectionViewTests/CVListLayoutTests.swift +++ b/CollectionViewTests/CVListLayoutTests.swift @@ -27,7 +27,7 @@ class CVListLayoutTests: XCTestCase { XCTAssertFalse(test.layout.shouldInvalidateLayout(forBoundsChange: test.frame.offsetBy(dx: 4, dy: 5))) } - private let _prepareCounts = (sections: 100, items: 300) + private let _prepareCounts = (sections: 100, items: 300) func testTesterPerformance_bigSection() { self.measure { _ = LayoutTester(sections: 1, itemsPerSection: _prepareCounts.sections * _prepareCounts.items) @@ -65,7 +65,7 @@ class CVListLayoutTests: XCTestCase { // MARK: - Multi Section indexPathsForItems(in rect) /*-------------------------------------------------------------------------------*/ - private let _counts = (sections: 100, items: 5000) + private let _counts = (sections: 100, items: 5000) func testIndexPathsInRectPerformance_multiSection_top() { let test = LayoutTester(sections: _counts.sections, itemsPerSection: _counts.items) From e483b8fe80c31aa9b43783bff1922d2495d1c2c4 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Fri, 20 Jan 2023 07:42:44 -0800 Subject: [PATCH 12/16] Bumper min version --- CollectionView.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index c7144d5..db01871 100644 --- a/CollectionView.xcodeproj/project.pbxproj +++ b/CollectionView.xcodeproj/project.pbxproj @@ -615,7 +615,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -649,7 +649,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -681,7 +681,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -710,7 +710,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; From 9a21f5da2ae1a360b21d91ae088a9b6a12419ad0 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Fri, 20 Jan 2023 09:09:45 -0800 Subject: [PATCH 13/16] Project settings --- CollectionView.xcodeproj/project.pbxproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index db01871..b2b737e 100644 --- a/CollectionView.xcodeproj/project.pbxproj +++ b/CollectionView.xcodeproj/project.pbxproj @@ -333,7 +333,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1410; + LastUpgradeCheck = 1420; ORGANIZATIONNAME = "Noun Project"; TargetAttributes = { DE9071E81CAC7FD800AD0E37 = { @@ -615,7 +615,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -649,7 +649,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -681,7 +681,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -710,7 +710,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = com.thenounproject.CollectionViewTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_COMPILATION_MODE = wholemodule; From 762a38ff91974b016fa67bf8af0fdd536d906082 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Fri, 7 Apr 2023 09:51:11 -0700 Subject: [PATCH 14/16] Update project settings --- .../xcshareddata/xcschemes/CollectionView.xcscheme | 2 +- .../xcshareddata/xcschemes/CollectionViewTests.xcscheme | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme index c8f727d..dff8ee9 100644 --- a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme +++ b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 27 Sep 2023 10:01:15 -0700 Subject: [PATCH 15/16] Update unavailable functions --- .../ResultsController/MutableResultsController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CollectionView/ResultsController/MutableResultsController.swift b/CollectionView/ResultsController/MutableResultsController.swift index 270b6c5..5002ae4 100644 --- a/CollectionView/ResultsController/MutableResultsController.swift +++ b/CollectionView/ResultsController/MutableResultsController.swift @@ -626,10 +626,10 @@ public class MutableResultsController } @available(*, unavailable, message: "This functionality has been replaced with CollectionViewProvider.") - public var hasEmptyPlaceholder: Bool = false + public var hasEmptyPlaceholder: Bool { return false } @available(*, unavailable, message: "This functionality has been replaced with CollectionViewProvider.") - public private(set) var placeholderChanges: CollectionViewProvider? + public var placeholderChanges: CollectionViewProvider? { return nil } } extension MutableResultsController { From eacb0f0f5796754041ad5183d58980276cdf12e2 Mon Sep 17 00:00:00 2001 From: Wes Byrne Date: Mon, 2 Oct 2023 15:28:40 -0700 Subject: [PATCH 16/16] fix draw calls --- CollectionView.xcodeproj/project.pbxproj | 10 ++++++++++ CollectionView/CollectionViewCells.swift | 2 +- .../Preview/CollectionViewPreviewController.swift | 2 +- CollectionViewTests/ReaultionalRCTests.swift | 1 - 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index b2b737e..13f6bd4 100644 --- a/CollectionView.xcodeproj/project.pbxproj +++ b/CollectionView.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ DE13C18A1E4CE46100CEC80C /* ManagedObjectContextObservationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextObservationCoordinator.swift; sourceTree = ""; }; DE1EE9A42072B00B00B76FE2 /* CollapsableCollectionViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableCollectionViewProvider.swift; sourceTree = ""; }; DE297DDF1E6A8B5400AAFC2A /* SupplementaryViewIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupplementaryViewIdentifier.swift; sourceTree = ""; }; + DE331AE62ACB313400CB3D6E /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; DE35BE1E20376FCC008FEF6F /* SortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortDescriptor.swift; sourceTree = ""; }; DE3E47551E4259B800F19D9E /* EditDistance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditDistance.swift; sourceTree = ""; }; DE46308D2044C114001AF02E /* WagnerFischer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WagnerFischer.swift; sourceTree = ""; }; @@ -148,6 +149,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + DE331AE52ACB313400CB3D6E /* Frameworks */ = { + isa = PBXGroup; + children = ( + DE331AE62ACB313400CB3D6E /* Metal.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; DE4630892044C114001AF02E /* DataStructures */ = { isa = PBXGroup; children = ( @@ -219,6 +228,7 @@ DE9071EB1CAC7FD800AD0E37 /* CollectionView */, DEA638AF200874950023F2BD /* CollectionViewTests */, DE9071EA1CAC7FD800AD0E37 /* Products */, + DE331AE52ACB313400CB3D6E /* Frameworks */, ); sourceTree = ""; }; diff --git a/CollectionView/CollectionViewCells.swift b/CollectionView/CollectionViewCells.swift index 2f9440d..b14a125 100644 --- a/CollectionView/CollectionViewCells.swift +++ b/CollectionView/CollectionViewCells.swift @@ -94,7 +94,7 @@ open class CollectionReusableView: NSView { if let c = self.backgroundColor { NSGraphicsContext.saveGraphicsState() c.setFill() - dirtyRect.fill() + self.bounds.intersection(dirtyRect).fill() NSGraphicsContext.restoreGraphicsState() } super.draw(dirtyRect) diff --git a/CollectionView/Preview/CollectionViewPreviewController.swift b/CollectionView/Preview/CollectionViewPreviewController.swift index dd18502..c79f3c3 100644 --- a/CollectionView/Preview/CollectionViewPreviewController.swift +++ b/CollectionView/Preview/CollectionViewPreviewController.swift @@ -70,7 +70,7 @@ class BackgroundView: NSView { if !self.isHidden, let c = backgroundColor { NSGraphicsContext.saveGraphicsState() c.set() - dirtyRect.fill() + self.bounds.intersection(dirtyRect).fill() NSGraphicsContext.restoreGraphicsState() } } diff --git a/CollectionViewTests/ReaultionalRCTests.swift b/CollectionViewTests/ReaultionalRCTests.swift index 1162712..b6711f0 100644 --- a/CollectionViewTests/ReaultionalRCTests.swift +++ b/CollectionViewTests/ReaultionalRCTests.swift @@ -295,7 +295,6 @@ extension String { randomString += NSString(characters: &nextChar, length: 1) as String } return randomString - } } fileprivate class Parent: NSManagedObject, CustomDisplayStringConvertible {