|
import UIKit |
|
|
|
protocol EfficientScrollViewDataSource: class { |
|
func numberOfPages() -> Int |
|
func onBind(view: UIView?, index: Int) -> UIView |
|
} |
|
|
|
class EfficientScrollView: UIScrollView { |
|
|
|
weak var dataSource: EfficientScrollViewDataSource? |
|
private var pages: [UIView] = [] |
|
private var totalItemCount: Int = 0 |
|
var onPageChanged: ((Int, UIView?) -> Void)? |
|
|
|
var currentPage: Int = -1 |
|
var vertical = true |
|
|
|
override func didMoveToSuperview() { |
|
super.didMoveToSuperview() |
|
if superview == nil { |
|
removeObserver(self, forKeyPath: "contentOffset", context: nil) |
|
} else { |
|
addObserver(self, forKeyPath: "contentOffset", options: [NSKeyValueObservingOptions.new], context: nil) |
|
} |
|
} |
|
|
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { |
|
var page = 0 |
|
if vertical { |
|
page = height == 0 ? 0 : Int(round(contentOffset.y / height)) |
|
} else { |
|
page = width == 0 ? 0 : Int(round(contentOffset.x / width)) |
|
} |
|
|
|
if page != currentPage { |
|
currentPage = page |
|
load(page: page) |
|
load(page: page - 1) |
|
load(page: page + 1) |
|
onPageChanged?(currentPage, pages.first(where: { $0.tag == page })) |
|
} |
|
|
|
} |
|
|
|
private func load(page: Int, force: Bool = false) { |
|
if page < 0 || page >= totalItemCount { return } |
|
if let view = pages.first(where: { $0.tag == page }) { |
|
if force { |
|
if let v = dataSource?.onBind(view: view, index: page) { |
|
if v.superview == nil { addSubview(v) } |
|
} |
|
} |
|
} else { |
|
if let view = pages.first(where: { $0.tag < currentPage - 1 || $0.tag > currentPage + 1 }) { |
|
view.tag = page |
|
if let v = dataSource?.onBind(view: view, index: page) { |
|
if v.superview == nil { addSubview(v) } |
|
} |
|
} else { |
|
if let view = dataSource?.onBind(view: nil, index: page) { |
|
view.tag = page |
|
addSubview(view) |
|
pages.append(view) |
|
} |
|
} |
|
} |
|
//print("pages size: \(pages.count)") |
|
reloadContentSizeAndPosition() |
|
} |
|
|
|
func reloadAllData() { |
|
totalItemCount = dataSource?.numberOfPages() ?? 0 |
|
|
|
//if totalItemCount < 3 { |
|
pages.forEach({ $0.tag = -3; $0.removeFromSuperview() }) |
|
//} |
|
|
|
if totalItemCount > 0 { |
|
if currentPage == -1 { |
|
currentPage = 0 |
|
} |
|
load(page: currentPage - 1, force: true) |
|
load(page: currentPage, force: true) |
|
load(page: currentPage + 1, force: true) |
|
} else { |
|
currentPage = -1 |
|
} |
|
} |
|
|
|
// MARK: size fixing |
|
private var height: CGFloat = 0 |
|
private var width: CGFloat = 0 |
|
|
|
override func layoutSubviews() { |
|
super.layoutSubviews() |
|
reloadContentSizeAndPosition() |
|
} |
|
|
|
private func reloadContentSizeAndPosition() { |
|
height = bounds.height |
|
width = bounds.width |
|
contentSize = vertical ? CGSize(width: width, height: CGFloat(totalItemCount) * height) : |
|
CGSize(width: CGFloat(totalItemCount) * width, height: height) |
|
pages.forEach { |
|
if tag >= 0 && tag < totalItemCount { |
|
$0.frame = vertical ? CGRect(x: 0, y: CGFloat($0.tag) * height, width: width, height: height) : |
|
CGRect(x: CGFloat($0.tag) * width, y: 0, width: width, height: height) |
|
} |
|
} |
|
} |
|
|
|
} |
Hi Chandan,
I adapted your code but I have a problem. I am adding new rows to my datasource like this:
If I don't call reloadAllData() then the new posts are not picked up (total is still 6 instead of 12)
If I call reloadAllData() then my views go blank except for the very first page in the datasource. I can still swipe through all 12, but only the first one shows up visually. Any ideas?
-- Steve in Canada