IOS仿寫知乎日報 - 主頁面

jopen 8年前發布 | 18K 次閱讀 iOS開發 移動開發

失業后發現自己的項目經驗太少了,除了公司的 App 和自己的游戲之外就幾乎為零了,所以必須要增加自己的實戰經驗。之前寫 UI 都是純代碼,為了熟悉 Storyboard,特地選擇了知乎日報來練手。

在之前這個 仿知乎日報的 iOS App 已經很出名了,我也是借鑒(就是抄)了部分的實現,圖片和 API 則是完全照搬。當然我也有我自己的不同之處,我的 UI 是盡可能采用 Storyboard+xib 來實現,另外也在一些細節上更貼近正版知乎日報。

這篇主要講一下首頁的實現,以下是動圖。

首頁結構

首頁主要由以下幾部分組成:

  1. 頂部的圖片輪播
  2. 下面的 TableView
  3. 頂部的其他小東西:展開側邊欄的按鈕,刷新控件,「今日新聞」的標題,和一個隨著 TableView 上滑出現的 View

上滑效果

先說一下 TableView 的實現。首先自定義一個 UITableViewCell,按照原版的把大小和位置設定好,這個不復雜,如下圖:

接下來弄 TableView,這個 TableView 是和父視圖同樣大小的,也就是充滿屏幕(注意,TableView 的父視圖不是 HomeViewController 的 UIView,而是其下的 Subview,輪播視圖以及其他控件都是放在這個 View 中的,至于為什么不直接放在 HomeViewController 的 View 里面,下一篇講側邊欄實現的時候再解釋……)。

在視覺上,第一感覺這個 TableView 好像應該是放在輪播圖片的下面的(也就是 TableView 的 top 貼著輪播圖片的 bottom),最開始我也是這樣做的。

但是后來做上滑效果的時候才發現這樣不行,因為上滑的時候需要 Cell 和輪播圖片同時向上移動,這樣 TableView 的 origin 就會改變, contentOffset 就不好計算了,而輪播圖片的移動全靠這個 offset 來決定。

我也試過將 TableView 的初始 contentOffset 設為輪播圖片的下面,但是滑上去就下不來了……所以,最后的解決辦法是將 TableView 鋪滿屏幕,上面加一個和輪播圖片同樣高度的 Header,完美!

上面說過,上滑效果全靠 TableView 的 contentOffset 來實現。HomeViewController 要實現 UIScrollViewDelegate 中的 scrollViewDidScroll: 這個方法。 在這個方法里面,加入以下代碼:

CGFloat offsetY = scrollView.contentOffset.y;
if (offsetY > 0) {
    if (!self.topView) {
        self.topView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 64)];
        [self.homeView insertSubview:self.topView belowSubview:self.showSideMenuButton];
    }
    CGFloat alpha = offsetY / 64;
    self.topView.backgroundColor = [UIColor colorWithRed:60.f / 255.f
                                                   green:198.f / 255.f
                                                    blue:253.f / 255.f
                                                   alpha:alpha];
    self.carouselViewTop.constant = -offsetY;

}

這段代碼做了這些:

  1. 首先判斷 contentOffset.y,如果大于零那就是在向上滑動。
  2. 如果 topView 不存在的話,就新生成一個,并且 topView 的背景顏色隨著滑動距離變化。
  3. 最后設置輪播圖片距離父視圖 top 的約束,這個變量是從 Storyboard 中拖過來的,將這個約束設為 -offsetY 就可以實現輪播圖片和 Cell 一起向上滑動的效果了。

還需要注意的是,展示側邊欄的按鈕,還有刷新控件和「今日新聞」的 UILabel,必須在層級上高于這個 topView,不然就會被 topView 蓋住。

有一個小細節的地方,困擾了我好久,就是 TableView 的第一個 Cell 和上面的輪播圖片始終有一段距離。最后各種嘗試和搜索后才找到解決方法:在 Storyboard 中選中 HomeViewController,在 Attributes Inspector 中把 Adjust Scroll View Insets 這個選項勾掉。

圖片輪播

這部分在實現思路上基本完全借鑒了上面提及的那個仿作,整個控件的容器是一個 UIScrollView ,里面并排擺放所有的圖片,還有一個 UIPageControl 來顯示對應的索引。

自定義一個 BannerView,用來顯示每一個輪播的圖片以及標題。上面的容器里裝的就是這個 View。

重要的輪播邏輯是這樣的:通過 API 獲取的輪播個數是5個,但是容器中的 View 是7個(5+2)。這一排的 BannerView 按照序號是這樣排列的,5-1-2-3-4-5-1,也就是把第一個和最后一個復制一份添加到數組的尾和頭。而 ScrollView 的初始 offset 是數組的第二個(也就是序號為1的)。這樣,1在右劃的時候會在左面顯示5,5在左劃的時候會顯示1。如果 ScrollView 的 contentOffset 停留在數組的第一個(5),那么就把 contentOffset 設為數組的第6個(正確順序的5)。同理,如果 ScrollView 的 contentOffset 停留在數組的最后一個(1),那么就把 contentOffset 設為數組的第2個(正確順序的1)。這樣就實現了一個可以無限循環的輪播。

相關代碼如下:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat offsetX = scrollView.contentOffset.x;
    if (offsetX ==  6 * kScreenWidth) {
        _scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
        _pageControl.currentPage = 0;
    } else if (offsetX == 0) {
        _scrollView.contentOffset = CGPointMake(5 * kScreenWidth, 0);
        _pageControl.currentPage = 4;
    } else {
        _pageControl.currentPage = offsetX/kScreenWidth - 1;
    }
}

具體運行時的效果如下:

刷新動畫

這里有兩部分內容,一是刷新控件的實現,二是刷新控件的控制。

刷新控件的是由兩部分組成的,一個 UIActivityIndicatorView 和由 CAShapeLayer 繪制的圓環。

定義一個 RefreshView,初始化中加入以下代碼:

- (void)customInit {
    _indicatorView = [[UIActivityIndicatorView alloc]initWithFrame:self.bounds];

    _grayCircleShapeLayer = [CAShapeLayer layer];
    _grayCircleShapeLayer.lineWidth = 2.f;
    _grayCircleShapeLayer.strokeColor = [UIColor grayColor].CGColor;
    _grayCircleShapeLayer.fillColor = [UIColor clearColor].CGColor;
    _grayCircleShapeLayer.opacity = 0;
    _grayCircleShapeLayer.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;

    _whiteCircleShapeLayer = [CAShapeLayer layer];
    _whiteCircleShapeLayer.lineWidth = 2.f;
    _whiteCircleShapeLayer.strokeColor = [UIColor whiteColor].CGColor;
    _whiteCircleShapeLayer.fillColor = [UIColor clearColor].CGColor;
    _whiteCircleShapeLayer.opacity = 0;
    _whiteCircleShapeLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width/2, self.width/2)
                                                                 radius:self.width/2
                                                             startAngle:M_PI_2 endAngle:M_PI * 5 / 2
                                                              clockwise:YES].CGPath;
    _whiteCircleShapeLayer.strokeEnd = 0;

    [self addSubview:_indicatorView];
    [self.layer addSublayer:_grayCircleShapeLayer];
    [self.layer addSublayer:_whiteCircleShapeLayer];
}

圓環由一個灰色的背景圓環和一個表示進度的白色圓弧組成,下拉過程中更新白色圓弧的長度,到指定位置后,整個圓環消失,開始 UIActivityIndicatorView 的動畫。

更新圓環進度的代碼如下:

-(void)updateProgress:(CGFloat)progress {
    if (progress <= 0) {
        _whiteCircleShapeLayer.opacity = 0;
        _grayCircleShapeLayer.opacity = 0;
    } else {
        _whiteCircleShapeLayer.opacity = 1;
        _grayCircleShapeLayer.opacity = 1;
    }

    if (progress > 1) {
        progress = 1;
    }
    _whiteCircleShapeLayer.strokeEnd = progress;
}

對刷新控件的控制其實和上滑的控制一樣,也在 HomeViewController 中的 scrollViewDidScroll: 中,這部分邏輯就是 offsetY < 0 的那一部分。

self.carouselViewHeight.constant = 220 - offsetY;
if (offsetY <= -kRefreshOffsetY * 1.5) {
   self.tableView.contentOffset = CGPointMake(0, -kRefreshOffsetY * 1.5);
} else if (offsetY <= 0 && offsetY >= -kRefreshOffsetY * 1.5) {
   if (self.isRefreshing) {
       [self.refreshView updateProgress:0];
   } else {
       [self.refreshView updateProgress:-offsetY / kRefreshOffsetY];
   }
}
if (offsetY < -kRefreshOffsetY && !scrollView.isDragging) {
   [self.refreshView startAnimation];
   self.isRefreshing = YES;
   dispatch_after(
                  dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
                  dispatch_get_main_queue(), ^{
                      [self.refreshView stopAnimation];
                      self.isRefreshing = NO;
                  });
}

這段代碼的邏輯有:

  1. 下拉時增加輪播圖片的高度。
  2. TableView 不是無限下拉的,只能下拉到一個指定的位置,超過的話,TableView 就不再下滑了。
  3. 下拉一段后再上滑,如果進入了刷新狀態,不顯示圓環;如果沒進入刷新狀態,那么就根據 下拉距離/下拉閾值 來更新圓環進度。
  4. 如果下拉距離達到了閾值并且松手了(沒有拖動),那么就進入刷新狀態。我這里做了個2秒刷新時間。

遺留

  1. 目前這個主頁只做了展示,點擊沒有任何效果。
  2. TableView 滑上再滑下的時候,topView 不會完全消失,可能會有淡淡地殘留,這點還沒有優化。
  3. 原版的輪播圖片底部和頂部有黑色陰影的漸變,這樣在純白的圖片下,按鈕和文字標題都可以清晰顯示出來,這點我也沒做。

另外,代碼請戳 github

來自: http://vulgur.me/2016/01/06/fake-zhihu-home/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!