Skip to content

Instantly share code, notes, and snippets.

@brocoo
Created April 19, 2018 10:31
Show Gist options
  • Save brocoo/2ec7006f5592bb8c9f6e3a4f505f51f6 to your computer and use it in GitHub Desktop.
Save brocoo/2ec7006f5592bb8c9f6e3a4f505f51f6 to your computer and use it in GitHub Desktop.
Generic data source storing pages of content, providing an efficient way to handle pagination.
import Foundation
// MARK: -
public struct PaginatedList<T: Decodable> {
// MARK: - Action
enum Mutation {
case created
case newPageAdded(Int)
}
// MARK: - Properties
private var contents: [T]
private var pages: [CountableRange<Int>]
private(set) var lastContentUpdate: Mutation
private(set) var hasMoreContent: Bool
// MARK: - Static properties
static var empty: PaginatedList { return PaginatedList() }
// MARK: - Initializer
private init() {
pages = []
contents = []
lastContentUpdate = .created
hasMoreContent = false
}
public init(with page: Page<T>) throws {
guard page.pageIndex == 0 else { throw PaginatedListError.initialPageIndexNotZero(page.pageIndex) }
contents = page.asArray
pages = [(0..<contents.endIndex)]
lastContentUpdate = .created
hasMoreContent = page.hasNextPage
}
// MARK: - Helper functions / properties
public mutating func append(_ page: Page<T>) throws {
let nextPageIndex = pages.count
if nextPageIndex > 0 {
guard nextPageIndex == page.pageIndex else { throw PaginatedListError.wrongNextPageIndex(page.pageIndex, expected: nextPageIndex) }
let startIndex = contents.endIndex
contents.append(contentsOf: page.asArray)
pages.append((startIndex..<contents.endIndex))
lastContentUpdate = .newPageAdded(nextPageIndex)
hasMoreContent = page.hasNextPage
} else {
self = try PaginatedList(with: page)
}
}
public func appending(_ page: Page<T>) throws -> PaginatedList<T> {
var newPagedList = self
try newPagedList.append(page)
return newPagedList
}
public func contents(atPage i: Int) -> [T] {
return Array(contents[pages[i]])
}
public func indexes(forPage i: Int) -> CountableRange<Int> {
return pages[i]
}
}
// MARK: -
extension PaginatedList: RandomAccessCollection {
// MARK: - RandomAccessCollection
public var startIndex: Int {
return contents.startIndex
}
public var endIndex: Int {
return contents.endIndex
}
public func index(after i: Int) -> Int {
return contents.index(after: i)
}
public subscript(index: Int) -> T {
return contents[index]
}
}
// MARK: -
enum PaginatedListError: Error {
// MARK: - Errors
case initialPageIndexNotZero(Int)
case wrongNextPageIndex(Int, expected: Int)
}
// MARK: -
extension Error {
// MARK: -
var isPaginationRelated: Bool {
return self is PaginatedListError
}
}
// MARK: -
public struct Page<T: Decodable> {
// MARK: - Properties
let pageIndex: Int
let totalResults: Int
let totalPages: Int
private let results: [T]
// MARK: - Computed properties
var nextPageIndex: Int { return pageIndex + 1 }
var hasNextPage: Bool { return nextPageIndex < totalPages }
var asArray: [T] { return results }
// MARK: - Initializer
init(pageIndex: Int, totalResults: Int, totalPages: Int, results: [T]) {
self.pageIndex = pageIndex
self.totalResults = totalResults
self.totalPages = totalPages
self.results = results
}
}
// MARK: -
extension Page: RandomAccessCollection {
// MARK: - RandomAccessCollection
public var startIndex: Int {
return results.startIndex
}
public var endIndex: Int {
return results.endIndex
}
public func index(after i: Int) -> Int {
return results.index(after: i)
}
public subscript(index: Int) -> T {
return results[index]
}
}
// MARK: -
extension Page: Decodable {
// MARK: - Keys
private enum CodingKeys: String, CodingKey {
case page
case totalResults = "total_results"
case totalPages = "total_pages"
case results
}
// MARK: - Initializer
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let pageIndex = try container.decode(Int.self, forKey: .page) - 1
let totalResults = try container.decode(Int.self, forKey: .totalResults)
let totalPages = try container.decode(Int.self, forKey: .totalPages)
let results = try container.decode([T].self, forKey: .results)
self.init(pageIndex: pageIndex, totalResults: totalResults, totalPages: totalPages, results: results)
}
}
@brocoo
Copy link
Author

brocoo commented Oct 21, 2018

Sure!
Please check out my repo: WhatFilm, I use it in there.

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