UIScrollView 的基本使用

本文详细介绍了UIScrollView的基本使用,包括contentSize、contentOffset和contentInset三个核心属性的作用及应用技巧,并探讨了如何设置弹簧效果和滚动指示器。

UIScrollView 基本使用

  • UIScrollView 的三个属性

    • contentSize 设置滚动区域,只有设置了滚动区域才能够滚动
    • contentOffset 设置滚动内容偏移,决定当前显示的内容
    • contentInset 设置滚动外框的偏移
  • UIScrollView 无法滚动原因

  • UIScrollView 设置弹簧效果 & 滚动指示器

常用属性演练

准备工作

  • 新建项目
  • ViewController 中实现以下代码,添加 scrollView
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
}

#pragma mark - 设置界面
- (void)setupUI {

    // 1. 创建 UIScrollView
    UIScrollView *sv = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    sv.backgroundColor = [UIColor blueColor];
    [self.view addSubview:sv];
}

@end
  • 添加 imageView
// 2. 添加 imageView
UIImage *image = [UIImage imageNamed:@"002"];
// initWithImage 方法创建的 imageView 会根据 image 的大小自动调整大小
UIImageView *iv = [[UIImageView alloc] initWithImage:image];

// 将 图像视图 添加到 滚动视图上
[sv addSubview:iv];

运行程序,会发现不会滚动,那么如何滚动呢?

探索头文件

NS_CLASS_AVAILABLE_IOS(2_0) @interface UIScrollView : UIView <NSCoding>

/// The point at which the origin of the content view is offset from the origin of the scroll view.
/// 内容视图原点(origin)所在的偏移位置,相对于 scroll view 的 origin,默认是 CGPointZero
@property(nonatomic)         CGPoint                      contentOffset;                  // default CGPointZero
/// The size of the content view
/// 内容视图的大小,默认是 CGSizeZero
@property(nonatomic)         CGSize                       contentSize;                    // default CGSizeZero
/// The distance that the content view is inset from the enclosing scroll view.
/// 内容视图围绕(enclosing) scroll view 的距离,默认值是 UIEdgeInsetsZero
@property(nonatomic)         UIEdgeInsets                 contentInset;                   // default UIEdgeInsetsZero. add additional scroll area around content
  • 定义属性,方便后续代码演练
@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, weak) UIImageView *imageView;
  • setupUI 方法中使用成员变量记录局部变量
_scrollView = sv;
_imageView = iv;
  • 新建 demoScrollView 方法并在 viewDidLoad 方法中调用
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    [self demoScrollView];
}

#pragma mark - 演练 scrollview
- (void)demoScrollView {

}

三个属性演练

  • contentSize
  • contentOffset
  • contentInset
contentSize
  • demoScrollView 中实现以下方法
- (void)demoScrollView {

    // 1. 设置 contentSize
    // 让 scrollView 的 contentSize 等于 图像视图的大小
    // 设置了滚动视图的 contentSize 之后,滚动视图就能够滚动了
    _scrollView.contentSize = _imageView.bounds.size;   
}

结论

设置了滚动视图的 contentSize 之后,滚动视图就能够滚动了

没有 contentSize,scrollView 就不知道要滚多远

  • 单独设置 contentSizewidth
// contentSize 的 width 决定了水平方向能滚多远
_scrollView.contentSize = CGSizeMake(_imageView.bounds.size.width, 0);
  • 单独设置 contentSizeheight
// contentSize 的 height 决定了垂直方向能滚多远
_scrollView.contentSize = CGSizeMake(0, _imageView.bounds.size.height);

结论

  • scrollView 要滚动就必须设置了滚动视图的 contentSize
  • contentSize 的 width 决定了水平方向滚动距离
  • contentSize 的 height 决定了垂直方向滚动距离
  • 方法名重构 —— 快捷键 cmd + shift + e
/// 演示 contentSize
///
/// 结论:
/// - scrollView 要滚动就必须设置了滚动视图的 contentSize
/// - contentSize 的 width 决定了水平方向滚动距离
/// - contentSize 的 height 决定了垂直方向滚动距离
- (void)demoContentSize {

    // 1. 设置 contentSize
    // 让 scrollView 的 contentSize 等于 图像视图的大小
    // 设置了滚动视图的 contentSize 之后,滚动视图就能够滚动了
    _scrollView.contentSize = _imageView.bounds.size;

    // contentSize 的 width 决定了水平方向能滚多远
//    _scrollView.contentSize = CGSizeMake(_imageView.bounds.size.width, 0);

    // contentSize 的 height 决定了垂直方向能滚多远
//    _scrollView.contentSize = CGSizeMake(0, _imageView.bounds.size.height);
}
contentOffset
  • 新建方法 demoContentOffset 并在 viewDidLoad 调用
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    [self demoContentSize];
    [self demoContentOffset];
}

#pragma mark - 演练 scrollview
/// 演示 contentOffset
- (void)demoContentOffset {

}
  • 增加演示按钮
/// 演示 contentOffset
- (void)demoContentOffset {

    // 1. 增加演示按钮
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoLight];
    btn.center = self.view.center;
    [self.view addSubview:btn];

    [btn addTarget:self action:@selector(clickOffsetButton) forControlEvents:UIControlEventTouchUpInside];
}

/// 点击测试 offset 按钮
- (void)clickOffsetButton {

}
  • 实现代码修改 contentOffset
/// 点击测试 offset 按钮
- (void)clickOffsetButton {

    // 修改 scrollView 的 contentOffset
    _scrollView.contentOffset = CGPointMake(50, 50);

    // bounds 决定了内部控件布局的原点坐标
    NSLog(@"%@", NSStringFromCGRect(_scrollView.bounds));
}
  • 修改代码,递增 contentOffset 的变化
// 2> 递增 contentOffset
CGPoint p = _scrollView.contentOffset;
p.x += 50;
p.y += 50;
_scrollView.contentOffset = p;
  • 修改 NSLog
// bounds 决定了内部控件布局的原点坐标
// scrollView 的 contentOffset 属性本质上就是 bounds 的原点
NSLog(@"%@ - %@", NSStringFromCGRect(_scrollView.bounds), NSStringFromCGPoint(_scrollView.contentOffset));

结论

  • scrollView 通过修改 contentOffset 调整内部视图的坐标位置,从而给用户产生一种视觉上的滚动的效果
  • contentOffset 的值本质上就是 bounds 的原点(origin) 值,苹果在为了方便程序员的理解,增加了这个属性
  • 文档释义:contentOffset:内容视图原点(origin)所在的偏移位置,相对于 scroll view 的 origin,默认是 CGPointZero


contentOffset 相关方法
  • 探索头文件
/// 以恒定速度动画移动到新的 offset
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;  // animate at constant velocity to new offset
/// 滚动到可见区域(靠近边缘-不会滚动到边缘外侧),如果当前区域完全可见,则什么也不做
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;         // scroll so rect is just visible (nearest edges). nothing if rect completely visible

苹果头文件的特点:越是重要的属性和方法就越靠上

  • 新建测试方法
/// 测试 offset 相关方法
- (void)testSetOffsetMethod {

}
  • 修改按钮监听方法
[btn addTarget:self action:@selector(testSetOffsetMethod) forControlEvents:UIControlEventTouchUpInside];
  • 实现方法,测试 setContentOffset:animated: 方法
/// 测试 offset 相关方法
- (void)testSetOffsetMethod {

    // 1. 测试 setContentOffset
    CGFloat x = arc4random_uniform(_imageView.bounds.size.width);
    CGFloat y = arc4random_uniform(_imageView.bounds.size.height);

    // 利用系统默认的动画效果,动画时长不能修改
    [_scrollView setContentOffset:CGPointMake(x, y) animated:YES];
}
  • 自定义动画效果
// 2> 自定义动画效果
[UIView
 animateWithDuration:1.0
 delay:0 usingSpringWithDamping:0.8
 initialSpringVelocity:0
 options:0
 animations:^{
     _scrollView.contentOffset = CGPointMake(x, y);
 } completion:nil];
  • 新建方法 testScrollRectMethod
- (void)testScrollRectMethod {

    // 传入当前完全可见区域,什么也不发生
    [_scrollView scrollRectToVisible:_scrollView.bounds animated:YES];
}
  • 随机区域
// 2> 随机区域
CGFloat x = arc4random_uniform(_imageView.bounds.size.width);
CGFloat y = arc4random_uniform(_imageView.bounds.size.height);
CGRect rect = CGRectMake(x, y, _scrollView.bounds.size.width, _scrollView.bounds.size.height);

[_scrollView scrollRectToVisible:rect animated:YES];
contentInset
/// 内容视图围绕(enclosing) scroll view 的距离,默认值是 UIEdgeInsetsZero
@property(nonatomic)         UIEdgeInsets                 contentInset;                   // default UIEdgeInsetsZero. add additional scroll area around content
  • 新增方法 demoContentInset 并且在 viewDidLoad 调用
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    [self demoContentSize];
    [self demoContentOffset];
    [self demoContentInset];
}

#pragma mark - 演练 scrollview
/// 演示 contentInset
- (void)demoContentInset {
}
  • 实现方法 demoContentInset
/// 演示 contentInset
- (void)demoContentInset {

    UIEdgeInsets inset = UIEdgeInsetsMake(50, 50, 50, 50);

    // 边距设置了,但是初始没有效果,需要拖拽一下才有效果
    _scrollView.contentInset = inset;
}
  • 利用 contentOffset 设置初始位置
// 设置 contentOffset 调整到边距对应位置
_scrollView.contentOffset = CGPointMake(-inset.left, -inset.top);

结论

  • scrollView 通过修改 contentInset 调整内部和边缘的偏移

  • 设置边距之后,初始没有效果,需要拖拽一下才有效果

  • 可以通过设置 contentOffset 调整初始位置

scrollView 与内容相关的三个属性示意图如下:


结论

  • scrollView 要滚动就必须设置了滚动视图的 contentSize

    • contentSize 的 width 决定了水平方向滚动距离
    • contentSize 的 height 决定了垂直方向滚动距离
  • scrollView 通过修改 contentOffset 调整内部视图的坐标位置,从而给用户产生一种视觉上的滚动的效果

    • contentOffset 的值本质上就是 bounds 的原点(origin) 值,苹果在为了方便程序员的理解,增加了这个属性
  • scrollView 通过修改 contentInset 调整内部和边缘的偏移

    • 设置边距之后,初始没有效果,需要拖拽一下才有效果
    • 可以通过设置 contentOffset 调整初始位置
设置弹簧效果 & 滚动指示器
  • 探索头文件
/// 默认 YES
@property(nonatomic)         BOOL                         bounces;                        // default YES. if YES, bounces past edge of content and back again
/// 始终垂直弹,默认是 NO,如果设置成 YES,即使内容比区域小,同样允许垂直方向弹动
@property(nonatomic)         BOOL                         alwaysBounceVertical;           // default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag vertically
/// 始终水平弹,默认是 NO,如果设置成 YES,即使内容比区域小,同样允许水平方向弹动
@property(nonatomic)         BOOL                         alwaysBounceHorizontal;         // default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag horizontally

/// 是否允许滚动,默认是 YES,关闭之后禁止任何拖拽
@property(nonatomic,getter=isScrollEnabled) BOOL          scrollEnabled;                  // default YES. turn off any dragging temporarily
/// 显示水平滚动指示器
@property(nonatomic)         BOOL                         showsHorizontalScrollIndicator; // default YES. show indicator while we are tracking. fades out after tracking
/// 显示垂直滚动指示器
@property(nonatomic)         BOOL                         showsVerticalScrollIndicator;   // default YES. show indicator while we are tracking. fades out after tracking
/// 滚动指示器边距
@property(nonatomic)         UIEdgeInsets                 scrollIndicatorInsets;          // default is UIEdgeInsetsZero. adjust indicators inside of insets
  • 新建方法,并且在 viewDidLoad 调用
- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    [self demoContentSize];
    [self demoContentOffset];
    [self demoContentInset];

    [self demoBounces];
}

#pragma mark - 演练 scrollview
/// 演示弹簧效果
- (void)demoBounces {

}
  • 实现方法,禁止弹簧效果
/// 演示弹簧效果
- (void)demoBounces {
    // 1. 禁止弹簧效果
    _scrollView.bounces = NO;
}
  • 测试始终弹动
// 2. 测试始终允许弹簧效果
// 取消 contentSize 无法滚动
_scrollView.contentSize = CGSizeZero;
_scrollView.alwaysBounceVertical = YES;
_scrollView.alwaysBounceHorizontal = YES;
  • 测试禁止滚动属性
_scrollView.scrollEnabled = NO;

如果禁止滚动,弹簧效果同样失效

  • 测试滚动指示器
// 3. 滚动指示器
// 1> 禁用垂直滚动指示器
_scrollView.showsVerticalScrollIndicator = NO;
// 1> 禁用水平滚动指示器
_scrollView.showsHorizontalScrollIndicator = NO;

查看视图层次结构会发现,禁用指示器之后,那两个 UIImageView 不见了

结论

苹果是用 imageView 实现的水平垂直指示器

### ### UIScrollView基本使用方法 在 Swift 中使用 `UIScrollView` 实现 UIKit 组件的滚动功能,是 iOS 开发中常见的做法。通过设置 `UIScrollView` 的 `contentSize` 属性,可以定义其可滚动的区域。以下是一个基本示例,展示如何创建一个可以垂直滚动的视图: ```swift import UIKit class ScrollViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let scrollView = UIScrollView() scrollView.frame = view.bounds scrollView.contentSize = CGSize(width: view.frame.width, height: 1000) scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(scrollView) let contentView = UIView() contentView.frame.size = CGSize(width: view.frame.width, height: 1000) scrollView.addSubview(contentView) for i in 0..<20 { let label = UILabel() label.text = "条目 $i + 1)" label.font = UIFont.systemFont(ofSize: 20) label.frame = CGRect(x: 20, y: 100 * i + 20, width: 200, height: 80) contentView.addSubview(label) } } } ``` 上述代码创建了一个 `UIScrollView` 并设置了其内容大小为 1000 点高,确保内容可以垂直滚动。内容视图 `contentView` 承载了多个 `UILabel` 控件,实现基本的滚动效果[^2]。 ### ### 使用 Auto Layout 实现 UIScrollView 滚动 为了确保 `UIScrollView` 的内容能够根据子视图动态调整,推荐使用 Auto Layout 约束来管理布局。以下是一个使用 Auto Layout 设置 `UIScrollView` 和 `contentView` 的示例: ```swift import UIKit class ScrollViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let scrollView = UIScrollView() scrollView.frame = view.bounds scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(scrollView) let contentView = UIView() contentView.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(contentView) NSLayoutConstraint.activate([ contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: 1000) ]) for i in 0..<20 { let label = UILabel() label.text = "条目 $i + 1)" label.font = UIFont.systemFont(ofSize: 20) label.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(label) NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 100 * CGFloat(i) + 20), label.heightAnchor.constraint(equalToConstant: 80) ]) } } } ``` 在该实现中,`contentView` 使用 Auto Layout 约束与 `UIScrollView` 对齐,并设置了宽度和最小高度约束。每个子视图(如 `UILabel`)也通过约束进行定位,确保内容可以根据实际布局动态调整滚动范围[^1]。 ### ### UIScrollView 与 UITableView 联动 如果需要滚动的内容是列表形式,可以使用 `UITableView` 来实现更高效的滚动效果。`UITableView` 本身继承自 `UIScrollView`,因此可以直接利用其滚动功能: ```swift import UIKit class TableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = "条目 $indexPath.row + 1)" return cell } } ``` 在此示例中,`UITableView` 被用作 `UITableViewController` 的主视图,并通过数据源方法动态加载列表项。由于 `UITableView` 继承自 `UIScrollView`,因此其滚动功能可以直接使用,无需额外配置[^2]。 ### ### UIScrollView 嵌套 UITableView 联动 在某些场景下,需要在 `UIScrollView` 中嵌套 `UITableView` 实现更复杂的滚动效果。这种情况下,需要处理手势冲突问题。可以通过自定义 `UIScrollView` 并实现 `UIGestureRecognizerDelegate` 协议的方法来实现手势同时识别: ```swift import Foundation import UIKit class NestScrollView: UIScrollView { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override init(frame: CGRect) { super.init(frame: frame) } } extension NestScrollView: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } } ``` 通过返回 `true`,允许两个手势同时识别,从而实现 `UIScrollView` 与 `UITableView` 的联动效果[^3]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值