Skip to content

Instantly share code, notes, and snippets.

@shaps80
Last active March 28, 2019 05:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shaps80/56f6f9c86fd9e209eee5f864311a70dd to your computer and use it in GitHub Desktop.
Save shaps80/56f6f9c86fd9e209eee5f864311a70dd to your computer and use it in GitHub Desktop.
Provides an iterator that allows for simple traversing over an dataSource's IndexPath's – including support for a header and footer per section.
public struct IndexPathIterator: IteratorProtocol {
public let indexPaths: [IndexPath]
private var index: Array<IndexPath>.Index
/// Creates a new iterator for the specified indexPaths
///
/// - Parameter indexPaths: The indexPaths to iterate over
public init(indexPaths: Set<IndexPath>) {
self.indexPaths = indexPaths.sorted()
self.index = self.indexPaths.startIndex
}
/// Returns the next element. If there are no more elements, this returns nil.
///
/// - Returns: Returns the next element. If the collection is empty, this returns nil
public mutating func next() -> Array<IndexPath>.Element? {
guard !indexPaths.isEmpty, index != indexPaths.endIndex else { return nil }
let result = indexPaths[index]
indexPaths.formIndex(after: &index)
return result
}
}
public extension IndexPath {
enum Kind: Int {
case header
case footer
case item
}
struct KindMask: RawRepresentable, OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let header = KindMask(rawValue: 1 << Kind.header.rawValue)
public static let footer = KindMask(rawValue: 1 << Kind.footer.rawValue)
public static let item = KindMask(rawValue: 1 << Kind.item.rawValue)
public func contains(_ member: Kind) -> Bool {
if member == .header && contains(KindMask.header) { return true }
if member == .footer && contains(KindMask.footer) { return true }
if member == .item && contains(KindMask.item) { return true }
return false
}
}
init(kind: Kind, item: Int, section: Int) {
switch kind {
case .header:
self = [section, -1, 0]
case .footer:
self = [section, item, 1]
case .item:
self = IndexPath(item: item, section: section)
}
}
var kind: Kind {
guard count == 3 else { return .item }
return self[2] == 0 ? .header : .footer
}
var section: Int {
return self[0]
}
var item: Int {
return self[1]
}
}
import Foundation
public extension Collection where Element == IndexPath {
/// Returns indexPaths of the specified kinds, that are in the given section
///
/// - Parameters:
/// - kinds: The kinds to match against
/// - section: The section to match against
/// - Returns: An array of indexPaths, empty if no matches
func filter(include kinds: IndexPath.KindMask, inSections sections: Int...) -> [SubSequence.Element] {
return filter { sections.contains($0.section) && kinds.contains($0.kind) }
}
/// Returns indexPaths of the specified kinds, that are in the given section
///
/// - Parameters:
/// - kinds: The kinds to match against
/// - section: The section to match against
/// - Returns: An array of indexPaths, empty if no matches
func filter(include kinds: IndexPath.KindMask, inSections sections: [Int]) -> [SubSequence.Element] {
return filter { sections.contains($0.section) && kinds.contains($0.kind) }
}
/// Returns indexPaths of the specified kinds, that are in the specified sections
///
/// - Parameters:
/// - kinds: The kinds to match against
/// - range: The range of sections to match against
/// - Returns: An array of indexPaths, empty if no matches
func filter(include kinds: IndexPath.KindMask, inSections range: Range<Int>) -> [SubSequence.Element] {
return filter { range.contains($0.section) && kinds.contains($0.kind) }
}
/// Returns indexPaths of the specified kinds, that are in the specified sections
///
/// - Parameters:
/// - kinds: The kinds to match against
/// - range: The range of sections to match against
/// - Returns: An array of indexPaths, empty if no matches
func filter(include kinds: IndexPath.KindMask, inSections range: ClosedRange<Int>) -> [SubSequence.Element] {
return filter { range.contains($0.section) && kinds.contains($0.kind) }
}
/// Returns indexPaths of the specified kinds, in any section
///
/// - Parameter kinds: The kinds to match against
/// - Returns: an array of indexPaths, empty if no matches
func filter(include kinds: IndexPath.KindMask) -> [SubSequence.Element] {
return filter { kinds.contains($0.kind) }
}
/// Returns all header indexPaths only
var headers: [SubSequence.Element] {
return filter(include: .header)
}
/// Returns all footer indexPaths only
var footers: [SubSequence.Element] {
return filter(include: .footer)
}
/// Returns all item indexPaths only
var items: [SubSequence.Element] {
return filter(include: .item)
}
}
public extension Set where Element == IndexPath {
init(from view: UICollectionView) {
var indexPaths: Set<IndexPath> = []
for section in 0..<(view.dataSource?.numberOfSections?(in: view) ?? 0) {
indexPaths.insert(IndexPath(kind: .header, item: -1, section: section))
let itemCount = view.dataSource?.collectionView(view, numberOfItemsInSection: section) ?? 0
for item in 0..<itemCount {
indexPaths.insert(IndexPath(item: item, section: section))
}
indexPaths.insert(IndexPath(kind: .footer, item: itemCount, section: section))
}
self.init(indexPaths)
}
init(from view: UITableView) {
var indexPaths: Set<IndexPath> = []
for section in 0..<(view.dataSource?.numberOfSections?(in: view) ?? 0) {
indexPaths.insert(IndexPath(kind: .header, item: -1, section: section))
let itemCount = view.dataSource?.tableView(view, numberOfRowsInSection: section) ?? 0
for item in 0..<itemCount {
indexPaths.insert(IndexPath(item: item, section: section))
}
indexPaths.insert(IndexPath(kind: .footer, item: itemCount, section: section))
}
self.init(indexPaths)
}
}
@shaps80
Copy link
Author

shaps80 commented Aug 7, 2018

Example:

// provide an array of IndexPath's

let indexPaths: Set<IndexPath> = [
    IndexPath(kind: .header, item: -1, section: 0),
    IndexPath(item: 0, section: 0),
    IndexPath(item: 1, section: 0),
    IndexPath(item: 2, section: 0),
    IndexPath(item: 3, section: 0),
    IndexPath(kind: .footer, item: 4, section: 0),

    IndexPath(kind: .header, item: -1, section: 1),
    IndexPath(item: 0, section: 1),
    IndexPath(item: 1, section: 1),
    IndexPath(item: 2, section: 1),
    IndexPath(kind: .footer, item: 3, section: 1),
]

// Alternatively you can automatically fetch all indexPaths associated with a given collection or table view
let indexPaths = Set(from: tableView)

let paths = indexPaths
    .sorted()
    .filter(include: [.header, .item], inSections: 0...1)

var array = IndexPathIterator(indexPaths: Set(paths))
array.next() // [0, -1, 0]
array.next() // [0, 0]
array.next() // [0, 1]
array.next() // [0, 2]
array.next() // [0, 3]
array.next() // [1, -1, 0]
array.next() // [1, 0]
array.next() // [1, 1]
array.next() // [1, 2]
array.next() // nil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment