xcode Swift:如何使 ScrollView 与 PageControl 一起使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36174529/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Swift: how to make a ScrollView work with PageControl?
提问by SagittariusA
Strctly following thistutorial, 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:
严格按照本教程,在使用 UIScrollView 分页部分中,我刚刚实现了一个ScrollView
用作幻灯片放映,其中包含从以前的UICollectionViewController
. 加载滚动视图时,它无法正常工作,因为我看到了这些:
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.
相反,当我向后滑动图像时,它们以正确的方式显示,每页一个。或者更好的是,当我在幻灯片中获得第 4 幅图像时,这个问题就会消失,只有在那时,以下所有图像都是正确的,前一个也是如此。这是一个影响前 2 或 3 个图像的问题。
Moreoverthe 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:
此外,幻灯片甚至不是从UICollectionViewCell
用户刚刚点击开始,而是从第一个开始。你可以在这里阅读所有代码:
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
我想问题可能出在/***/
我从教程中没有理解的地方。感谢您的关注
UPDATELooking here in SO for similar posts, someone advised to create subviews in viewDidLayoutSubviews()
, so here's what I have just tried:
更新在 SO 中寻找类似的帖子,有人建议在 中创建子视图viewDidLayoutSubviews()
,所以这是我刚刚尝试过的:
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:
现在图像已正确显示,前 3 个图像不再有奇怪的效果。但为什么?我是一名初级 iOS 开发人员,我仍然不知道要覆盖的所有方法以及它们的工作顺序。无论如何,另一个仍然存在的问题是图像总是从第一个开始显示,即使点击了另一个图像。例如,看看这个:
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
更新 2
Here's the code of prepareForSegue()
from the previous UICollectionViewController
when the user taps a cell:
下面是该代码prepareForSegue()
从以前UICollectionViewController
当用户点击一个单元格:
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
}
}
采纳答案by beyowulf
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:
您应该将滚动视图的偏移量更新为等于过渡后要显示的图像的偏移量。self.scrollView.contentOffset = CGPoint(x: pagesScrollViewSize.width * CGFloat(self.firstToShow), y: 0.0)
您可以在 viewDidLayoutSubviews 中执行此操作,这将使其看起来像:
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.
您突出显示的行只是向下舍入到最近的图像索引,以确定滚动视图所在的页面。问题是当你的视图控制器显示时没有滚动,所以它总是显示第一张图像。要首先获得要显示的图像,您只需计算图像的偏移量,将滚动视图的偏移量设置为如上所示。