UIScrollView 基本使用
蘋果的官方文檔:Scroll View Programming Guide
UIScrollView 是 iOS 非常重要的具備滾動能力的視圖,能滾動的控件基本繼承自 UIScrollView,比如UITableView, UICollectionView, UITextView等。
主題
響應滾動互動
var delegate: UIScrollViewDelegate?
内容大小和偏移量
var contentSize: CGSize
視圖可以滾動的大小,決定了可以滾動的範圍。
var contentOffset: CGPoint
滾動偏移量,
func setContentOffset(CGPoint, animated: Bool)
設定偏移量
管理 Content Inset Behavior
以下屬性都用于控制邊距的。
var adjustedContentInset: UIEdgeInsets
隻讀屬性,iOS11+,表示
contentView.frame.origin
偏移了
scrollview.frame.origin
多少,也就是實際邊距。根據
contentInsetAdjustmentBehavior
來決定是否需要添加 safeAreaInsets,
var contentInset: UIEdgeInsets
UIScrollView 四周邊距
var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior
iOS 11+, 在設定邊距的時候考慮 safeAreaInsets的方式,一共有以下四個值, 預設值為 automatic
enum UIScrollView.ContentInsetAdjustmentBehavior
狀态值 | adjustedContentInset |
---|---|
automatic | 取決于 Controller,如果 Contoller 的 屬性為true(預設值)且含有導航欄,則上下會自動添加 safeAreaInsets,即 adjustedContentInset =safeAreaInset + contentInset,不管是否滾動。其他情況與scrollableAxes一緻 |
scrollableAxes | 滾動方向需要加上 SafeAreaInset |
never | adjustedContentInset = contentInset |
always | adjustedContentInset = safeAreaInset + contentInset |
func adjustedContentInsetDidChange()
adjustedContentInset 改變通知
配置 ScrollView
var isScrollEnabled: Bool
是否允許滑動
var isDirectionalLockEnabled: Bool
預設值為false, 如果設定為 true 隻能同時滑動一邊(如果垂直和水準方向都可以滑動時)
var isPagingEnabled: Bool
是否按照分頁滑動
var scrollsToTop: Bool
是否開啟 scroll-to-top 手勢, 預設值 true, 這是 ScrollView 的一個影藏功能,點選狀态欄,會自動回到頂部。
var bounces: Bool
是否開啟彈框功能,預設值為 true,當需要滾動時才觸發。
var alwaysBounceVertical: Bool
一直觸發垂直彈簧效果。
var alwaysBounceHorizontal: Bool
一直觸發水準彈簧效果。
滾動到固定位置
func scrollRectToVisible(CGRect, animated: Bool)
滾動到固定位置。
其他屬性和方法參考官方文檔
使用 UIScrollView + UIStackView 快速适配螢幕
衆所周知,不同手機頁面高度是不一緻的,這就可能造成部分内容較多頁面小螢幕可能展示不完。這時候就需要頁面具有滾動功能。
借助 UIScrollView + UIStackView ,我們頁面可分割為多個抽屜View,且不用關心頁面布局和小螢幕手機顯示不完的問題。
基礎原理
層次結構包含如下圖:

綠色:ViewController.view
藍色:UIStackView,大小和 UIScrollView 重合,通過裡面添加的子 View 決定是否滾動和,垂直方向的限制。
黑色:各個抽屜 View。
通過 view 決定 UIStackeView 大小, 設定 StackeView的寬度和 UIScrollView 一緻,且四周限制等于 UIScrollView。這樣就能決定 UIScrollView 的 contentsize,實作小螢幕自動滾動。
示例代碼
Controller 完整代碼
class BaseController: UIViewController { // 将該功能添加在基礎 Controller中
lazy var contentView = lazyContentView() // 設定為懶加載,外部使用才加載。
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
}
private func lazyContentView() -> UIStackView {
let contentView = UIStackView()
contentView.spacing = 8 // 設定預設間距
contentView.axis = .vertical // 垂直布局
scrollView.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
// 設定 contentView 四周與 scrollView 四周一緻。
contentView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// 寬度和高度限制中其中一個與 scrollView 相等
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true // 寬度相等,垂直滑動
// contentView.heightAnchor.constraint(equalToConstant: 800).isActive = true // 高度相等,水準滑動
view.addSubview(scrollView)
// 添加 ScrollView 的限制
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// scrollView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
return contentView
}
}
子類使用,添加 View
func setupView() {
let redView = UIView()
redView.backgroundColor = .red
redView.translatesAutoresizingMaskIntoConstraints = false
redView.heightAnchor.constraint(equalToConstant: 300).isActive = true // 設定高度限制 或者内部垂直方向限制是确定的(能自己計算出高度),
contentView.addArrangedSubview(redView)
// contentView 添加其他具備垂直方向限制确定的 子View
}
UIStackView 擴充方法
UIStackView 通過
spacing
屬性可以設定預設間距,如果需要設定不同間距,可以使用系統
customSpacing(after:)
方法,但是該方法 iOS11+,我們可以自定義一個方法相容設定不同間距
設定額外間距方法
/// 在基礎 spacing 之後再增加多少距離。
/// - Parameters:
/// - spacing: 額外增加的距離
/// - arrangedSubview: 目标視圖
func setAppendSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) {
if #available(iOS 11.0, *) {
let customSpacing = self.spacing + spacing
setCustomSpacing(customSpacing, after: arrangedSubview)
} else {
guard let aimIndex = arrangedSubviews.firstIndex(of: arrangedSubview) else {
Log.assert("視圖未添加,無法擷取 index。應該先添加視圖")
return
}
// swiftlint:disable init_by_MSUI
let emptyView = UIView()
emptyView.addSubview(arrangedSubview)
arrangedSubview.leftAnchor.constraint(equalTo: emptyView.leftAnchor).isActive = true
arrangedSubview.rightAnchor.constraint(equalTo: emptyView.rightAnchor).isActive = true
arrangedSubview.topAnchor.constraint(equalTo: emptyView.topAnchor).isActive = true
arrangedSubview.bottomAnchor.constraint(equalTo: emptyView.bottomAnchor, constant: -spacing).isActive = true
removeArrangedSubview(arrangedSubview)
insertArrangedSubview(emptyView, at: aimIndex)
}
}
添加一組子view 到 UIStackView
extension UIStackView {
func addArrangedSubviews(_ views: [UIView]) {
guard !views.isEmpty else {
return
}
views.forEach { addArrangedSubview($0) }
}
}
Tip: 向 UIStackView 中多次添加同一個子 view ,隻有添加一個。
UIScrollView 實作分頁功能
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIScrollView *scroll;
@property (weak, nonatomic) IBOutlet UIPageControl *page;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets=NO;
self.scroll.backgroundColor = [UIColor greenColor];
self.scroll.contentInset = UIEdgeInsetsMake(0, 0, 5, 10);
self.scroll.showsVerticalScrollIndicator = NO;//設定旁邊顯示條
self.scroll.showsHorizontalScrollIndicator = NO;//設定水準顯示條
self.scroll.scrollEnabled=YES;
_scroll.pagingEnabled = YES;//支援分頁,如果不設定,滑動是随意的。
self.scroll.bounces = NO;//彈簧效果(分頁建議取消,可以避免由于滑動過度導緻出現底層視圖)
_scroll.delegate = self;
//準備好2個頁
UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 375, 298)];//注意這裡的位置是相對與父視圖ScrollView的位置,不是相對于螢幕。。。剛開始了解錯啦
view1.backgroundColor = [UIColor orangeColor];
UIView *view2 = [[UIView alloc]initWithFrame:CGRectMake(375, 0, 375, 298)];
view2.backgroundColor = [UIColor yellowColor];
[_scroll addSubview:view1];
[_scroll addSubview:view2];
_scroll.contentSize = CGSizeMake(self.scroll.frame.size.width*2, 298);//等下試試大小(主要是寬度)可不可以改變
self.page.currentPage = 0;
self.page.numberOfPages = 2;//總用兩頁
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) prepareTwoView{
}
#pragma ScrollViewDelegate
//實作代理
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"完成滾動,觀察是否改變");
//[learn]怎麼計算目前頁值 偏移的x值除以寬度,
int index = fabs(self.scroll.contentOffset.x)/scrollView.frame.size.width;
self.page.currentPage = index;
}
@end
效國圖如下:
3、可以使用UIScrollView的代理實作捏合縮放
若要使用,目前視圖應遵守UIScrollViewDelegate代理協定,并設定代理屬性(self.scrollView.delegate=self)
4、【擴充】縮放圖檔位置如何顯示在中間、縮放圖檔橫豎屏效果最佳、實作輕按兩下縮放、UIScrollView的嵌套使用(相同方向,相反方向,交叉方向)
位址連結
注意事項
1. Xib 使用 UIScrollView 在低版本奔潰
描述: 在Storyboard中使用到ScrollView,項目要求相容最低版本為iOS 9, 在運作到使用ScrolleView的界面時,百分之百奔潰,奔潰日志"Could not instantiate class named _UIScrollViewLayoutGuide", 但是Storyboad并沒有報錯。
原因: storyboard自動使用ContentLayoutGuide, 隻需要取消掉就好了。
修正後