UIScrollViewj儘管繼承於UIView,但它是一個相對比較特殊的視圖,特別是當它遇到了AutoLayout以後。在UIScrollView中使用AutoLayout的目的除了使用相對約束肯定子控件的位置和大小外,更重要的是如何自動計算出UIScrollView的contentSize(關於使用UIScrollView而且最終手動指定contentSize的AutoLayout用法再也不今天討論之列,嚴格意義上來講這也不是一種真正的UIScrollView的AutoLayout應用)。html
所謂UIScrollView的特殊之處就在於當它遇到了AutoLayout以後其contentSize的計算規則有些特殊。首先contentSize是根據子視圖的leading/trailing/top/bottom進行肯定的,而子視圖的位置約束又必須依賴於UIScrollView來肯定。這就有點相似於前面UICollectionView自適應高度文章中提到的:UICollectionViewCell的大小計算就是計算contentView的大小,而contentView的大小計算依賴於子視圖的leading/trailing/top/bottom,子視圖的位置約束又依賴於contentView,此時只要子視圖存在固有尺寸(intrinsicContentSize)或者指定了尺寸又設置了leading/trailing/top/bottom,AutoLayout佈局引擎便可計算出contentView的大小。
再回到AutoLayout,其實它的contentSize計算原理和UICollectionViewCell自適應非常相似,只是UIScrollView內部並無一個contentView的東西(可是能夠想象其存在,方便後面的理解,不過要清楚UIScrollView滾動的本質並不是包含一個contentView而是經過bounds和frame座標體系轉換來實現的),只要設置子視圖的leading/trailing/top/bottom(一般是經過edges=0讓子視圖上下左右間距都爲0保證整個視圖都在UIScrollView可視範圍以內),而後經過設置size(width/height)約束肯定子視圖大小進而由AutoLayout反向計算出UIScrollView的contentSize。
假設A是UIScrollView(藍色)、B是子視圖1(綠色)、C是子視圖2(綠色)、D是contentSize的計算區域(灰色,事實上它不存在),要想讓cotentSize能夠自動計算只須要肯定B、C上下左右佈局間距,而後再指定B、C間距和尺寸以後AutLayout既能夠自動推斷出contentSize的大小,原理以下圖(下圖佈局相似於下面Demo3):git
對於單個子視圖佈局比較簡單,只要設置leading/trailing/top/bottom,再設置子視圖的size(width/height)便可,固然若是子視圖存在固有尺寸而且想要使用固有尺寸的話,則這一步也能夠省略。例以下面demo中演示了一個UIScrollView包含一個UIImageView子視圖的圖片查看界面。在下面的佈局中僅僅設置了UIImageView上下左右邊距,而UIImageView存在固有尺寸,所以整個佈局就至關簡單了(AutoLayout佈局使用SnapKit庫)。github
class ImageViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(self.scrollView) self.scrollView.addSubview(self.imageView) self.scrollView.snp.makeConstraints { (make) in make.edges.equalTo(0.0) } self.imageView.snp.makeConstraints { (make) in // 下面的約束用於肯定contentSize的邊距約束(leading/trailing/top/bottom) // 而因爲UIImageView和UILabel、UIButton同樣存在固有尺寸(intrinsicContentSize),所以不須要其餘size約束就能夠計算出contentSize大小 make.edges.equalTo(0.0) } } // MARK: - 私有屬性 private lazy var scrollView:UIScrollView = { let temp = UIScrollView() return temp }() private lazy var imageView:UIImageView = { let image = UIImage(named: "img") let temp = UIImageView(image:image) return temp }() }
不少UIScrollView的AutoLayout的佈局文章中都會提到使用一個容器視圖包含多個子視圖,而後分別完成子視圖佈局和容器視圖在UIScrollView中的佈局,以此來簡化佈局過程。下面的Demo中演示了一個圖片分頁查看的佈局狀況,containerView做爲容器佈局時設置上下左右間距,而後設置其高度等於UIScrollView高度(由於要實現左右滾動),而此時並不須要設置寬度,由於寬度的計算依賴於子視圖。在containerView的子視圖中只要設置子視圖與containerView的邊距及各自間距和寬度,以後AutoLayout就能夠計算出containerView的寬度。如此一來containerView已經設置完了四周間距和尺寸就能夠計算出contentSize。swift
class SlideViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false self.view.addSubview(self.scrollView) self.scrollView.addSubview(self.containerView) self.containerView.addSubview(self.firstImageView) self.containerView.addSubview(self.secondImageView) self.containerView.addSubview(self.thirthImageView) self.scrollView.snp.makeConstraints { (make) in make.top.equalTo(self.topLayoutGuide.snp.bottom) make.left.bottom.right.equalTo(0.0) } // 下面的約束肯定了containerView的高度,至關於contentSize.height已經肯定,width經過cotnentView的子視圖肯定便可 self.containerView.snp.makeConstraints { (make) in make.edges.equalTo(0.0) make.height.equalTo(self.scrollView.snp.height) } self.firstImageView.snp.makeConstraints { (make) in make.top.left.bottom.equalTo(0.0) make.width.equalTo(self.scrollView.snp.width) } self.secondImageView.snp.makeConstraints { (make) in make.top.bottom.equalTo(0.0) make.left.equalTo(self.firstImageView.snp.right) make.width.equalTo(self.scrollView.snp.width) } self.thirthImageView.snp.makeConstraints { (make) in make.top.bottom.equalTo(0.0) make.left.equalTo(self.secondImageView.snp.right) make.width.equalTo(self.scrollView.snp.width) make.right.equalTo(0.0) // 肯定右邊距 } } // MARK: - 私有屬性 private lazy var scrollView:UIScrollView = { let temp = UIScrollView() temp.isPagingEnabled = true return temp }() private lazy var containerView:UIView = { let temp = UIView() return temp }() private lazy var firstImageView:UIImageView = { let image = UIImage(named: "1") let temp = UIImageView(image:image) temp.contentMode = .scaleAspectFill temp.clipsToBounds = true return temp }() private lazy var secondImageView:UIImageView = { let image = UIImage(named: "2") let temp = UIImageView(image:image) temp.contentMode = .scaleAspectFill temp.clipsToBounds = true return temp }() private lazy var thirthImageView:UIImageView = { let image = UIImage(named: "3") let temp = UIImageView(image:image) temp.contentMode = .scaleAspectFill temp.clipsToBounds = true return temp }() }
demo2的containerView包含多個子視圖的佈局方式相對來講好像使用要多一些,可是其實佈局原理並無任何變化,若是熟悉了UIScrollView的AutoLayout佈局原理,用不用containerView你們能夠根據狀況自行決定,若是僅僅是簡單的幾個子視圖佈局沒有特殊的需求那麼直接佈局可能會更簡單,可是若是子視圖相對較多而且可能全部子視圖有公共的操做需求(例如全部子視圖在鍵盤彈出後須要改變其位置)則更適合使用containerView佈局。下面代碼中去掉containerView完成demo2的需求,原理相同,代碼也不難理解。ide
class SlideViewController2: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false self.view.addSubview(self.scrollView) self.scrollView.addSubview(self.firstImageView) self.scrollView.addSubview(self.secondImageView) self.scrollView.addSubview(self.thirthImageView) self.scrollView.snp.makeConstraints { (make) in make.top.equalTo(self.topLayoutGuide.snp.bottom) make.left.bottom.right.equalTo(0.0) } self.firstImageView.snp.makeConstraints { (make) in make.top.left.bottom.equalTo(0.0) make.size.equalTo(self.scrollView.snp.size) } self.secondImageView.snp.makeConstraints { (make) in make.top.bottom.equalTo(0.0) make.left.equalTo(self.firstImageView.snp.right) make.size.equalTo(self.scrollView.snp.size) } self.thirthImageView.snp.makeConstraints { (make) in make.top.bottom.equalTo(0.0) make.left.equalTo(self.secondImageView.snp.right) make.size.equalTo(self.scrollView.snp.size) make.right.equalTo(0.0) // 肯定右邊距 } } // MARK: - 私有屬性 private lazy var scrollView:UIScrollView = { let temp = UIScrollView() temp.isPagingEnabled = true return temp }() private lazy var firstImageView:UIImageView = { let image = UIImage(named: "1") let temp = UIImageView(image:image) temp.contentMode = .scaleAspectFill temp.clipsToBounds = true return temp }() private lazy var secondImageView:UIImageView = { let image = UIImage(named: "2") let temp = UIImageView(image:image) temp.contentMode = .scaleAspectFill temp.clipsToBounds = true return temp }() private lazy var thirthImageView:UIImageView = { let image = UIImage(named: "3") let temp = UIImageView(image:image) temp.contentMode = .scaleAspectFill temp.clipsToBounds = true return temp }() }
其實歸納起來UIScrollView的佈局最主要的問題就是解決contentSize的計算問題。而根據UIScrollView的特色contentSize的計算最終就是根據上下左右邊距和子控件自身尺寸來反向推導出來的。在遇到多個子視圖的狀況下具體用不用容器視圖根據狀況而定,容器視圖僅僅起到輔助做用,整個佈局原理是徹底相同的。使用UIScrollView的AutoLayout佈局優勢自沒必要多說,除了從frame計算中擺脫出來以外(絕對佈局和相對佈局的區別),天生支持屏幕旋轉(屏幕的旋轉適配只須要在佈局時稍加註意便可),例如上面三個demo均支持豎屏和橫屏查看,相對於frame佈局代碼簡化了不少。佈局