Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CollectionKitTests/FlowLayoutSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class FlowLayoutSpec: QuickSpec {
it("should not display cells outside of the visible area") {
let layout = FlowLayout().transposed()
layout.mockLayout(parentSize: (100, 50), (50, 50), (50, 50), (50, 50), (50, 50))
let visible = layout.visibleIndexes(visibleFrame: CGRect(x: 50, y: 0, width: 100, height: 50))
let visible = layout.visible(in: CGRect(x: 50, y: 0, width: 100, height: 50)).indexes
expect(visible).to(equal([1, 2]))
}
}
Expand Down
2 changes: 1 addition & 1 deletion CollectionKitTests/WaterfallLayoutSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class WaterfallLayoutSpec: QuickSpec {
it("should not display cells outside of the visible area") {
let layout = WaterfallLayout()
layout.mockLayout(parentSize: (100, 50), (50, 50), (50, 50), (50, 50), (50, 50))
let visible = layout.visibleIndexes(visibleFrame: CGRect(x: 0, y: 50, width: 100, height: 50))
let visible = layout.visible(in: CGRect(x: 0, y: 50, width: 100, height: 50)).indexes
expect(visible).to(equal([2, 3]))
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/CollectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ open class CollectionView: UIScrollView {
}

private func _loadCells(forceReload: Bool) {
let newIndexes = flattenedProvider.visibleIndexes(visibleFrame: visibleFrame)
let newIndexes = flattenedProvider.visible(in: visibleFrame).indexes

// optimization: we assume that corresponding identifier for each index doesnt change unless forceReload is true.
guard forceReload ||
Expand Down
4 changes: 2 additions & 2 deletions Sources/Layout/InsetLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ open class InsetLayout: WrapperLayout {
rootLayout.layout(context: InsetLayoutContext(original: context, insets: insets))
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
return rootLayout.visibleIndexes(visibleFrame: visibleFrame.inset(by: -insets))
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
return rootLayout.visible(in: visibleFrame.inset(by: -insets))
}

open override func frame(at: Int) -> CGRect {
Expand Down
3 changes: 2 additions & 1 deletion Sources/Layout/Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ open class Layout {
fatalError("Subclass should provide its own layout")
}

open func visibleIndexes(visibleFrame: CGRect) -> [Int] {
open func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
fatalError("Subclass should provide its own layout")
}

Expand All @@ -38,6 +38,7 @@ extension Layout {
return InsetLayout(self, insets: insets)
}

/// Visible insets in the opposite of the layout direction are doubled
public func insetVisibleFrame(by insets: UIEdgeInsets) -> VisibleFrameInsetLayout {
return VisibleFrameInsetLayout(self, insets: insets)
}
Expand Down
30 changes: 24 additions & 6 deletions Sources/Layout/SimpleLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ open class SimpleLayout: Layout {
return frames[at]
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
var result = [Int]()
for (i, frame) in frames.enumerated() {
if frame.intersects(visibleFrame) {
result.append(i)
}
}
return result
return (result, visibleFrame)
}
}

Expand All @@ -54,7 +54,14 @@ open class VerticalSimpleLayout: SimpleLayout {
maxFrameLength = frames.max { $0.height < $1.height }?.height ?? 0
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
guard !visibleFrame.isEmptyOrNegative else {
// When this vertical layout gets called in a
// section provider with horizontal layout we need
// to guard here because the optimised index search
// here doesn't take the X axes into account.
return ([], visibleFrame)
}
var index = frames.binarySearch { $0.minY < visibleFrame.minY - maxFrameLength }
var visibleIndexes = [Int]()
while index < frames.count {
Expand All @@ -67,7 +74,7 @@ open class VerticalSimpleLayout: SimpleLayout {
}
index += 1
}
return visibleIndexes
return (visibleIndexes, visibleFrame)
}
}

Expand All @@ -78,7 +85,10 @@ open class HorizontalSimpleLayout: SimpleLayout {
maxFrameLength = frames.max { $0.width < $1.width }?.width ?? 0
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
guard !visibleFrame.isEmptyOrNegative else {
return ([], visibleFrame)
}
var index = frames.binarySearch { $0.minX < visibleFrame.minX - maxFrameLength }
var visibleIndexes = [Int]()
while index < frames.count {
Expand All @@ -91,6 +101,14 @@ open class HorizontalSimpleLayout: SimpleLayout {
}
index += 1
}
return visibleIndexes
return (visibleIndexes, visibleFrame)
}
}

extension CGRect {
/// Returns whether a rectangle has zero or less
/// width or height, or is a null rectangle.
var isEmptyOrNegative: Bool {
return isEmpty || size.width < 0 || size.height < 0
}
}
14 changes: 8 additions & 6 deletions Sources/Layout/StickyLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ public class StickyLayout: WrapperLayout {
}
}

public override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
// TODO: Fix for the new FlattenedProvider.visible(in:)
public override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
self.visibleFrame = visibleFrame
topFrameIndex = stickyFrames.binarySearch { $0.frame.minY < visibleFrame.minY } - 1
if let index = stickyFrames.get(topFrameIndex)?.index, index >= 0 {
var oldVisible = rootLayout.visibleIndexes(visibleFrame: visibleFrame)
if let index = oldVisible.index(of: index) {
oldVisible.remove(at: index)
var oldVisible = rootLayout.visible(in: visibleFrame)
if let index = oldVisible.indexes.index(of: index) {
oldVisible.indexes.remove(at: index)
}
return oldVisible + [index]
oldVisible.indexes += [index]
return oldVisible
}
return rootLayout.visibleIndexes(visibleFrame: visibleFrame)
return rootLayout.visible(in: visibleFrame)
}

public override func frame(at: Int) -> CGRect {
Expand Down
5 changes: 3 additions & 2 deletions Sources/Layout/TransposeLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ open class TransposeLayout: WrapperLayout {
rootLayout.layout(context: TransposeLayoutContext(original: context))
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
return rootLayout.visibleIndexes(visibleFrame: visibleFrame.transposed)
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
let visible = rootLayout.visible(in: visibleFrame.transposed)
return (visible.indexes, visible.frame.transposed)
}

open override func frame(at: Int) -> CGRect {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Layout/VisibleFrameInsetLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ open class VisibleFrameInsetLayout: WrapperLayout {
super.layout(context: context)
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
return rootLayout.visibleIndexes(visibleFrame: visibleFrame.inset(by: insets))
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
return rootLayout.visible(in: visibleFrame.inset(by: insets))
}
}
4 changes: 2 additions & 2 deletions Sources/Layout/WrapperLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ open class WrapperLayout: Layout {
rootLayout.layout(context: context)
}

open override func visibleIndexes(visibleFrame: CGRect) -> [Int] {
return rootLayout.visibleIndexes(visibleFrame: visibleFrame)
open override func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
return rootLayout.visible(in: visibleFrame)
}

open override func frame(at: Int) -> CGRect {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Protocol/LayoutableProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ extension LayoutableProvider where Self: Provider {
public func layout(collectionSize: CGSize) {
internalLayout.layout(context: layoutContext(collectionSize: collectionSize))
}
public func visibleIndexes(visibleFrame: CGRect) -> [Int] {
return internalLayout.visibleIndexes(visibleFrame: visibleFrame)
public func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
return internalLayout.visible(in: visibleFrame)
}
public var contentSize: CGSize {
return internalLayout.contentSize
Expand Down
2 changes: 1 addition & 1 deletion Sources/Protocol/Provider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public protocol Provider {

// layout
func layout(collectionSize: CGSize)
func visibleIndexes(visibleFrame: CGRect) -> [Int]
func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect)
var contentSize: CGSize { get }
func frame(at: Int) -> CGRect

Expand Down
4 changes: 2 additions & 2 deletions Sources/Provider/EmptyCollectionProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ open class EmptyCollectionProvider: ItemProvider, CollectionReloadable {
open func frame(at: Int) -> CGRect {
return .zero
}
open func visibleIndexes(visibleFrame: CGRect) -> [Int] {
return [Int]()
open func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
return ([Int](), visibleFrame)
}

open func animator(at: Int) -> Animator? {
Expand Down
57 changes: 41 additions & 16 deletions Sources/Provider/FlattenedProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,33 @@ struct FlattenedProvider: ItemProvider {
return provider.contentSize
}

func visibleIndexes(visibleFrame: CGRect) -> [Int] {
var visible = [Int]()
for sectionIndex in provider.visibleIndexes(visibleFrame: visibleFrame) {
let beginIndex = childSections[sectionIndex].beginIndex
if let sectionData = childSections[sectionIndex].sectionData {
let sectionFrame = provider.frame(at: sectionIndex)
let intersectFrame = visibleFrame.intersection(sectionFrame)
let visibleFrameForCell = CGRect(origin: intersectFrame.origin - sectionFrame.origin, size: intersectFrame.size)
let sectionVisible = sectionData.visibleIndexes(visibleFrame: visibleFrameForCell)
for item in sectionVisible {
visible.append(item + beginIndex)
}
} else {
visible.append(beginIndex)
}
func visible(in visibleFrame: CGRect) -> (indexes: [Int], frame: CGRect) {
let visible = provider.visible(in: visibleFrame)
// sort child sections by the indexes from layout
let sections = Array(0..<childSections.count).sorted(by: visible.indexes)
// load indexes from all child sections
let indexes = sections.flatMap { index -> [Int] in
let section = provider.frame(at: index)
// intersection that doesn't return invalid frame when the
// rects don't intersect but rather returns a rect spanning
// the part of the border of the visible frame where the
// section frame would enter the visible frame if it
// were closer. This allows for the section to add its
// visible inset to that rect and show according to that.
// Calculation source https://math.stackexchange.com/a/99576
let x = max(0, visible.frame.origin.x - section.origin.x)
let y = max(0, visible.frame.origin.y - section.origin.y)
let maxTop = max(visible.frame.origin.y, section.origin.y)
let maxLeft = max(visible.frame.origin.x, section.origin.x)
let minBottom = min(visible.frame.maxY, section.maxY)
let minRight = min(visible.frame.maxX, section.maxX)
let visibleFrameForCell = CGRect(x: x, y: y, width: minRight - maxLeft, height: minBottom - maxTop)
let child = childSections[index]
let childVisible = child.sectionData?.visible(in: visibleFrameForCell)
let childIndexes = childVisible?.indexes ?? [0]
return childIndexes.map { $0 + child.beginIndex }
}
return visible
return (indexes, visible.frame)
}

func frame(at: Int) -> CGRect {
Expand Down Expand Up @@ -138,3 +148,18 @@ struct FlattenedProvider: ItemProvider {
return provider.hasReloadable(reloadable)
}
}

extension CGPoint {
static var infinity: CGPoint = CGPoint(x: CGFloat.infinity, y: CGFloat.infinity)
}

extension Array where Element: Equatable {

/// Returns an array sorted based on another array
///
/// - Parameter array: The array with items that we sort bt
/// - Returns: An array sorted by elements in parameter array.
func sorted(by array: [Element]) -> [Element] {
return array.filter { contains($0) } + filter { !array.contains($0) }
}
}