UIScrollViewj盡管繼承于UIView,但它是一個(gè)相對(duì)比較特殊的視圖,特別是當(dāng)它遇到了AutoLayout之后。在UIScrollView中使用AutoLayout的目的除了使用相對(duì)約束確定子控件的位置和大小外,更重要的是如何自動(dòng)計(jì)算出UIScrollView的contentSize(關(guān)于使用UIScrollView并且最終手動(dòng)指定contentSize的AutoLayout用法不再今天討論之列,嚴(yán)格意義上來(lái)說(shuō)這也不是一種真正的UIScrollView的AutoLayout應(yīng)用)。
所謂UIScrollView的特殊之處就在于當(dāng)它遇到了AutoLayout之后其contentSize的計(jì)算規(guī)則有些特殊。首先contentSize是根據(jù)子視圖的leading/trailing/top/bottom進(jìn)行確定的,而子視圖的位置約束又必須依賴(lài)于UIScrollView來(lái)確定。這就有點(diǎn)類(lèi)似于前面UICollectionView自適應(yīng)高度文章中提到的:UICollectionViewCell的大小計(jì)算就是計(jì)算contentView的大小,而contentView的大小計(jì)算依賴(lài)于子視圖的leading/trailing/top/bottom,子視圖的位置約束又依賴(lài)于contentView,此時(shí)只要子視圖存在固有尺寸(intrinsicContentSize)或者指定了尺寸又設(shè)置了leading/trailing/top/bottom,AutoLayout布局引擎即可計(jì)算出contentView的大小。 再回到AutoLayout,其實(shí)它的contentSize計(jì)算原理和UICollectionViewCell自適應(yīng)很是類(lèi)似,只是UIScrollView內(nèi)部并沒(méi)有一個(gè)contentView的東西(但是可以想象其存在,方便后面的理解,不過(guò)要清楚UIScrollView滾動(dòng)的本質(zhì)并非包含一個(gè)contentView而是通過(guò)bounds和frame坐標(biāo)體系轉(zhuǎn)換來(lái)實(shí)現(xiàn)的),只要設(shè)置子視圖的leading/trailing/top/bottom(通常是通過(guò)edges=0讓子視圖上下左右間距都為0保證整個(gè)視圖都在UIScrollView可視范圍之內(nèi)),然后通過(guò)設(shè)置size(width/height)約束確定子視圖大小進(jìn)而由AutoLayout反向計(jì)算出UIScrollView的contentSize。 假設(shè)A是UIScrollView(藍(lán)色)、B是子視圖1(綠色)、C是子視圖2(綠色)、D是contentSize的計(jì)算區(qū)域(灰色,事實(shí)上它不存在),要想讓cotentSize可以自動(dòng)計(jì)算只需要確定B、C上下左右布局間距,然后再指定B、C間距和尺寸之后AutLayout既可以自動(dòng)推斷出contentSize的大小,原理如下圖(下圖布局類(lèi)似于下面Demo3):

對(duì)于單個(gè)子視圖布局比較簡(jiǎn)單,只要設(shè)置leading/trailing/top/bottom,再設(shè)置子視圖的size(width/height)即可,當(dāng)然如果子視圖存在固有尺寸并且想要使用固有尺寸的話(huà),則這一步也可以省略。例如下面demo中演示了一個(gè)UIScrollView包含一個(gè)UIImageView子視圖的圖片查看界面。在下面的布局中僅僅設(shè)置了UIImageView上下左右邊距,而UIImageView存在固有尺寸,因此整個(gè)布局就相當(dāng)簡(jiǎn)單了(AutoLayout布局使用SnapKit庫(kù))。
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約束就可以計(jì)算出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的布局文章中都會(huì)提到使用一個(gè)容器視圖包含多個(gè)子視圖,然后分別完成子視圖布局和容器視圖在UIScrollView中的布局,以此來(lái)簡(jiǎn)化布局過(guò)程。下面的Demo中演示了一個(gè)圖片分頁(yè)查看的布局情況,containerView作為容器布局時(shí)設(shè)置上下左右間距,然后設(shè)置其高度等于UIScrollView高度(因?yàn)橐獙?shí)現(xiàn)左右滾動(dòng)),而此時(shí)并不需要設(shè)置寬度,因?yàn)閷挾鹊挠?jì)算依賴(lài)于子視圖。在containerView的子視圖中只要設(shè)置子視圖與containerView的邊距及各自間距和寬度,之后AutoLayout就可以計(jì)算出containerView的寬度。如此一來(lái)containerView已經(jīng)設(shè)置完了四周間距和尺寸就可以計(jì)算出contentSize。
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的高度,相當(dāng)于contentSize.height已經(jīng)確定,width通過(guò)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包含多個(gè)子視圖的布局方式相對(duì)來(lái)說(shuō)好像使用要多一些,但是其實(shí)布局原理并沒(méi)有任何變化,如果熟悉了UIScrollView的AutoLayout布局原理,用不用containerView大家可以根據(jù)情況自行決定,如果僅僅是簡(jiǎn)單的幾個(gè)子視圖布局沒(méi)有特殊的需求那么直接布局可能會(huì)更簡(jiǎn)單,但是如果子視圖相對(duì)較多并且可能所有子視圖有公共的操作需求(例如所有子視圖在鍵盤(pán)彈出后需要改變其位置)則更適合使用containerView布局。下面代碼中去掉containerView完成demo2的需求,原理相同,代碼也不難理解。
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 }() }
其實(shí)概括起來(lái)UIScrollView的布局最主要的問(wèn)題就是解決contentSize的計(jì)算問(wèn)題。而根據(jù)UIScrollView的特點(diǎn)contentSize的計(jì)算最終就是根據(jù)上下左右邊距和子控件自身尺寸來(lái)反向推導(dǎo)出來(lái)的。在遇到多個(gè)子視圖的情況下具體用不用容器視圖根據(jù)情況而定,容器視圖僅僅起到輔助作用,整個(gè)布局原理是完全相同的。使用UIScrollView的AutoLayout布局優(yōu)點(diǎn)自不必多說(shuō),除了從frame計(jì)算中擺脫出來(lái)之外(絕對(duì)布局和相對(duì)布局的區(qū)別),天生支持屏幕旋轉(zhuǎn)(屏幕的旋轉(zhuǎn)適配只需要在布局時(shí)稍加注意即可),例如上面三個(gè)demo均支持豎屏和橫屏查看,相對(duì)于frame布局代碼簡(jiǎn)化了很多。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注