Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
612 views
in Technique[技术] by (71.8m points)

ios - Swift: how to make a ScrollView work with PageControl?

Strctly following this tutorial, in section Paging with UIScrollView, I have just implemented a ScrollView to use as a slideshow with downloaded photos from a previous UICollectionViewController. When scroll view is loaded, it does not work well because I see these ones:

enter image description here enter image description here enter image description here

Instead, when I slide back the images they are displayed in the correct way, one for each page. Or better, this problem disappears when I get the 4-th image in the slideshow, and only at that point all the following ones are correct, and so are the previous too. It is a problem which affects the first 2 or 3 images.

Moreover the slideshow does not even start from the UICollectionViewCell the user has just tapped, but always from the first. You can read all the code here:

import Foundation
import UIKit

class PagedScrollViewController: UIViewController, UIScrollViewDelegate {

@IBOutlet var scrollView: UIScrollView!
@IBOutlet var pageControl: UIPageControl!

/* This will hold all the images to display – 1 per page. 
   It must be set from the previous view controller in prepareforsegue() method: 
   it will be the array of downloaded images for that photo gallery */
var pageImages:[UIImage]!

/* position in array images of the first to be showed, i.e. the one the user has just tapped */
var firstToShow:Int!

var currentImageViewForZoom:UIImageView?

/* This will hold instances of UIImageView to display each image on its respective page. 
   It’s an array of optionals, because you’ll be loading the pages lazily (i.e. as and when you need them) 
   so you need to be able to handle nil values from the array. */
var pageViews:[UIImageView?] = []

override func viewDidLoad() {
    super.viewDidLoad()

    self.scrollView.delegate = self
    self.scrollView.maximumZoomScale = 1.0
    self.scrollView.zoomScale = 10.0

    self.pageControl.numberOfPages = self.pageImages.count
    self.pageControl.currentPage = self.firstToShow

    for _ in 0..<self.pageImages.count {
        self.pageViews.append(nil)
    }

    /* The scroll view, as before, needs to know its content size. 
       Since you want a horizontal paging scroll view, you calculate the width to be the number of pages multiplied by the width of the scroll view. 
       The height of the content is the same as the height of the scroll view
    */
    let pagesScrollViewSize = self.scrollView.frame.size
    self.scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(self.pageImages.count),
        height: pagesScrollViewSize.height)

    // You’re going to need some pages shown initially, so you call loadVisiblePages()
    self.loadVisiblePages()

}

func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
    return self.currentImageViewForZoom
}

func scrollViewDidScroll(scrollView: UIScrollView) {
    self.loadVisiblePages()
}

/*
Remember each page is a UIImageView stored in an array of optionals. 
When the view controller loads, the array is filled with nil. 
This method will load the content of each page.

1 - If it's outside the range of what you have to display, then do nothing

2 - If pageView is nil, then you need to create a page. So first, work out the frame for this page. 
    It’s calculated as being the same size as the scroll view, positioned at zero y offset, 
    and then offset by the width of a page multiplied by the page number in the x (horizontal) direction.

3 - Finally, you replace the nil in the pageViews array with the view you’ve just created, 
    so that if this page was asked to load again, you would now not go into the if statement and instead do nothing, 
    since the view for the page has already been created

*/
func loadPage(page: Int) {
    if page < 0 || page >= self.pageImages.count {
        //1
        return
    }

    //2
    if let _ = self.pageViews[page] {/*Do nothing. The view is already loaded*/}
    else {
        // 2
        var frame = self.scrollView.bounds
        frame.origin.x = frame.size.width * CGFloat(page)
        frame.origin.y = 0.0

        let newPageView = UIImageView(image: self.pageImages[page])
        newPageView.contentMode = .ScaleAspectFit
        newPageView.frame = frame
        self.scrollView.addSubview(newPageView)

        // 3
        self.pageViews[page] = newPageView
        self.currentImageViewForZoom = newPageView
    }
}

/*
This function purges a page that was previously created via loadPage(). 
It first checks that the object in the pageViews array for this page is not nil. 
If it’s not, it removes the view from the scroll view and updates the pageViews array with nil again to indicate that this page is no longer there.
Why bother lazy loading and purging pages, you ask? 
Well, in this example, it won’t matter too much if you load all the pages at the start, since there are only five and they won’t be large enough to eat up too much memory. 
But imagine you had 100 pages and each image was 5MB in size. That would take up 500MB of memory if you loaded all the pages at once! 
Your app would quickly exceed the amount of memory available and be killed by the operating system. 
Lazy loading means that you’ll only have a certain number of pages in memory at any given time.
*/
func purgePage(page: Int) {
    if page < 0 || page >= self.pageImages.count {
        // If it's outside the range of what you have to display, then do nothing
        return
    }

    // Remove a page from the scroll view and reset the container array
    if let pageView = self.pageViews[page] {
        pageView.removeFromSuperview()
        self.pageViews[page] = nil
    }
}

func loadVisiblePages() {
    // First, determine which page is currently visible
    let pageWidth = self.scrollView.frame.size.width

    // floor() function will round a decimal number to the next lowest integer
    let page = Int(floor((self.scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0))) /***/

    // Update the page control
    self.pageControl.currentPage = page

    // Work out which pages you want to load
    let firstPage = page - 1
    let lastPage = page + 1

    // Purge anything before the first page
    for var index = 0; index < firstPage; ++index {
        self.purgePage(index)
    }

    // Load pages in our range
    for index in firstPage...lastPage {
        self.loadPage(index)
    }

    // Purge anything after the last page
    for var index = lastPage+1; index < self.pageImages.count; ++index {
        self.purgePage(index)
    }
}
}

I guess the problem could be the line with /***/ which is something I have not understood from the tutorial. Thank you for your attention

UPDATE Looking here in SO for similar posts, someone advised to create subviews in viewDidLayoutSubviews(), so here's what I have just tried:

override func viewDidLayoutSubviews() {
    self.loadVisiblePages()
}

and now the images are correctly displayed and there's no more that strange effect for the first 3 images. But why? I am a junior iOS developer and I still don't know all those methods to override and the order in which they work. Anyway, the other problem which persists is that images are showed always from the first, even if another image is tapped. For example, have a look at this:

enter image description here

always the first image (left up corner) is displayed even if another one is tapped. And finally, in my code I implemented the delegate method to have zoom but it does not work too.

UPDATE 2

Here's the code of prepareForSegue() from the previous UICollectionViewController when the user taps a cell:

 override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "toSlideShow") {
        let pagedScrollViewController:PagedScrollViewController = segue.destinationViewController as! PagedScrollViewController
        pagedScrollViewController.pageImages = self.imagesDownloaded
        pagedScrollViewController.firstToShow = self.collectionView?.indexPathsForSelectedItems()![0].row
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You should update the scroll view's offset to the be equal to the offset of the image you want to be showing after you transition. self.scrollView.contentOffset = CGPoint(x: pagesScrollViewSize.width * CGFloat(self.firstToShow), y: 0.0) You can do this in viewDidLayoutSubviews, which would make it look like:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        self.scrollView.delegate = self
        self.scrollView.maximumZoomScale = 2.0
        self.scrollView.zoomScale = 1.0
        self.scrollView.minimumZoomScale = 0.5

        self.pageControl.numberOfPages = self.pageImages.count
        self.pageControl.currentPage = self.firstToShow

        for _ in 0..<self.pageImages.count {
            self.pageViews.append(nil)
        }

        /* The scroll view, as before, needs to know its content size.
        Since you want a horizontal paging scroll view, you calculate the width to be the number of pages multiplied by the width of the scroll view.
        The height of the content is the same as the height of the scroll view
        */
        let pagesScrollViewSize = self.scrollView.frame.size
        self.scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(self.pageImages.count),
            height: pagesScrollViewSize.height)

        self.scrollView.contentOffset = CGPoint(x: pagesScrollViewSize.width * CGFloat(self.firstToShow), y: 0.0)

        // You’re going to need some pages shown initially, so you call loadVisiblePages()
        self.loadVisiblePages()

    }

The line you highlighted is just rounding down to the closest image index to determine what page the scroll view is on. The problem is when your view controller is displayed no scrolling has been done so it will always show the first image. To get the image you want to show first, you can just calculate what the offset should be for the image set your scroll view's offset to that as shown above.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...