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()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章