本文由我是喬忘記瘋狂(簡書)翻譯自AppCoda,作者:joyce echessa
原文:A Beginner’s Guide to UIScrollView
讓用戶可以通過拖拽手勢來觀看想看到的內容
讓用戶可以通過捏合手勢來放大或縮小觀看的內容
在iOS應用中常見的表格視圖(UITableView)就繼承自滾動視圖,並因此可以通過上下滾動來顯示更多的內容。
在本篇教程中,我們將討論滾動視圖的諸多方面內容,主要包括:使用純代碼和可視化編程兩種方式來創建一個滾動視圖、實現滾動和縮放功能,以及如何嵌套使用滾動視圖。
繼續閱讀之前,請先下載本文示例代碼所需的資源文件,詳見:資源文件地址。(譯注:原文資源文件地址需要FQ訪問,本人已轉存到GitHub上,詳見這裡)
使用純代碼方式創建UIScrollView
UIScrollView同其他視圖一樣,可以通過純代碼和可視化編程兩種方式來創建。在創建之後,只需要少量額外設置就可以讓UIScrollView獲得基本的滾動功能。
UIScrollView也和其他視圖一樣,應該被一個控制器管理或者添加到某個視圖層級中。想要完成滾動功能還需要對UIScrollView進行以下兩步設置:
必須設置UIScrollView的contentSize屬性,它提供了UIScrollView的內容的大小,也就是可以滾動的區域的大小。
必須為UIScrollView添加一個或多個用於顯示和滾動的子視圖,這些視圖提供了UIScrollView顯示的內容。
你還可以根據應用的具體需求設置UIScrollView的一些顯示效果,比如:是否顯示水平和豎直方向的滾動條、滾動的彈性效果、縮放的彈性效果,以及允許的滾動方向等。
接下來我們將在代碼中創建一個UIScrollView。在下載的資源文件中打開ScrollViewDemo工程。它就是一個簡單的Single View Application工程,只不過將storyboard中根控制器的類型綁定為自己新建的叫做ScrollViewController的控制器,還在項目中添加了一張我們要用到的圖片,圖片名稱為image.png。
接下來打開ScrollViewController.swift文件,添加如下代碼。
var scrollView: UIScrollView! var imageView: UIImageView!
按如下代碼所示修改viewDidLoad()方法。
override func viewDidLoad() { super.viewDidLoad() imageView = UIImageView(image: UIImage(named: "image.png")) scrollView = UIScrollView(frame: view.bounds) scrollView.backgroundColor = UIColor.blackColor() scrollView.contentSize = imageView.bounds.size scrollView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight scrollView.addSubview(imageView) view.addSubview(scrollView) }
上述代碼創建了一個UIScrollView和UIImageView,UIImageView被設置為UIScrollView的子視圖。contentSize屬性控制滾動區域的大小,我們將它設置為跟圖片的尺寸一樣大(2000×1500)。我們將滾動視圖的背景色設置為黑色,這樣圖片就像在一塊黑色幕布上滾動一樣。我們將滾動視圖的autoresizingMask屬性設置為.FlexibleWidth和.FlexibleHeight,使它能夠在設備旋轉之後自動適應新的寬度和高度。運行當前應用,你已經可以通過拖拽手勢來滾動顯示圖片了。
當你啟動應用後,你會發現圖片初始顯示區域是它左上角的部分。
這是因為滾動視圖的bounds的起點默認為(0, 0),代表了左上角。如果你想改變啟動後顯示的位置,你需要更改滾動視圖的bounds的起點。因為這種需求經常被提起,所以UIScrollView專門提供了一個屬性contentOffset用來實現這種需求。
在代碼中添加如下語句,注意添加在設置autoresizingMask語句之後。
scrollView.contentOffset = CGPoint(x: 1000, y: 450)
重新運行應用,你會發現一開始就會顯示圖片的另一部分而不是左上角。你可以通過這種方式來決定程序啟動後將要顯示的內容。
縮放
我們已經添加了一個UIScrollView,並且能夠讓用戶通過拖拽來觀看尺寸大於屏幕尺寸的內容。相當棒,但如果視圖能夠縮放的話會帶來更好的體驗。
要支持縮放功能,你必須為UIScrollView設置一個代理,而且代理必須遵守UIScrollViewDelegate協議,代理還需要實現viewForZoomingInScrollView()方法,該方法返回想要被縮放的視圖。
你還應該為縮放設置一個比例,可以通過UIScrollView的minimumZoomScale和maximumZoomScale這兩個屬性來實現,它們的默認值都是1.0。
按照如下代碼更改ScrollViewController的定義:
class ScrollViewController: UIViewController, UIScrollViewDelegate {
然後添加如下代碼:
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { return imageView }
接下來在viewDidLoad()方法的最後添加如下代碼:
scrollView.delegate = self scrollView.minimumZoomScale = 0.1 scrollView.maximumZoomScale = 4.0 scrollView.zoomScale = 1.0
在上述代碼中,我們設置了zoomScale為1.0,然後設置了縮放的最大和最小比例。在程序運行後,會按照圖片的原始尺寸顯示(因為zonmScale為1.0),當你使用捏合手勢來操作圖片時,你會發現圖片可以被縮放了。我們設置了maximumZoomScale為4.0,所以圖片最大只能放大到4倍。你也會發現,圖片放大4倍後會變得很模糊,所以接下來我們會把它的縮放比例重新設置為1.0。
從上面的圖片中我們可以發現,我們之前將minimumZoomScale設置為0.1實在是太小了,屏幕空出了很多空閒的地方。在橫屏模式下,空閒的區域看上去更大。我們希望圖片能在某一方向上能與屏幕相匹配,讓圖片既能完全顯示,又能盡量減少屏幕的空閒空間。
要達到這樣的效果,你必須通過圖片尺寸和UIScrollView的尺寸來計算最小的縮放比例。
首先在viewDidLoad()方法中刪除以下三行代碼:
scrollView.minimumZoomScale = 0.1 scrollView.maximumZoomScale = 4.0 scrollView.zoomScale = 1.0
在控制器類中添加如下方法。在方法中,我們算出圖片同UIScrollView的高度和寬度的比值,並將最小縮放比例設置為兩者中更小的那個。注意,我們已經刪除了maximumZoomScale的設置,所以它的默認值為1.0。
func setZoomScale() { let imageViewSize = imageView.bounds.size let scrollViewSize = scrollView.bounds.size let widthScale = scrollViewSize.width / imageViewSize.width let heightScale = scrollViewSize.height / imageViewSize.height scrollView.minimumZoomScale = min(widthScale, heightScale) scrollView.zoomScale = 1.0 }
在viewDidLoad()方法最後調用這個方法:
setZoomScale()
在viewWillLayoutSubviews()方法中也需要調用該方法,這樣當用戶改變屏幕方向後,圖片的尺寸仍然是正確的。
override func viewWillLayoutSubviews() { setZoomScale() }
運行程序,現在你會發現無論你縮放到多小,圖片都會完整顯示並且盡量占滿剩余的空間。
我們可以發現,圖片是被定位在屏幕左上角的,我們希望將它放在屏幕中間。
在代碼中添加如下方法。
func scrollViewDidZoom(scrollView: UIScrollView) { let imageViewSize = imageView.frame.size let scrollViewSize = scrollView.bounds.size let verticalPadding = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0 let horizontalPadding = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0 scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) }
這個方法在縮放的時候就會被調用,它會通知代理UIScrollView的縮放比例發生改變了。在上面的方法中,我們計算了圖片在滾動視圖中的內間距,從而使圖片始終在屏幕的中間。對於上、下方向的內邊距,我們首先判斷圖片視圖的高度是否小於滾動視圖的高度,如果是就將邊距設為兩者的差值的一半,否則設為0。水平間距我們采用同樣的方式計算。然後通過contentInset屬性設置所有方向的內邊距,這個屬性代表了UIScrollView的內容距離UIScrollView本身四周的距離。
運行程序,你會發現當你縮小圖片時,圖片始終保持在屏幕的中間。
通過雙擊來縮放
UIScrollView默認只支持通過捏合手勢來實現縮放效果,如果想實現通過雙擊來縮放,則需要自己做些額外的設置。
iOS人機界面指南中介紹了可以通過雙擊手勢來達到縮放的效果。使用雙擊手勢進行縮放需要一定的前提:要縮放的視圖只能在最大和最小比例兩個固定值之間來回縮放,就像蘋果官方的相冊應用一樣,當你雙擊圖片時,圖片放大至最大,當你再次雙擊時,圖片縮小至最小,或者可以通過連續的雙擊使視圖一點點達到最大,然後再次雙擊的時候,將視圖恢復為全屏顯示。但是大多數應用需要實現更靈活的雙擊縮放效果,例如地圖應用,當你雙擊時會使其放大,繼續雙擊會繼續放大,想要縮小則可以使用雙指捏合手勢來實現。
要想在你的程序中實現雙擊縮放功能,你需要監聽UIScrollView的手勢並進行處理。在我們的程序中,我們將模仿蘋果官方的相冊應用的效果,當你雙擊時放大到最大值,再次雙擊時則縮小到最小值。
在代碼中添加如下兩個方法。
func setGestureRecognizer() { let doubleTap = UITapGestureRecognizer(target: self, action: "handleDoubleTap:") doubleTap.numberOfTapsRequired = 2 scrollView.addGestureRecognizer(doubleTap) } func handleDoubleTap(recognizer: UITapGestureRecognizer) { if (scrollView.zoomScale > scrollView.minimumZoomScale) { scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) } else { scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true) } }
然後在viewDidLoad()方法最後調用上面的方法。
setGestureRecognizer()
在上面的代碼中,我們為UIScrollView添加了一個雙擊手勢的監聽,然後根據圖片當前的縮放比例,來判斷是將圖片放大或者縮小。
運行程序,你會發現已經能通過雙擊手勢來縮放圖片了。
用可視化編程方式創建UIScrollView
使用storyboard可以實現和我們上面使用代碼方式實現的同樣的功能,而且更為簡單,代碼量更少。
在Main.storyboard文件中,拖一個新的視圖控制器,並將其設置為初始控制器(既可以將箭頭拖到新控制器上,也可以在屬性選項卡中選中Is Initial View Controller復選框)。
拖一個UIScrollView到新的控制器中,然後設置其邊緣始終粘著屏幕。
然後拖一個UIImageView到剛才的UIScrollView中,將它的邊緣設置為粘著UIScrollView。
要記住UIScrollView需要知道它的內容的大小,才可以實現滾動。當你為UIImageView設置圖片時,UIScrollView的內容大小就會被自動設置為圖片的大小。
在UIImageView的屬性選項卡中,將Image屬性設置為image.png,然後通過updating the frames解決自動布局的問題。運行程序,你會發現已經實現圖片的滾動顯示了,並且沒有敲一行代碼。你還可以在UIScrollView的屬性選項卡中查看還有哪些屬性可以設置,比如可以設置最大和最小的縮放比例。
如果想要實現縮放功能,你仍然需要通過代碼,設置代理並實現viewForZoomingInScrollView()方法,同我們之前做過的一樣,就不再重復一遍了。
UIScrollView的嵌套使用
可以在一個UIScrollView中嵌套另一個UIScrollView,兩個UIScrollView既可以是相同方向滾動的,也可以是不同方向的。這部分內容的示例代碼請使用NestedScrollViews項目。
相同方向的UIScrollView嵌套
相同方向的UIScrollView嵌套是指一個UIScrollView,它有另一個UIScrollView作為子控件,並且它們的滾動方向一致。你可以用相同方向的嵌套來實現這樣的效果,比如在UIScrollView中添加多組要區分開的數據,你還可以通過它來實現兩個UIScrollView同時滾動時的視差效果。在我們的示例中,我們將兩個相同方向的UIScrollView設置不同的滾動速度,從而實現滾動時的視差效果。
打開NestedScrollViews項目中的storyboard文件,你將看到兩個UIScrollView,分別叫做foreground和background。background裡面添加了一個UIImageView,並將圖片設置為image.png,foreground裡面添加了一些標簽和一個作為容器用的UIVIew,這些標簽只是為了方便我們觀看視圖的滾動,容器視圖我們將在下一節內容中才用到。
我們的界面這樣就算搭建完成了,現在運行程序的話,你會發現只有foreground視圖在滾動,而background視圖保持不動。接下來我們將要實現background的滾動,並且實現滾動的視差效果。
首先將foreground和background兩個UIScrollView連線到控制器,之後代碼會如下所示:
@IBOutlet weak var background: UIScrollView! @IBOutlet weak var foreground: UIScrollView!
我們需要知道foreground視圖滾動了多長的距離,用來計算background視圖需要滾動多長的距離。所以我們需要為foreground視圖設置一個代理,用來監聽它的滾動。
class ViewController: UIViewController, UIScrollViewDelegate {
在viewDidLoad()方法中設置foreground視圖的代理。
foreground.delegate = self
然後實現如下代理方法。
func scrollViewDidScroll(scrollView: UIScrollView) { let foregroundHeight = foreground.contentSize.height - CGRectGetHeight(foreground.bounds) let percentageScroll = foreground.contentOffset.y / foregroundHeight let backgroundHeight = background.contentSize.height - CGRectGetHeight(background.bounds) background.contentOffset = CGPoint(x: 0, y: backgroundHeight * percentageScroll) }
在上面的代碼中,我們獲取了foreground視圖可以滾動的最大高度,然後用當前滾動的距離除以它以獲取滾動的比例,然後獲取background視圖可以滾動的最大高度,將其乘以滾動比例,就可以得到background應該滾動的距離。運行你的程序,在進行滾動時你會發現foreground和background兩個視圖都在滾動,並且background視圖滾動的更快,從而有一種視差效果。
交叉方向的UIScrollView嵌套
交叉方向的UIScrollView嵌套是指一個UIScrollView,它有另一個UIScrollView作為子控件,並且它們的滾動方向正好相差90°,接下來我們就演示一下這種情況。
在NestedScrollViews項目中,你會發現在foreground裡面有一個Container View,我們將用它來設置我們水平滾動的UIScrollView。
在storyboard中新拖入一個控制器,按住Control鍵從Container View拖到新的控制器,選擇embed方式。然後選中這個控制器,將它的Size選項設為Freeform並將其高度設為128,因為Container View的高度就是128。
往新控制器中拖入一個UIScrollView,設置其邊緣始終粘著父控件。然後在UIScrollView中拖入一個70×70的UIView,將其背景色設為灰色方便我們觀看,然後復制多個,從左到右依次擺放在UIScrollView中。你不需要精確地去設置每一個UIView的位置,接下來我會教你們怎麼去做。現在我們的控制器界面應該是這個樣子。
選擇最左邊的UIView,添加它上邊和左邊的約束,再添加寬度和高度約束。
再選擇最右邊的UIView,添加它的上邊、右邊、寬度和高度約束。
接下來,選中我們的UIScrollView,然後點擊上面菜單欄中的Editor > Resolve Auto Layout Issues > All Views > Add Missing Constraints。這樣我們所有的UIView就都添加好了約束。運行你的程序,豎直滾動到底部,你會看見我們的Container View,你可以水平滾動它裡面的內容。下圖中,我將控制器自身視圖的背景色設置為透明,所以你看到的效果就是這樣的。
我們的教程這就結束了,並沒有包含UIScrollView所有的方方面面,但我希望通過這篇教程可以讓你對UIScrollView有初步的了解,更多UIScrollView的知識,你可以查看蘋果的官方文檔:Scroll View Programming Guide。
你可以在這裡下載完整的示例程序作為學習參考。(譯注:原文資源文件地址需要FQ訪問,本人已轉存到GitHub上,詳見這裡)