iOS开发------仿知乎上下滑动切换页面

放松的时候看看知乎,生活不解的时候问问知乎,这貌似已经成为了生活中的一种习惯,它独特翻页方式也是本人喜欢的一个原因,通过上划与下滑进行页面的翻页,不必返回再进入下一个页面,显得非常的简介并且人性化,这里就模拟知乎进行一次模拟滚动换页,希望能给想做这种效果的人一种思路,实现的大体思路就是在ScrollView中嵌套ScrollView来感应达到切换效果。利用工作之余就做了一个类似功能的小Demo,当然距离知乎还差得远,但是基本原理应该如此,如果有更好的思路,也请告知一下,在这里先初始化一个Thanks() O(∩_∩)O

如果有解析不详细的地方,也欢迎去Demo的GitHub下载一下本人的Demo:https://github.com/YRunIntoLove/YSimilarZHPullDownDemo

看一下效果图(因为图片太大,所以草草的滑动了几下,Sorry - -):

效果图


梳理层次(顺序:子视图 -> 父视图):

  • 响应滑动的ScrollView,搭载自定义的customView,能够响应滑动反馈进行上下翻页的回调,并通过Delegate告知superView进行怎么样的翻页操作,Demo中叫做SimilarZHPullDownView
  • 进行整体翻页的ScrollView,搭载SimilarZHPullDownView,通过感知上下翻页的回调,设置自身的ContentOffset更改偏移量达到翻页的效果,Demo中叫做YSimilarZHPullDownMainView


SimilarZHPullDownView

需要一个滚动视图(ScrollView)作为底层视图,响应滑动操作,加载的标签以及自定义的视图都贴到这个滚动视图上,通过代理中的相关回调获取偏移量,判断进行什么样子的滚动。
这里选择通过懒加载的方式加载这个属性,与Objective-C不太一样,正好熟悉一下Swift中的懒加载

/// 懒加载底层的滚动视图
lazy var bottomScrollView : UIScrollView  =
{
    //当前视图的宽度
    let width = self.bounds.size.width

    //初始化滚动视图
    var scrollView:UIScrollView = UIScrollView(frame:CGRectMake(0, 0, width, self.bounds.size.height))

    //添加自定义视图
    scrollView.addSubview(self.customView!)

    //自定义视图的的高度
    var height = self.customView?.bounds.size.height

    //头页
    scrollView.addSubview(self.createLable(CGRectMake(0,-1 * self.responseHeight,width,30), title: self.headerTitle))

    //尾页
    scrollView.addSubview(self.createLable(CGRectMake(0,height!,width,self.responseHeight), title:self.footerTitle))

    //设置ContentSize的高度,保证不小于视图的高度
    height = (height > self.bounds.size.height ? height : self.bounds.size.height)

    scrollView.contentSize = CGSize(width: self.bounds.size.width, height: height!)
    scrollView.delegate = self;

    return scrollView
}()

一个描述最小响应幅度大小的变量,上拉或者下拉达到切换状态的最小幅度,或者说上拉、下拉达到换页响应的最小幅度

/// 响应的高度,就是滑动响应的最小幅度
final let responseHeight = CGFloat(60)

一个自定义视图,将需要展示的视图赋值给这个属性即可,但是必须设置好frame

/// 中间位置的自定义视图
var customView:UIView?

其他的相关属性:

/// 代理
weak var delegate:SimilarZHPullDownViewDelegate?

/// 标签上写的Title
var headerTitle = "上一篇"
var footerTitle = "下一篇"
var title = "Title"

/// 类型
var type:SimilarZHPullDownType = .Default

相关的类型属性通过枚举来完成,枚举格式如下:

//滑动响应的方式
enum PullType
{
    case PullTypeUp   //上翻页
    case PullTypeDown //下翻页
}

//视图的类型
enum SimilarZHPullDownType
{
    case Default    //默认中间页
    case Header     //第一页
    case Footer     //尾页
}

Swift是可以实现init方法的多态模式的,只不过需要用convenience 关键字来修饰一下init方法,并且这个关键词只能修饰init方法,只不过参数不一样而已,因此下面的就是便利构造方法:

//MARK: - FUNCTION
override init(frame: CGRect) {    
    super.init(frame: frame)
}

/**
*  便利构造方法
*/
@available(iOS 8.0,*)
convenience init(frame: CGRect ,custom:UIView)
{
    self.init(frame:frame)
    self.customView = custom
}

/**
*  便利构造方法
*/
@available(iOS 8.0,*)
convenience init(custom:UIView)
{
    self.init(frame:CGRectNull,custom:custom)
}

/**
*  便利构造方法
*/
@available(iOS 8.0,*)
convenience init(custom:UIView,title:String)
{
    self.init(custom:custom)
    self.title = title
}

在加载视图的时候添加上底层的scrollView,用self.scrollView实现懒加载的实现

override func layoutSubviews(){
    self.addSubview(self.bottomScrollView)
}

最后这个视图的最重要的,通过底层ScrollView的代理方法,获得偏移量的垂直偏移量,通过偏移量的大小判断是否进行滚动以及滚动的方式

//MARK: -UIScrollView Delegate

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    //获得偏移量
    let contentOffsetY = scrollView.contentOffset.y

    //当前响应的高度,因为下拉,所以响应距离为负数
    let beforeHeight = self.responseHeight * (-1)
    if(contentOffsetY < beforeHeight)
    {
        print("我是DownView,上一页!")
        //表示上一页
        self.delegate?.similarZHPullDownView(self, pullType: .PullTypeUp)
    }

    //偏移量是视图左上角,所以垂直坐标需要-滚动视图的高度
    else if(contentOffsetY > (scrollView.contentSize.height - scrollView.bounds.size.height + self.responseHeight))
    {
        print("我是DownView,下一页!")
        //表示下一页
        self.delegate?.similarZHPullDownView(self, pullType: .PullTypeDown)
    }

}

对外的传值和之前一样,用的是Delegate(委托) 传值,但是这里依旧可以使用闭包传值,以后可以尝试,定义的协议如下

protocol SimilarZHPullDownViewDelegate : class
{ 
    /**
     *  需要翻页进行的回调
     */
    @available(iOS 8.0,*)
    func similarZHPullDownView(similarZHPullDownView : SimilarZHPullDownView , pullType:PullType)   
}

这样视图就基本完成了

YSimilarZHPullDownMainView

这个视图是一个过渡作用的视图,是负责存储并且展示SimilarZHPullDownView的一个容器

定义两个对外的属性,提供默认值,第一个是第一个视图下拉出现的标签显示的String字段,第二个是最后一篇上拉出现标签显示的String字段,在外也可以通过设置来更改这两个存储属性

/***  第一篇上划以及最后一篇下滑显示的默认字样,可修改 ***/
var headerTitle:String = "已是第一篇"
var footerTitle:String = "最后一篇"

创建底层进行切换的ScrollView,这个ScrollView不能响应手动滚动,必须通过SimilarZHPullDownView的Delegate回调方法来更新contentOffset,达到切换的效果

/// 存放滚动页的主滚动页
lazy var scrollView:UIScrollView = {

    var scrollView:UIScrollView = UIScrollView(frame: CGRectMake(0,self.titleLabel.bounds.size.height,self.bounds.size.width,self.bounds.size.height - 50 - 64))

    scrollView.pagingEnabled = true//分页显示
    scrollView.showsVerticalScrollIndicator = false //不显示垂直滚条
    scrollView.scrollEnabled = false //不能滚动

    return scrollView   
}()

SimilarZHPullDownView上面显示title属性的标签,就是Demo中显示测试I的视图

/// 显示标题的标题
lazy var titleLabel:UILabel = {

    var label:UILabel = UILabel(frame: CGRectMake(0,0,self.bounds.size.width,50))
    label.textAlignment = .Center
    return label
}()

存储SimilarZHPullDownView对象的数据源,并在设置新值得时候进行数据的处理,是存储属性,willSet相当于Objcetive-C中的KVO,监听属性的新值

/// 存放预览视图的数组,默认为空数组,为存储属性,设置KVO
var pullViews:[SimilarZHPullDownView] = []
{
    willSet
    {
        self.pullViews = newValue

        //开始做处理
        self.pullViews.first?.headerTitle = self.headerTitle
        self.pullViews.first?.type = .Header

        self.pullViews.last?.footerTitle = self.footerTitle
        self.pullViews.last?.type = .Footer
    }
}

自定义的便利构造方法,require表示继承与该类的类必须实现这个init方法,当然这里可以不用加require,只是为了了解一下这个关键字而已(调皮0.0)

//MARK: - 构造方法
override init(frame: CGRect)
{
    super.init(frame: frame)

    self.addSubview(self.titleLabel)
    self.addSubview(self.scrollView)
}

/**
 *  便利构造方法
 */
@available(iOS 8.0,*)
required convenience init(frame: CGRect,pullViews:[SimilarZHPullDownView])
{
    self.init(frame:frame)
    self.pullViews = pullViews
}

在加载该视图的时候,设置底层滚动视图容纳域的大小以及添加数据源中所有的SimilarZHPullDownView对象

override func layoutSubviews() {

    //设置自身的contentSize
    self.scrollView.contentSize = CGSize(width: self.bounds.size.width, height: CGFloat(self.pullViews.count) * self.bounds.size.height)

    self.addChildView()
}

添加所有SimilarZHPullDownView视图的方法如下

//MARK: - 功能方法

/**
*  添加子视图
*/
@available(iOS 8.0,*)
func addChildView()
{
    //获取当前视图的高度和宽度
    let height = self.bounds.size.height
    let width = self.bounds.size.width

    for(var i:Int = 0 ; i < self.pullViews.count; i++)
    {
        //获取存储的SimilarZHPullDownView对象
        let pullDownView = self.pullViews[i]

        pullDownView.frame = CGRectMake(0, CGFloat(i) * height, width, height - 64 - 50)

        //设置代理
        pullDownView.delegate = self

        //添加视图
        self.scrollView.addSubview(pullDownView)
    }

    self.titleLabel.text = self.pullViews.first?.title
}

实现SimilarZHPullDownView Delegate的协议方法

// MARK: - SimilarZHPullDownView Delegate
func similarZHPullDownView(similarZHPullDownView: SimilarZHPullDownView, pullType: PullType)
{
    //获得当前的偏移量
    let contentOffset = self.scrollView.contentOffset

    //获得索引数
    let index = self.pullViews.indexOf(similarZHPullDownView)

    var paramNumber = CGFloat(1)

    switch pullType
    {
        case .PullTypeUp: //上翻页
            guard similarZHPullDownView.type == .Header else{

                paramNumber = CGFloat(-1)
                self.pullDone(paramNumber, contentOffset: contentOffset, index: index!)
                break
        }

        case .PullTypeDown://下翻页
            guard similarZHPullDownView.type == .Footer else{
                paramNumber = CGFloat(1)
                self.pullDone(paramNumber, contentOffset: contentOffset, index: index!)
                break
        }
    }
}

封装之后的滚动操作

/**
 *  滚动操作
 */
@available(iOS 8.0,*)
func pullDone(paramNumber:CGFloat,var contentOffset:CGPoint,index:Int)
{
    contentOffset.y += (paramNumber * self.bounds.size.height)
    self.scrollView.setContentOffset(contentOffset, animated: true)
    //显示即将出现的similarZHPullDownView对象的title
    self.titleLabel.text = self.pullViews[index + Int(paramNumber)].title
}

ViewController中进行加载过渡视图YSimilarZHPullDownMainView并作相关设置,Demo中的设置如下:

override func viewDidLoad() {
    super.viewDidLoad()

    self.navigationItem.title = "Yue"

    //创建DemoMainView对象

    //赋值
    /*** 设置表头与表位文字需要设置数据源之前 ***/
    demoMainView.headerTitle = "啦啦啦,我已经是第一篇了"
    demoMainView.footerTitle = "哈哈哈,我是最后一篇啦"
    demoMainView.pullViews = self.createPullDownViews()

    //添加视图
    self.view.addSubview(demoMainView)

}
/**
 *  创建测试PullDown视图
 */
@available(iOS 8.0,*)
func createPullDownViews() -> [SimilarZHPullDownView]
{
    var views :[SimilarZHPullDownView] = []

    for(var i:Int = 0 ; i < 3; i++)
    {
        let similarPullDownView = SimilarZHPullDownView(custom:self.createCustomView(),title:"测试\(i)")
        views.append(similarPullDownView)
    }

    return views
}
//随机创建UIView的对象
func createCustomView() -> UIView
{
    //初始化视图
    let view = UIImageView()

    //获得随机数
    //let count:UInt32 = arc4random_uniform(3) + UInt32(1)

    let count = 1//为了测试

    view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height * CGFloat(count))
    view.image = UIImage(named: "testImage.jpg")
    view.contentMode = .ScaleToFill

    return view
}

这样功能基本就会像Demp中那样实现响应上下拉切换页面,理解不深,如果有错误,也请指点一下,Thanks()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值