Swift手撸轮播效果

一、创建ScrollView

swift 复制代码
@objcMembers class LSLottieAnimView: UIView, UIScrollViewDelegate {
	private var scrollView: UIScrollView = UIScrollView()
	
	func addScrollView() {
		scrollView.showsHorizontalScrollIndicator = false
    scrollView.isPagingEnabled = true
    // 自定义frame
    scrollView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    scrollView.delegate = self
    // 此处创建了三个子view的宽度
    scrollView.contentSize = CGSize(width: scrollView.frame.width * CGFloat(3), height: scrollView.frame.height)
    
    let u1 = UIView()
    u1.frame = scrollView.bounds
    u1.backgroundColor = .black
    let u2 = UIView()
    u2.frame = scrollView.bounds
    u2.backgroundColor = .green
    let u3 = UIView()
    u3.frame = scrollView.bounds
    u3.backgroundColor = .yellow
    scrollView.addSubview(u1)
    scrollView.addSubview(u2)
    scrollView.addSubview(u3)
    
    UIApplication.shared.keyWindow?.rootViewController?.view.addSubview(scrollView)
    
    // 添加拖拽手势识别器
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
    scrollView.addGestureRecognizer(panGesture)
	}
}

二、处理手势

swift 复制代码
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
    let scrollView = gesture.view as! UIScrollView
    let translation = gesture.translation(in: scrollView)
    
    // 
    let count = scrollView.subviews.count - 1 - 1;
    
    // 禁止回弹
    if (scrollView.contentOffset.x <= 0 && translation.x > 0) || (Int(scrollView.frame.width) * count <= Int(scrollView.contentOffset.x) && translation.x < 0) {
        return
    }
    
    // 当setContentOffset的动画未完成时,此时isDecelerating为true
    if scrollView.isDecelerating || !scrollView.isScrollEnabled {
        return
    }
    
    switch gesture.state {
        // case .began:
	        // 如果开启轮播,则停止自动轮播
	        // scrollView.layer.removeAllAnimations()
        
    case .changed:
        // 根据手势的偏移量进行滚动
        if scrollView.contentOffset.x - translation.x <= 0 {
            scrollView.contentOffset.x = 0
        } else if scrollView.contentOffset.x - translation.x >= scrollView.frame.width * CGFloat(count) {
            scrollView.contentOffset.x = scrollView.frame.width * CGFloat(count)
        } else {
            scrollView.contentOffset.x -= translation.x
        }
        
    case .ended, .cancelled, .failed:
        
        // 获取滚动视图当前的页索引
        let pageIndex = Int(scrollView.contentOffset.x / scrollView.frame.width)
        
        // 修改后的页面索引
        var willPageIndex = pageIndex
        
        // 根据手势的速度和偏移量来判断是否切换到上一页或下一页
        let velocity = gesture.velocity(in: scrollView)
        if velocity.x > 0 {
            // 向右滑动
        } else if velocity.x < 0 {
            // 向左滑动
            if pageIndex < count {
                willPageIndex = pageIndex + 1
            }
        } else {
            // 根据偏移量判断是否切换到上一页或下一页
            if translation.x < -scrollView.frame.width / 2 && pageIndex < count {
                willPageIndex = pageIndex + 1
            } else if translation.x > scrollView.frame.width / 2 && pageIndex > 0 {
                willPageIndex = pageIndex - 1
            }
        }
        
        // 只有修改后才需要调用setContentOffset,否则会出现scrollViewDidEndScrollingAnimation不调用的情况
        if (scrollView.contentOffset.x != CGFloat(willPageIndex) * scrollView.frame.width) {
            scrollView.setContentOffset(CGPoint(x: CGFloat(willPageIndex) * scrollView.frame.width, y: 0), animated: true)
            scrollView.isScrollEnabled = false
        }
        
    default:
        break
    }
    
    // 重置手势的偏移量
    gesture.setTranslation(.zero, in: scrollView)
}

// MARK: UIScrollViewDelegate
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    scrollView.isScrollEnabled = true
}

三、注意事项

swift 复制代码
1. 如果需要回弹,只需要修改如下代码即可
// 注释如下代码
if (scrollView.contentOffset.x <= 0 && translation.x > 0) || (Int(scrollView.frame.width) * count <= Int(scrollView.contentOffset.x) && translation.x < 0) {
    return
}

// 修改成如下代码
	        case .changed:
//            // 根据手势的偏移量进行滚动
//            if scrollView.contentOffset.x - translation.x <= 0 {
//                scrollView.contentOffset.x = 0
//            } else if scrollView.contentOffset.x - translation.x >= scrollView.frame.width * CGFloat(count) {
//                scrollView.contentOffset.x = scrollView.frame.width * CGFloat(count)
//            } else {
                scrollView.contentOffset.x -= translation.x
//            }

2. 在调用setContentOffset且animated为true时,需要考虑将isScrollEnabled设置为false,等到动画完成后(scrollViewDidEndScrollingAnimation)将isScrollEnabled恢复到true,否则在动画期间仍然可以拖拽
3. 在调用setContentOffset时,如果值和之前相同,则不会触发scrollViewDidEndScrollingAnimation
相关推荐
小锋学长生活大爆炸3 小时前
【开源软件】这次iPhone也是用上Claw了 | PhoneClaw
ios·开源软件·iphone·claw
SameX7 小时前
独立开发一个把走过的路变成 km² 的 App,聊聊 25m 网格和后台 GPS 的坑
ios
XD7429716368 小时前
科技早报晚报|2026年4月30日:Agent 安全壳、浏览器 iOS 测试台与可穿戴数据 API,今天更值得看的 3 个技术机会
科技·ios·开源项目·科技新闻·开发者工具
北京自在科技11 小时前
Find Hub App 小更新
android·ios·安卓·findmy·airtag
2501_9159214311 小时前
HTTPS前端劫持 新一代流量劫持解决方案
前端·网络协议·ios·小程序·https·uni-app·iphone
911hzh11 小时前
Flutter WebRTC iOS 原理解析:从 getUserMedia 到 Texture,讲清视频采集、纹理渲染与远端通话链路
flutter·ios·webrtc
软泡芙12 小时前
【iOS】 开发入门指南
ios
水中加点糖12 小时前
ios中使用DockKit和CoreML实现自定义目标的自动跟随(一)
目标检测·ios·目标跟踪·硬件控制·dockkit
2501_9159090613 小时前
iOS应用签名的三种方法全解析:从官方到第三方工具
android·ios·小程序·https·uni-app·iphone·webview
四眼蒙面侠15 小时前
深入 Open Agent SDK(番外篇):实战验证——把 SDK 塞进一个 macOS 原生 Agent 应用
swift·claudecode·bmad·agentsdk·openagentsdk