0

I would like to create a UIScrollView that cycles between 3 paging views infinitely while I change the subviews to hold different reusable view controllers giving the illusion of infinitely many screen while only allocating the resources for 3 screens at a time similar to a UICollectionView.

Below is an attempt I've made at implementing such a view based on this answer Infinite UIScrollView however my project has not been working as expected.

Ive been trying to use 3 views as the basis for infinite scrolling and many labels as the subviews to be added and removed as the user scrolls.

import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {

    var scrollView:UIScrollView = {
        let scrollView:UIScrollView = UIScrollView()
        scrollView.backgroundColor = UIColor.orange
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()

    var labels:[CustomLabel] = [CustomLabel]()

    var pages:[Page] = [Page]()

    var visiblePages:Set<Page>!{
        didSet{
            print("visible pages count: \(recycledPages.count)")
        }
    }
    var recycledPages:Set<Page>!{
        didSet{
            print("recycled pages count: \(recycledPages.count)")
        }
    }

    var visibleLabels:Set<CustomLabel>!
    var recycledLabels:Set<CustomLabel>!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        recycledPages = Set<Page>()
        visiblePages = Set<Page>()

        recycledLabels = Set<CustomLabel>()
        visibleLabels = Set<CustomLabel>()


        scrollView.contentSize = CGSize(width: view.bounds.width * 3, height: view.bounds.height)
        scrollView.delegate = self
        scrollView.isPagingEnabled = true
        scrollView.indicatorStyle = .white
        view.addSubview(scrollView)


        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
            ])

        configureLabels()
        setUpPages()

        for i in 0..<pages.count{
            scrollView.addSubview(pages[i])
        }
    }

    func setUpPages(){
        let page1 = Page(),page2 = Page(),page3 = Page()

        page1.backgroundColor = .red
        page2.backgroundColor = .green
        page3.backgroundColor = .blue

        page1.index = 0
        page2.index = 1
        page3.index = 2

        pages = [page1,page2,page3]
        visiblePages = [page1,page2,page3]
        setContentViewFrames()
    }

    func configureLabels(){
        let label1 = CustomLabel(), label2 = CustomLabel(), label3 = CustomLabel()

        label1.text = "label1"
        label2.text = "label2"
        label3.text = "label3"

        label1.backgroundColor = .red
        label2.backgroundColor = .green
        label3.backgroundColor = .blue

        labels = [label1,label2,label3]
        setContentViewFrames()
    }



    //    func dequeueRecycledPage()->CustomLabel?{
    //        let page = recycledPages.first
    //        if let page = page{
    //            recycledPages.remove(page)
    //            return page
    //        }
    //        return nil
    //    }

    var currentIndex = 0
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if (scrollView.contentOffset.x < 0) {
            let newOffset = CGPoint(x: scrollView.bounds.width + scrollView.contentOffset.x, y: 0)
            scrollView.contentOffset.x = newOffset.x
            rotateViewsRight()
        }else if(scrollView.contentOffset.x > scrollView.bounds.size.width*2){
            let newOffset = CGPoint(x: scrollView.contentOffset.x - scrollView.bounds.width, y: 0)
            scrollView.contentOffset.x = newOffset.x
            rotateViewsLeft()
        }
        print("current index: \(currentIndex)")
    }

    lazy var previousOffset:CGPoint = CGPoint()

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        print("currentIndex called")

        if previousOffset.x < scrollView.contentOffset.x{
            currentIndex += 1
        }else if previousOffset.x > scrollView.contentOffset.x{
            currentIndex  -= 1
        }

        previousOffset = scrollView.contentOffset
    }

    func rotateViewsRight(){
        let endView = pages.removeLast()
        pages.insert(endView, at: 0)
        setContentViewFrames()
    }

    func rotateViewsLeft(){
        let endView = pages.removeFirst()
        pages.append(endView)
        setContentViewFrames()
    }

    func setContentViewFrames(){
        for i in 0..<pages.count{
    //            let adjustedIndex = i % pages.count
    //            let view = pages[i]
    //            view.frame = CGRect(origin: CGPoint(x: view.bounds.width * CGFloat(i), y: 0), size: view.bounds.size)
            pages[i].frame = CGRect(x: CGFloat(i)*view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height)
            let label = labels[currentIndex%labels.count]
            label.text = "current label: \(currentIndex)"
            label.frame = pages[i].frame
            pages[i].addSubview(label)

        }

    }

    func configurePage(forIndex index:Int){

    }


    func dequeueRecycledPage()->Page?{
        let page = recycledPages.first
        if let page = page{
            recycledPages.remove(page)
            return page
        }
        return nil
    }
}
Ben
  • 1,414
  • 13
  • 23
  • one approach could be just use collectionview paging enabled, since collectionview is embedded in scrollview, scrollview uses dequereusable cell. – Mohammad Yunus Apr 17 '19 at 07:16
  • *"based on this answer **Infinite UIScrollView** however my project has not been working as expected"* --- you need to explain what is "not working." – DonMag Apr 17 '19 at 12:06
  • @DonMag I apologize for not providing more details. Primarily the screen swipes back and the view is removed creating a flash effect for the user as I add and remove views. My other concern is how to avoid having all the views initialized even when they are not in user. I would like to use a similar effect to tiling but Im not sure how to implement it. – TheRedCamaro3.0 3.0 Apr 18 '19 at 01:30
  • @TheRedCamaro3.03.0 - I'd suggest you look at `UIPageViewController` and `UICollectionView` – DonMag Apr 18 '19 at 12:06
  • @DonMag could a UICollectionView be used to contain the views of UIViewControllers that contain resource intensive code including network calls, UICollectionViewControllers and large images. – TheRedCamaro3.0 3.0 Apr 18 '19 at 16:00
  • @TheRedCamaro3.03.0 - sure... although, based on your initial description: *"cycles between 3 paging views infinitely"* it sounds like exactly what `UIPageViewController` is designed for. – DonMag Apr 18 '19 at 16:03
  • @DonMag A UIPageViewController presented several problems with reusability of ViewControllers and animations of icons while swiping from page to page. When using reusable views it became difficult to detect the current page number a user was on and also which way the user was swiping. – TheRedCamaro3.0 3.0 Apr 18 '19 at 16:10
  • @TheRedCamaro3.03.0 - I expect you'd run into similar issues and far more difficulty trying to use a collection view. You initially said *"reusable view controllers giving the illusion of infinitely many screen while only allocating the resources for 3 screens at a time"* --- well, that is pretty much **exactly** what `UIPageViewController` gives you, with all the management of it being internal. Lots of articles and discussions out there on how to reliably track "current page" and "swipe direction". – DonMag Apr 18 '19 at 16:26
  • @DonMag Most of the techniques I've seen involve initializing viewcontrollers from an array and using the index to track a users progress thus maintaining the order of the viewcontrollers however while implementing reusable viewcontrollers of a similar nature to this https://stackoverflow.com/a/36876103/9367155 maintaining the order of view controllers and tracking user paging becomes much more difficult. Are there any resources you could recommend that could combine the best of both approaches. – TheRedCamaro3.0 3.0 Apr 18 '19 at 19:09
  • @TheRedCamaro3.03.0 - generally, keeping track of the "page index" is done with a combination of `willTransitionToViewControllers` and `didFinishAnimating`. Here's one article that may help: http://ceduliocezar.com/2016/05/19/swift-ios-uipageviewcontroller-how-to-keep-track-of-current-page/ – DonMag Apr 18 '19 at 19:23

0 Answers0