Swift 3.0 商城开发 —— 图片滑动组件
2017-04-10 本文已影响367人
zZ爱吃菜
效果图
![image](https://raw.githubusercontent.com/liu1013269528/Files/master/Blog/Image/swift%20%E5%95%86%E5%9F%8E%E5%BC%80%E5%8F%91/imagescroll/image_scroll.gif)
知识点
- 学习 UIScrollView 和 UIPageControl 的基本用法
- 学习 三个 UIImageView 实现无限循环滚动思想
- 学习 我的 UIView 常用扩展
- 学习 给我们的视图设置代理事件
主要设计思想
概要介绍我在设计此控件的一些具体步骤与思想
- 创建 UIScrollView 作为滚动视图的容器;
- 创建 UIPageControl 作为显示页数以及总页数的组件
- 在 UIScrollView 中创建三个 UIImageView 分别作为 上一页 当前页 下一页
- 通过绑定数据 data 的 didSet 来动态部署 UIScrollView 中的 UIImageView 视图。(实现无限循环)
- 通过 Timer 实现定时跳转到下一个视图
- 调用 UIScrollView 扩张 extension 实现具体操作细节
教程开始
- UIScrollView 使用
博主习惯创建组件的方式如下:
// fileprivate 是 Swift 3.0 新增加的访问控制权限:文件内访问
// 封装控件时,我们要主动的隐藏掉具体的实现,只开放使用接口即可
fileprivate var scrollView: UIScrollView = {
let object = UIScrollView()
// 是否可以拉伸滑动
object.bounces = false
// 隐藏 水平和垂直 滚动条
object.showsVerticalScrollIndicator = false
object.showsHorizontalScrollIndicator = false
// 分页
object.isPagingEnabled = true
return object
}()
- UIPageControl 使用
创建 UIPageControl 组件,构建如下:
fileprivate var pageControl: UIPageControl = {
let object = UIPageControl()
// 单页面下 隐藏
object.hidesForSinglePage = true
// 当前页背景色
object.currentPageIndicatorTintColor = UIColor.red
// 其他也背景色
object.pageIndicatorTintColor = UIColor.gray
return object
}()
- 主视图配置
主要包括:主视图的属性配置、添加子视图
private func prepareUI() {
self.backgroundColor = UIColor.white
self.scrollView.delegate = self
// 添加 滑动试图
self.addSubview(scrollView)
createScrollView()
// 添加 页面控制
self.addSubview(pageControl)
}
private func layoutUI() {
scrollView.frame = self.bounds
pageControl.frame = CGRect(x: self.frame.width - 85, y: self.frame.height - 25, width: 80, height: 20)
scrollView.contentSize = CGSize(width: CGFloat(imageCount) * viewSize.width, height: viewSize.height)
}
- 构建 UIScrollView 子视图
重点:通常情况下,开发者可能根据图片数组的数量在 UIScrollView 中创建对应数量的 UIImageView 扩充 UIScrollView 的 contentSize 实现所有图片的滑动;这种设计思路比较常规,但有两个问题:1.如果图片数据过大,此控件将占用过大的内存去创建视图;2.很难实现无限循环滑动。
我的思想:在 UIScrollView 中只创建三个 UIImageView 作为 上一页图片 当前页图片 下一页图片 的容器。通过判断当前页所在 图片数据中的位置,动态为三个 UIImageView 填充指定图片。
// 创建 滑动视图子视图
func createScrollView() {
for i in 0 ..< 3 {
let imageView: UIImageView = {
let object = UIImageView()
object.isUserInteractionEnabled = true
object.contentMode = UIViewContentMode.scaleToFill
return object
}()
imageView.frame = CGRect(x: CGFloat(i) * viewSize.width, y: 0, width: viewSize.width, height: viewSize.height)
let tap = UITapGestureRecognizer(target: self, action: #selector(touchImage))
imageView.addGestureRecognizer(tap)
scrollView.addSubview(imageView)
}
}
- 重点:动态更新 UIImageView 视图实现无限滚动
核心知识:如果通过三个 UIImageView 实现 无限图片的 无限循环滑动:
func updateScrollView() {
// 遍历三次
for i in 0 ..< 3 {
获取 UIScrollView 中 UIImageView
let imageView = scrollView.subviews[i] as! UIImageView
// 获取 当前展示的图片是 序号
var index = pageControl.currentPage
// 如果 UIImageView UIScrollView 中的第二个视图,即 上一个图片
if i == 0 {
// 则 index 等于 当前图片序号 - 1
index -= 1
}
// 如果 UIImageView 是 UIScrollView 中的第三个视图,即下一个图片
if i == 2 {
// 则 index 等于 当前图片序号 + 1
index += 1
}
// 越界操作 操作
if index < 0 {
index = pageControl.numberOfPages - 1
}
if index >= pageControl.numberOfPages {
index = 0
}
imageView.tag = index
if let currentData = data {
imageView.image = UIImage(named: currentData[index].imageUrl!)
}
}
// 调整 UIScrollView 偏移量 使其永远只显示中间的 UIImageView
scrollView.contentOffset = CGPoint(x: viewSize.width, y: 0)
}
糊涂的童鞋,奉上我画的示意图
假如我们的图片数据只有 6 张图片;
红色代表原始数组,蓝色代表我们的 UIScrollView 两边越界是 填充收尾图片
![image](https://raw.githubusercontent.com/liu1013269528/Files/master/Blog/Image/swift%20%E5%95%86%E5%9F%8E%E5%BC%80%E5%8F%91/imagescroll/scrollViewTeach.png)
最后部分
设置定时器
// MARK: Timer
fileprivate func startTimer() {
let selector = #selector(nextImage)
timer = Timer(timeInterval: 3.0, target: self, selector: selector, userInfo: nil, repeats: true)
RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes)
}
fileprivate func stopTimer() {
timer?.invalidate()
timer = nil
}
扩展完善交互时的事件
extension ImageScrollView: UIScrollViewDelegate {
// 当 scrollView 有偏移量时出发
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var page: Int = 0
var minDistance: CGFloat = CGFloat(MAXFLOAT)
for i in 0 ..< 3 {
let imageView = scrollView.subviews[i] as! UIImageView
// 由于当前页有用都是中间页,所以当 偏移量==图片宽度时,就是当前页。
let distance:CGFloat = abs(imageView.x - scrollView.contentOffset.x)
if distance < minDistance {
minDistance = distance
page = imageView.tag
}
}
pageControl.currentPage = page
}
// 开始拖动 UIScrollView 事件
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
stopTimer()
}
// 结束拖动 UIScrollView 事件
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
startTimer()
}
//
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updateScrollView()
}
// UIScrollView 结束滚动
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
updateScrollView()
}
}
学会设置代理
具体步骤:
- 定义代理 delegate
- 配置代理事件
- 实例对象,调用对象代理
// 1
@objc protocol ImageScrollViewDelegate {
@objc optional func touchAt(index: Int)
}
// 2
class ImageScrollView: UIView {
var delegate: ImageScrollViewDelegate?
// 触发代理
func touchImage(tap: UITapGestureRecognizer) {
if let index = tap.view?.tag {
delegate?.touchAt!(index: index)
}
}
...... }
// 3
// 实现代理:在调用 ImageScrollView 控件的 VC 中扩展,具体看源码
extension ImageScrollViewController: ImageScrollViewDelegate {
func touchAt(index: Int) {
print(index)
}
}
源码
组件源码
import UIKit
@objc protocol ImageScrollViewDelegate {
@objc optional func touchAt(index: Int)
}
class ImageScrollView: UIView {
var delegate: ImageScrollViewDelegate?
fileprivate var timer: Timer?
var viewSize: CGSize!
var data: [ImageScrollData]? {
didSet {
if timer != nil {
timer!.invalidate()
timer = nil
}
if let scrollData = data {
pageControl.numberOfPages = scrollData.count
pageControl.currentPage = 0
updateScrollView()
startTimer()
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
viewSize = frame.size
prepareUI()
layoutUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func prepareUI() {
self.backgroundColor = UIColor.white
self.scrollView.delegate = self
// 添加 滑动试图
self.addSubview(scrollView)
createScrollView()
// 添加 页面控制
self.addSubview(pageControl)
}
private func layoutUI() {
scrollView.frame = self.bounds
pageControl.frame = CGRect(x: self.frame.width - 85, y: self.frame.height - 25, width: 80, height: 20)
scrollView.contentSize = CGSize(width: CGFloat(3) * viewSize.width, height: viewSize.height)
}
// 创建 滑动视图子视图
func createScrollView() {
for i in 0 ..< 3 {
let imageView: UIImageView = {
let object = UIImageView()
object.isUserInteractionEnabled = true
object.contentMode = UIViewContentMode.scaleToFill
return object
}()
imageView.frame = CGRect(x: CGFloat(i) * viewSize.width, y: 0, width: viewSize.width, height: viewSize.height)
let tap = UITapGestureRecognizer(target: self, action: #selector(touchImage))
imageView.addGestureRecognizer(tap)
scrollView.addSubview(imageView)
}
}
func updateScrollView() {
for i in 0 ..< 3 {
let imageView = scrollView.subviews[i] as! UIImageView
var index = pageControl.currentPage
if i == 0 {
index -= 1
}
if i == 2 {
index += 1
}
if index < 0 {
index = pageControl.numberOfPages - 1
}
if index >= pageControl.numberOfPages {
index = 0
}
imageView.tag = index
if let currentData = data {
imageView.image = UIImage(named: currentData[index].imageUrl!)
}
}
scrollView.contentOffset = CGPoint(x: viewSize.width, y: 0)
}
// MARK: Timer
fileprivate func startTimer() {
let selector = #selector(nextImage)
timer = Timer(timeInterval: 3.0, target: self, selector: selector, userInfo: nil, repeats: true)
RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes)
}
fileprivate func stopTimer() {
timer?.invalidate()
timer = nil
}
func nextImage() {
scrollView.setContentOffset(CGPoint(x: 2.0 * viewSize.width, y: 0), animated: true)
}
func touchImage(tap: UITapGestureRecognizer) {
if let index = tap.view?.tag {
delegate?.touchAt!(index: index)
}
}
// 初始化 滑动视图
fileprivate var scrollView: UIScrollView = {
let object = UIScrollView()
object.bounces = false
object.showsVerticalScrollIndicator = false
object.showsHorizontalScrollIndicator = false
object.isPagingEnabled = true
return object
}()
fileprivate var pageControl: UIPageControl = {
let object = UIPageControl()
object.hidesForSinglePage = true
object.currentPageIndicatorTintColor = UIColor.red
object.pageIndicatorTintColor = UIColor.gray
return object
}()
}
extension ImageScrollView: UIScrollViewDelegate {
// 当 scrollView 有偏移量时出发
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var page: Int = 0
var minDistance: CGFloat = CGFloat(MAXFLOAT)
for i in 0 ..< 3 {
let imageView = scrollView.subviews[i] as! UIImageView
// 由于当前页有用都是中间页,所以当 偏移量==图片宽度时,就是当前页。
let distance:CGFloat = abs(imageView.x - scrollView.contentOffset.x)
if distance < minDistance {
minDistance = distance
page = imageView.tag
}
}
pageControl.currentPage = page
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
stopTimer()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
startTimer()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updateScrollView()
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
updateScrollView()
}
}
extension UIView {
/// X值
open var x: CGFloat {
return self.frame.origin.x
}
/// Y值
open var y: CGFloat {
return self.frame.origin.y
}
/// 宽度
open var width: CGFloat {
return self.frame.size.width
}
///高度
open var height: CGFloat {
return self.frame.size.height
}
open var size: CGSize {
return self.frame.size
}
open var origin: CGPoint {
return self.frame.origin
}
}
调用的 ViewController
import UIKit
class ImageScrollViewController: UIViewController {
var imageScrollView = ImageScrollView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 150))
var data = [ImageScrollData]()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
edgesForExtendedLayout = .init(rawValue: 0)
self.title = "图片无限滚动"
self.view.addSubview(imageScrollView)
for i in 1 ... 6 {
let item = ImageScrollData(imageUrl: "image_scroll_0\(i).jpg", imageDescribe: nil)
data.append(item)
}
imageScrollView.data = data
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ImageScrollViewController: ImageScrollViewDelegate {
func touchAt(index: Int) {
print(index)
}
}