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
neveradjustedContentInset = contentInset
alwaysadjustedContentInset = 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,且不用关心页面布局和小屏幕手机显示不完的问题。

基础原理

层次结构包含如下图:

截屏2021-08-20 下午2.26.11

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

效国图如下:

img

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

img

修正后

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值