前言
来源:http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift
UIScrollView是iOS当中最多样和有用的控件之一。它是非常主流的UITableView的基础并且是一种非常好的方式来展现比单屏大的内容。在这篇教程当中你将学习所有关于使用这个控件:
怎么使用UIScrollView来查看一个很大的图片
在缩放中怎么保持UIScrollView的内容是居中的
怎样嵌入一个非常负载的层级进入UIScrollView
怎么样使用UIScrollView的页面特性,和UIPageControl的连接,使得滑动多页面的内容
怎么制作一个”瞥一眼“的UIScrollView,方便快速浏览之前的下页的以及当前页的内容(有点纠结。。。)
还有更多内容
这篇教程假定你已经对Swift和iOS编程有一定的熟悉程度了。如果你是一个完全的新手,你最好先学习一下其他的基础教程。
这篇教程当然也假定你已经知道了怎么使用Interface Builder来添加新的对象到view并且知道连outlets在你的代码和Storyboard(网站这里还拼错了。。)之间。在继续之前你应该要熟悉Storyboards,如果你对Storyboards和Interface Builder很陌生的话最好先浏览一下Storyboard教程。
Getting Started
这一部分基本没有很大翻译的必要,对照网站看图片一步步实现就好了,重点将在下面代码实现和分析的部分
Scrolling and Zooming a Large Image
你现在首要学习的是怎样建立一个scroll view
能让用户缩放一个图片并且各种滑动。
首先,你需要建一个view controller
。打开ViewController.swift
,然后在文件的头部改变类的定义,当然它需要遵循UIScrollViewDelegate
协议。
class ViewController: UIViewController, UIScrollViewDelegate {
在类声明内容,添加如下outlet属性
@IBOutlet var scrollView: UIScrollView!
在下一步你将会链接scroll view。
打开storyboard然后拽入一个从 objects library 中拽入View Controller。选择这个新的 view controller 然后在Identity Inspector
中,把Class
设置为ViewController
。
这个 view controller 会显示一个 image scroll demo。按住Control
键然后从 table view 中的Image Scroll
的那一行拽进这个新的 view controller。在显示的弹出目录中,在Selection Segue
下选择Push
。这样会链接它们因此当用户点击了第一个 row 的时候,这个view controller 会被压入导航栈之中。
从 object library 中拽一个Scroll View
完全铺满放入 view controller。
现在是时候来写代码了。打开ViewController.swift
然后添加一个新的属性:
var imageView: UIImageView!
这个可以持有用户滑动的图片
现在是时候来到设置 scroll view 最有趣的时候了。用以下代码来替换viewDidLoad
之中的代码。
override func viewDidLoad() {
super.viewDidLoad()
// 1
let image = UIImage(named: "photo1.png")!
imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:image.size)
scrollView.addSubview(imageView)
// 2
scrollView.contentSize = image.size
// 3
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewDoubleTapped:")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTouchesRequired = 1
scrollView.addGestureRecognizer(doubleTapRecognizer)
// 4
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleWidth, scaleHeight);
scrollView.minimumZoomScale = minScale;
// 5
scrollView.maximumZoomScale = 1.0
scrollView.zoomScale = minScale;
// 6
centerScrollViewContents()
}
这个看起来比较复杂,所以我们一步步来分析。你会看到这个并不是那么可怕。
首先,你要创建一个名为
photo1.png
的 image view。然后这里强制解包表示如果工程不能找到photo1.png
这张图片程序就会崩溃,这个可以让你更早的发现问题如果你忘记放入图片了!下一步,你要设置 image view 的 frame(它的尺寸和位置)用来把它放在父视图(0,0)位置。最后,把 image view 作为子视图放入 scroll view 当中。你必须要告诉 scroll view 内容的 size,当它知道以后就可以得出滑动的横向和纵向距离。在这个例子中,它是图片的尺寸大小。
这里你需要设置一个双击手势来放大图片。你不需要设置一个
UIPinchGestureRecognizer
来缩放,因为UIScrollView
已经自带了一个!接下来,你需要算出 scroll view 的最小缩放比例。1的缩放比例表示内容是正常尺寸,小于1的缩放比例表示内容缩小,大于1的缩放比例表示内容放大。为了得到最小的缩放比例,你要计算出图片基于它的宽度来缩小使得图片能够合适的适配你的 scroll view 的边界。然后基于高度做同样的事情。这两个结果的最小值作为 scroll view 的最小缩放比例。这样的缩放比例可以使得当你完全缩小图片的时候,你可以看到整张图片。
你设置最大缩放比例为1,因为设置过大的时候可能会导致图片模糊。你设置最初的缩放比例为最小的,这样图片开始就是最小缩放状态。
这句代码能让 scroll view 居中图片。这个方法在哪里?接下来实现它!
func centerScrollViewContents() {
let boundsSize = scrollView.bounds.size
var contentsFrame = imageView.frame
if contentsFrame.size.width < boundsSize.width {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
} else {
contentsFrame.origin.x = 0.0
}
if contentsFrame.size.height < boundsSize.height {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
} else {
contentsFrame.origin.y = 0.0
}
imageView.frame = contentsFrame
}
这个方法对于UIScrollView
有轻微的作用:如果 scroll view 的contentSize
比它的bounds
还小,那么就把它放在左上角而不是居中。因为你允许用户完全缩小图片,如果图片放在正中间将会变得非常友好。这个方法实现了把 image view 始终放置在 scroll view 的中间。
最后,把双击点击手势的方法scrollViewDoubleTapped
添加进入类。
func scrollViewDoubleTapped(recognizer: UITapGestureRecognizer) {
// 1
let pointInView = recognizer.locationInView(imageView)
// 2
var newZoomScale = scrollView.zoomScale * 1.5
newZoomScale = min(newZoomScale, scrollView.maximumZoomScale)
// 3
let scrollViewSize = scrollView.bounds.size
let w = scrollViewSize.width / newZoomScale
let h = scrollViewSize.height / newZoomScale
let x = pointInView.x - (w / 2.0)
let y = pointInView.y - (h / 2.0)
let rectToZoomTo = CGRectMake(x, y, w, h);
// 4
scrollView.zoomToRect(rectToZoomTo, animated: true)
}
这个方法当被双击的时候会触发。接下来就是一步步分析代码:
首先,你需要得到是在 image view 中的哪里点击的。你将基于这个点来放大图片,你可以想象你是一个用户。
接下来,你计算1.5倍的缩放比例,但是要和 scroll view 的最大缩放比例做一个比较,取得最小值。
然后你需要用到步骤1当中的点来计算一个
CGRect
矩形用来放大。最后,你需要告诉 scroll view 去放大,当然你需要一个动画效果来让它感觉起来不错。
获得更多关于iOS手势的信息,浏览相关教程UIGestureRecognizer tutorial
现在,还记得你设置ViewController
遵循UIScrollViewDelegate
协议吗?好的,现在你需要实现一些必须的代理方法。添加如下方法到类中。
func viewForZoomingInScrollView(scrollView: UIScrollView!) -> UIView! {
return imageView
}
这个是 scroll view 缩放的核心与灵魂。你要告诉控制器是当你捏合的时候是哪一个 view 需要被放大和缩小。所以,你需要告诉它是你的imageView
。
最后,添加这个代理方法。
func scrollViewDidZoom(scrollView: UIScrollView!) {
centerScrollViewContents()
}
这个方法会在用户结束缩放的时候调用。这里,你需要重新居中 view,如果没有,scroll view 不会展现的很自然;相反,可能会偏向左上角。
现在,深呼吸一下,调整一下自己然后编译并运行你的工程!点击Image Scroll
如果它显示的很流畅,你可以得到一个好的图片可以让你缩放,捏合和点击!
Scrolling and Zooming a View Hierarchy
如果你不仅仅是在 scroll view 上需要一张图片呢?如果你想要一个复杂的视图层级并且能够缩放和捏合呢?没问题,这里正有一个 scroll view 可以用!好的是在这之前只需要很少的步骤就可以完成了。
创建一个新的文件iOS\Source\Cocoa Touch Class
的继承模板。起个类名为CustomScrollViewController
然后设置继承于UIViewController
。确认Also create XIB file
没有被勾选并且语言设置是Swift
。点击Next
然后保存工程。
打开CustomScrollViewController.swift
然后改动如下:
import UIKit
class CustomScrollViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet var scrollView: UIScrollView!
}
下一步,打开Main.storyboard
然后,增加一个View Controller
(从 table view 的第二行用 push segue 的方式连接)。设置它的类为你刚才创建的View Controller
。
当然,你也需要增加一个Scroll View
然后连接一个outlet
到它而且还要设置 view controller 作为这个 scroll view 的代理。
然后,打开CustomScrollViewController.swift
,添加如下属性:
var containerView: UIView!
与之前的 view controller 不同的地方是不仅没有了UIImageView
,你还拥有了一个UIView
叫containerView
。这将会成为一个小的指示来表明接下来要发生的事情。
现在,在viewDidLoad
里面实现如下:
override func viewDidLoad() {
super.viewDidLoad()
// Set up the container view to hold your custom view hierarchy
let containerSize = CGSize(width: 640.0, height: 640.0)
containerView = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size:containerSize))
scrollView.addSubview(containerView)
// Set up your custom view hierarchy
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 640, height: 80))
redView.backgroundColor = UIColor.redColor();
containerView.addSubview(redView)
let blueView = UIView(frame: CGRect(x: 0, y: 560, width: 640, height: 80))
blueView.backgroundColor = UIColor.blueColor();
containerView.addSubview(blueView)
let greenView = UIView(frame: CGRect(x: 160, y: 160, width: 320, height: 320))
greenView.backgroundColor = UIColor.greenColor();
containerView.addSubview(greenView)
let imageView = UIImageView(image: UIImage(named: "slow.png"))
imageView.center = CGPoint(x: 320, y: 320);
containerView.addSubview(imageView)
// Tell the scroll view the size of the contents
scrollView.contentSize = containerSize;
// Set up the minimum & maximum zoom scales
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleWidth, scaleHeight)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 1.0
scrollView.zoomScale = 1.0
centerScrollViewContents()
}
viewDidLoad
里面设置了一个只有一个根视图的视图层级,而这个根视图就是我们的实例变量,containerView
。然后你把这个单视图放在 scroll view 上面。这就是关键-只有一个 view 被加到 scroll view 上面如果你准备缩小它,因为在代理回调viewForZoomingInScrollView
中,只能返回一个 view。你设置了zoomScale
为1,而不是minScale
所以 content view 是正常尺寸,而不是铺满屏幕。
再一次的,实现centerScrollViewContents
方法和两个UIScrollViewDelegate
方法,只需要把以前的方法复制过来,替换imageView
为containerView
。
Note:你可以意识到缺少了`UITapGestureRecognizer `。这对于这篇教程来说很简单。作为额外练习轻松就可以添加进去了。
现在编译然后运行你的工程。这一次,选择Custom View Scroll
然后看看它的精彩表现,因为你能捏合和缩放一个不错的动手编写的 UIView 场景。
Paging with UIScrollView
在教程中的第三部分,你要创建一个可以页面滑动的 scroll view。这意味着当你停止扎ui时候 scroll view 会停在一页上。在 APP Store 上面当你浏览一个 app 的快照的时候看到的效果就是这样的。
创建一个新的子类模板iOS\Source\Cocoa Touch Class
。命名为PagedScrollViewController
然后让它继承自UIViewController
。确保Also create XIB file
不被勾选,然后选择语言为Swift
。点击下一步然后保存工程。
打开PagedScrollViewController.swift
然后改成如下的代码:
import UIKit
class PagedScrollViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet var scrollView: UIScrollView!
@IBOutlet var pageControl: UIPageControl!
}
然后,去Main.storyboard
中,选择 table view 当中第三行链接到 View Controller
。然后如同之前一样,设置它的类为PagedScrollViewController
。
把主视图的背景颜色设置为黑色,因此当 page control 添加的时候能看得见。默认是白色的,白色的东西放在白色是无用的!
添加一个 Page Control
元素到 view 的下面然后设置它的宽等于屏幕的宽。然后链接 outlet 到pageControl
。
当然,也要链接Scroll View
,然后设置它的代理为 view controller,就像之前做的一样。调整Scroll View
的尺寸铺满 view controller,但是留一些空间给Page Control
。
然后在Attributes Inspector
里面勾选Paging Enabled
。
现在打开PagedScrollViewController.swift
然后添加以下属性:
var pageImages: [UIImage] = []
var pageViews: [UIImageView?] = []
现在你可能发现有些不同了。没有容器视图,只有两个数组。
pageImages
:放置所有的需要展示的图片pageViews
:放置UIImageView
的实例而它们是用来显示在它们各自的 page 上面的图片。这是个可选类型,因为你需要懒加载这些 pages 所以你可能要处理数组里面的 nil 情况。
接下来,完成viewDidLoad
:
override func viewDidLoad() {
super.viewDidLoad()
// 1
pageImages = [UIImage(named: "photo1.png")!,
UIImage(named: "photo2.png")!,
UIImage(named: "photo3.png")!,
UIImage(named: "photo4.png")!,
UIImage(named: "photo5.png")!]
let pageCount = pageImages.count
// 2
pageControl.currentPage = 0
pageControl.numberOfPages = pageCount
// 3
for _ in 0..<pageCount {
pageViews.append(nil)
}
// 4
let pagesScrollViewSize = scrollView.frame.size
scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(pageImages.count),
height: pagesScrollViewSize.height)
// 5
loadVisiblePages()
}
我们来一步一步的分析所发生的事情:
首先,设置
page images
。你需要添加五个图片进去然后这个数组来接受他们。page
的索引从0开始,所以你设置最初的page
然后告诉它有几个 pages。接下来,设置放置了
UIImageView
实例的数组。首先,没有 pages 被懒加载所以你需要放置 nil 对象进去 一一对应。接着,你需要解包 option 来检查 page 是否被载入。这个
scroll view
,如同之前的一样,需要知道它的content size
。因为你像横向滑动(如果你想的话垂直方向更容易),计算出的宽式等于 pages 的个数来乘以scroll view
的宽度。高度是和scroll view
的高度等高。初始状态需要设置一些 pages,所以执行
loadVisiblePages
。
添加如下方法:
func loadPage(page: Int) {
if page < 0 || page >= pageImages.count {
// If it's outside the range of what you have to display, then do nothing
return
}
// 1
if let pageView = pageViews[page] {
// Do nothing. The view is already loaded.
} else {
// 2
var frame = scrollView.bounds
frame.origin.x = frame.size.width * CGFloat(page)
frame.origin.y = 0.0
// 3
let newPageView = UIImageView(image: pageImages[page])
newPageView.contentMode = .ScaleAspectFit
newPageView.frame = frame
scrollView.addSubview(newPageView)
// 4
pageViews[page] = newPageView
}
}
记住每个page
都是一个存放在可选数组的UIImageView
。当view controller
载入后,数组是空的。这个方法用来载入每个页面的内容。
首先,你使用了拆包来检查是否载入了试图。如果
pageView
已经包含了UIImageView
的话那就什么都不做并忽略后面的代码。如果
pageView
是空的,就需要创建一个page
。所以首先计算出这个page
的frame
。它和scroll view
的尺寸一样,坐标y轴
的offset
为0,然后x轴
的offset
为一页的宽乘以page
的数量。创建一个新的
UIImageView
,设置一下然后加入scroll view
。最后,把刚才创建的视图放入数组来替换掉空值,因此当下次载入的时候,就不会执行
if statement
语句中,因为page
的视图已经被创建了。
接下来,添加下列方法:
func purgePage(page: Int) {
if page < 0 || page >= 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 = pageViews[page] {
pageView.removeFromSuperview()
pageViews[page] = nil
}
}
这个方法是来清楚之前通过loadPage()
方法创建的page
。首先先检查pageViews
数组中的这个page
是不是空的。如果不为空,从scroll view
当中移除这个视图 然后更新数组中的值为 nil,表现出这个page
不再存在。
为什么要使用懒加载和移除视图,你可能要问?好吧,在这个例子中,它可能在开始载入所有pages
的时候不会有问题,因为这里只有仅仅5个视图还不足以把整个内存给吃掉。想象意向如果你有100张图并且每个5MB大小。这就会吃掉500MB的内润如果一次加载所有的pages
!你的 app 会快速占用可用的内存然后被系统给杀掉。懒加载意味着你只需要加载一定数量的pages
在内存中,任何时候都可以。
上面定义的两个方法是连在一起的都是通过loadVisiblePages()
来调用的。添加以下的代码实现:
func loadVisiblePages() {
// First, determine which page is currently visible
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)))
// Update the page control
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 {
purgePage(index)
}
// Load pages in our range
for index in firstPage...lastPage {
loadPage(index)
}
// Purge anything after the last page
for var index = lastPage+1; index < pageImages.count; ++index {
purgePage(index)
}
}
这里就获得了现在正在scroll view
上的page
,更新page control
并且载入或者清除相关的pages
。计算过程可能开起来很吓人,但是这还不错。你可以通过放入一些数字在i。(注意floor()
方法会产生一些小数接近最小的整数)。
你选择载入当前的页面和它周围的页面。当一个用户开始滑动的时候,它可以看到下一张准备到中间的页面。你也可以载入之前的或者下两张或下三张的图片,但是这个会增加内容使用并且没什么用。
最后一件事情就是实现UIScrollView
的代理方法。这次你只需要实现scrollViewDidScroll()
的代理方法。添加进PagedScrollViewController.swift
:
func scrollViewDidScroll(scrollView: UIScrollView!) {
// Load the pages that are now on screen
loadVisiblePages()
}
这些做完以后就能保证当滑动的时候,相关的pages
能被载入(不必要的会被清除)。
编译并运行工程,看看scroll view
的翻页功能。
总结
UIScrollView
是一个很重要的控件,后面的UITableView
也是继承自它。所以当我们足够了解UIScrollView
以后,对于UITableView
我们才能更加方便快捷的使用。
raywenderlich的文章非常适合大家学习,因为文章都是在面向大众的教程,顺序逻辑思维,逐步带入的教学方式,很适合大家观看。
当然,这其中的翻译难免有不妥当的地方,还请大家多多的指教,有什么建议和意见在下面的评论回复,谢谢!
(最近更新时间2015-08-27)
本系列翻译属于个人兴趣,不作任何其他用途,如有任何版权信息问题,请联络本人做相关处理,谢谢!
感谢原文来源网站:www.raywenderlich.com