天天看點

UIScrollView 使用

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 的

automaticallyAdjustsScrollViewInsets

屬性為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,且不用關心頁面布局和小螢幕手機顯示不完的問題。

基礎原理

層次結構包含如下圖:

UIScrollView 使用

綠色: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
           

效國圖如下:

UIScrollView 使用

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, 隻需要取消掉就好了。

UIScrollView 使用

修正後

UIScrollView 使用