diff --git a/CollectionView.xcodeproj/project.pbxproj b/CollectionView.xcodeproj/project.pbxproj index 1a799a3..13f6bd4 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 */ @@ -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 = ""; }; @@ -333,7 +343,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1420; ORGANIZATIONNAME = "Noun Project"; TargetAttributes = { DE9071E81CAC7FD800AD0E37 = { @@ -387,6 +397,7 @@ /* Begin PBXShellScriptBuildPhase section */ 1CB6A657211D6BAC00907CEF /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -510,6 +521,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 +580,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 +610,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 +620,13 @@ 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 = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = thenounproject.CollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -625,6 +644,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; @@ -634,13 +654,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 = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + 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; @@ -653,13 +679,19 @@ 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; 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; @@ -676,16 +708,23 @@ 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; 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..dff8ee9 100644 --- a/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme +++ b/CollectionView.xcodeproj/xcshareddata/xcschemes/CollectionView.xcscheme @@ -1,6 +1,6 @@ Int? { - return final[previousIndex] + if previousIndex < final.count { + return final[previousIndex] + } + return nil } } @@ -1701,7 +1696,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) } @@ -1779,12 +1774,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) } } 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/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/DataStructures/EditDistance/EditDistance.swift b/CollectionView/DataStructures/EditDistance/EditDistance.swift index e5be42a..716c6d1 100644 --- a/CollectionView/DataStructures/EditDistance/EditDistance.swift +++ b/CollectionView/DataStructures/EditDistance/EditDistance.swift @@ -109,7 +109,7 @@ public struct EditOperationIndex { 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/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..a02e27e 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,26 +111,9 @@ 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 - -} - -/// 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 } - } + func collectionView (_ collectionView: CollectionView, + layout collectionViewLayout: CollectionViewColumnLayout, + heightForFooterInSection section: Int) -> CGFloat } /** @@ -136,9 +133,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 +154,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 +217,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 +273,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,25 +289,30 @@ 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 top = attributes.frame.maxY } + + if sectionIdx == 0, let leading = leadingViewAttributes { + section.frame = section.frame.union(leading.frame) + } + section.frame.size.height += sectionInsets.bottom top = section.frame.maxY sections.append(section) } } - override open var collectionViewContentSize: CGSize { + open var collectionViewContentSize: CGSize { guard let cv = collectionView else { return CGSize.zero } var contentSize = cv.contentVisibleRect.size @@ -395,18 +323,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 +369,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 +394,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 +418,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 +432,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 +468,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 +513,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 +527,111 @@ open class CollectionViewColumnLayout: CollectionViewLayout { } } } + +extension CollectionViewColumnLayout { + @available(*, deprecated, renamed: "LayoutStrategy") + public typealias ItemRenderDirection = LayoutStrategy + + @available(*, deprecated, renamed: "layoutStrategy") + public 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..33d71fe 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, @@ -184,8 +195,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 +235,13 @@ 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 leadingViewAttributes: CollectionViewLayoutAttributes? + private var sectionAttributes = [SectionAttributes]() + /// Row transforms can be applied to flow elements that fall within the same row /// /// - none: No transform @@ -363,58 +389,67 @@ 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() + 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 @@ -435,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) - } } // 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,25 +620,20 @@ 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? { - - if elementKind == CollectionViewLayoutElementKind.SectionHeader { + open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> CollectionViewLayoutAttributes? { + switch elementKind { + case CollectionViewLayoutElementKind.LeadingView: + return leadingViewAttributes?.copy() + 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 @@ -608,24 +641,23 @@ 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 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 +669,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 +690,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 @@ -736,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 64ad8fa..9ff75af 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? + + public var allIndexPaths = OrderedSet() - override open var scrollDirection: CollectionViewScrollDirection { + 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,35 +126,33 @@ 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() { - 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 } - 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 +161,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..975658c 100644 --- a/CollectionView/Layouts/CollectionViewListLayout.swift +++ b/CollectionView/Layouts/CollectionViewListLayout.swift @@ -21,9 +21,17 @@ 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 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 @@ -32,7 +40,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 +51,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 +65,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 +76,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() }} @@ -105,6 +121,7 @@ public final class CollectionViewListLayout: 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 { @@ -123,19 +140,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() @@ -148,6 +165,15 @@ public final class CollectionViewListLayout: 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 { @@ -225,7 +255,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 +266,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,14 +309,17 @@ 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 { + 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 @@ -301,13 +334,13 @@ public final class CollectionViewListLayout: 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 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 +352,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/CollectionViewPreviewController.swift b/CollectionView/Preview/CollectionViewPreviewController.swift index 554ac74..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() } } @@ -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/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 + } + } 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 { @@ -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 { 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 3e7ce98..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) @@ -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/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 6f60286..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) @@ -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 } } 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 {