iOS-高仿優雅的好奇心日報

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

好奇心日報 關于好奇心有這樣一句話:

之所以叫好奇心日報,是因為我們認為好奇心是人類最美好的品質之一,我們篩選最有價值的信息,你能看到全球最有想法,最有關注的各界動態,以及他們背后的故事

正是出于好奇心偶然間下載安裝了這個 好奇心日報 ,打開這個APP首先吸引我的就是它簡潔干凈的界面和優雅的配色,整個首頁只有一個帶有logo的懸浮按鈕,然后就是帶有高清配圖不同與其他新聞平臺的新聞文章,圖標基本是簡單的黑、黃、白三種顏色加單線條圖形的設計。最后讓我下定決心仿它的是它那簡單到自然的動畫效果。說這么多當然不是給好奇心日報打廣告,只是想說我為什么寫這個項目;額,就這么簡單!

先看看 JFQDaily 效果:

如果你是一個iOS開發入門級的猿,有興趣話可以下載 JFQDaily源碼 然后結合本篇博客來看,相信你會有所收獲的,代碼寫的接地氣,注釋詳細!

一、準備工作

1、高仿,原生圖片圖標自然必不可少,利用 iOS images Extractor 抓取 好奇心日報 的圖片,如何使用iOS images Extractor抓取APP圖片,我的 iOS直播APP-點贊動畫的實現 這篇文章下面有介紹。

2、用青花瓷( Charles )提取密碼: kgya抓取 好奇心日報 數據。

3、篩選數據:

Paste_Image.png

上面是用chalers所抓取到的數據,分析并拿到你需要的數據就行,上面標注的都是項目中需要用到的數據,具體篩選過程就不再分析了,無非是打開 好奇心日報 看APP所展示的信息和抓到的數據做對比,找到相應的映射關系。如果你想走的更遠獨立行走是必要的。具體數據分析里的細節問題可以認真看源碼 Models 文件夾類的 數據模型 文件。好啦準備工作完成啦,開始動手建工程碼代碼吧!

二、項目文件結構

1、 AppDeleteggate文件夾

  • 放著AppDelegate .h和.m文件

2、Tools:工具類

  • NSString+JFMessage: NSString的類擴展,添加了計算文本高度和將毫秒轉換成日期的類方法
  • JFLoopView: 無限循環圖片輪播器,關于JFLoopView可以看 一行代碼實現圖片無限輪播器
  • MBProgressHUD+JFProgressHUD: MBProgressHUD的類擴展,添加了一個創建MBProgressHUD類方法,方便調用。
  • JFTimer: 定時器,在 一行代碼實現圖片無限輪播器 中有講到。
  • JFConfigFile: 這個里面是一些高頻的宏定義

3、Models:數據模型

  • 這里是根據之前使用chalers篩選的數據建立的數據模型

4、DataManager:數據管理器

  • 使用第三方框架AFNetworking創建的數據管理器,使用GET請求相關數據。

5、ViewControllers:控制器

  • JFHomeViewController: 首頁控制器
  • JFReaderViewController: 文章閱讀器控制器,使用WXWebView搭建。

6、views:界面

  • JFSuspensionView: 懸浮按鈕View
  • JFHomeNewsTableViewCell: 首頁cell,繼承自UITableViewCell
  • JFMenuView: 菜單界面
  • JFNewsClassificationView 新聞分類界面繼承自UIView

7、pods:項目所用到的第三方框架

  • Masonry :Masonry是目前最流行的AutoLayout框架。
  • AFNetworking :是一個非常方便的網絡請求庫,可以輕松實現各種網絡請求,比如經常使用的GET請求、POST請求等。
  • MJRefresh :李明杰老師寫的下拉刷新框架,使用方法很簡單。
  • MJExtension :json數據轉模型的框架,也是李明杰老師寫的,用法都很簡單。
  • SDWebImage :目前最受歡迎的圖片下載第三方框架,使用率很高。
  • MBProgressHUD :是一個顯示HUD窗口的第三方類庫,用法簡單。

各框架的具體用法網上很多資料,不再贅述!

三、代碼

1、搭建數據模型

這是我們用chalers抓取的json格式的數據,根據 MJExtension 的使用方法建立數據模型,先分別創建JFResponseModel、JFFeedsModel、JFPostModel、JFCategoryModel四個類,繼承自NSObject。

然后我們慢慢從json數據的最里層向外層聲明你所 需要 的對應參數,切記參數可以自行選擇創建,但是模型的類型一個不能少,參數名一定要一樣,如果命名有沖突 MJExtension 提供的有相應的方法進行映射。例如:將沖突的參數 description 名映射到 subhead

/** 設置模型屬性名和字典key之間的映射關系 */
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
    /* 返回的字典,key為模型屬性名,value為轉化的字典的多級key */
    return @{@"subhead":@"description"};
}

1.1 JFCategoryModel數據模型

#import
 
@interface JFCategoryModel : NSObject
 
/** 新聞類型(設計、娛樂、智能等)*/
@property (nonatomic, copy) NSString *title;
 
@end

1.2 JFPostModel數據模型

#import
 
@class JFCategoryModel;
 
@interface JFPostModel : NSObject
 
/** 新聞標題*/
@property (nonatomic, copy) NSString *title;
/** 副標題*/
@property (nonatomic, copy) NSString *subhead;
/** 出版時間*/
@property (nonatomic, assign) NSIntegerpublish_time;
/** 配圖*/
@property (nonatomic, copy) NSString *image;
/** 評論數*/
@property (nonatomic, assign) NSIntegercomment_count;
/** 點贊數*/
@property (nonatomic, assign) NSIntegerpraise_count;
/** 新聞文章鏈接(html格式)*/
@property (nonatomic, copy) NSString *appview;
 
@property (nonatomic, strong) JFCategoryModel *category;
 
@end

1.3 JFFeedsModel數據模型

#import
 
@class JFPostModel;
 
@interface JFFeedsModel : NSObject
 
/** 文章類型(以此來判斷cell(文章顯示)的樣式)*/
@property (nonatomic, copy) NSString *type;
 
/** 文章配圖 */
@property (nonatomic, copy) NSString *image;
 
@property (nonatomic, strong) JFPostModel *post;
 
@end

上面所提到的參數名沖突導致的參數名不一致問題的解決方法:

#import "JFPostModel.h"
 
@implementationJFPostModel
 
/** 設置模型屬性名和字典key之間的映射關系 */
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
    /* 返回的字典,key為模型屬性名,value為轉化的字典的多級key */
    return @{@"subhead":@"description"};
}
 
@end

1.4 JFResponseModel數據模型

#import
 
@class JFFeedsModel;
 
@interface JFResponseModel : NSObject
 
/** 下拉加載時判斷是否還有更多文章 false:沒有 true:有*/
@property (nonatomic, copy) NSString *has_more;
 
/** 下拉加載時需要拼接到URL中的key*/
@property (nonatomic, copy) NSString *last_key;
 
@property (nonatomic, strong) JFFeedsModel *feeds;
 
@end

1.5 JFBannersModel數據模型

JFBannersModel模型與JFFeedsModel參數一樣,所以繼承自JFFeedsModel就好

#import "JFFeedsModel.h"
 
@interface JFBannersModel : JFFeedsModel
 
@end

看起來模型很麻煩,只要你理清思路,掌握 MJExtension 用法,建立模型是很簡單的,最主要是模型建好后,在后面使用數據時會非常方便。

2、DataManager數據管理器

JFHomeNewsDataManager.h文件中添加一個請求新聞數據的方法和一個請求數據成功后回調的block方法。

#import
 
typedef void(^JFHomeNewsDataManagerBlock)(iddata);
 
@interface JFHomeNewsDataManager : NSObject
 
//  請求數據成功后返回新聞數據回調的block
@property (nonatomic, copy) JFHomeNewsDataManagerBlocknewsDataBlock;
 
//  請求新聞數據
- (void)requestHomeNewsDataWithLastKey:(NSString *)lastKey;
 
- (void)newsDataBlock:(JFHomeNewsDataManagerBlock)block;
 
@end

JFHomeNewsDataManager.m

#import "JFHomeNewsDataManager.h"
 
#import
#import "JFConfigFile.h"
 
#define kTimeOutInterval 10
 
@implementationJFHomeNewsDataManager
 
#pragma mark - 創建請求者
- (AFHTTPSessionManager *)manager {
    AFHTTPSessionManager *manager = [AFHTTPSessionManagermanager];
    manager.requestSerializer.timeoutInterval = kTimeOutInterval;
    manager.responseSerializer = [AFHTTPResponseSerializerserializer];
    //設置相應內容類(這里根據所請求的數據類型可自行選擇)
    manager.responseSerializer.acceptableContentTypes = [NSSetsetWithObjects:@"application/json",
                                                        @"text/html",
                                                        @"image/jpeg",
                                                        @"image/png",
                                                        @"application/octet-stream",
                                                        @"text/json",
                                                        nil];
    return manager;
}
 
#pragma mark - GET方式請求新聞數據
- (void)requestHomeNewsDataWithLastKey:(NSString *)lastKey {
    AFHTTPSessionManager *manager = [self manager];
    //拼接URL
    NSString *urlString = [NSStringstringWithFormat:@"http://app3.qdaily.com/app3/homes/index/%@.json?",lastKey];
 
    [managerGET:urlStringparameters:nilprogress:^(NSProgress * _NonnulldownloadProgress) {
 
    } success:^(NSURLSessionDataTask * _Nonnulltask, id  _NullableresponseObject) {
 
      //  JSON數據轉字典
        NSDictionary *dataDictionary = [NSJSONSerializationJSONObjectWithData:responseObjectoptions:NSJSONReadingMutableContainerserror:nil];
 
        if (self.newsDataBlock) {
            self.newsDataBlock([dataDictionaryvalueForKey:@"response"]);
        }
 
    } failure:^(NSURLSessionDataTask * _Nullabletask, NSError * _Nonnullerror) {
 
    }];
}
 
- (void)newsDataBlock:(JFHomeNewsDataManagerBlock)block {
    self.newsDataBlock = block;
}
 
@end

使用GET方法請求數據,將last_key參數拼接到URL中:

//拼接URL NSString *urlString = [NSString stringWithFormat:@"http://app3.qdaily.com/app3/homes/index/%@.json?",lastKey];

打斷點看我們請求到的JSON數據轉換成字典dataDictionary里的內容:

response里的數據是我們需要處理的,所以用 valueForKey: 方法拿到其數據返回給JFHomeViewController:

[dataDictionaryvalueForKey:@"response"]

數據處理部分到此算是告一段落,接下來就是要把數據展示到界面上,然后實現好奇心日報的交互動畫。

3、UI布局

3.1 項目中實現的三種不同的cell樣式

cellType對應的就是數據模型JFFeedsModel中的type

  • cellType = 0,UITableViewCell的樣式是上方新聞配圖、然后是新聞標題,最下面是副標題。

  • cellType = 1,UITableViewCell的樣式是新聞配圖在右側,左側是新聞標題,在其下面是新聞種類、評論數和點贊數。

cellType = 2,UITableViewCell的樣式和cellType = 0時基本一致,就多了最下面的新聞種類、評論數和點贊數。

顯然用蘋果提供的cell是不行的,所以創建JFHomeNewsTableViewCell繼承自UITableViewCell,然后我們來自定義cell。

JFHomeNewsTableViewCell.h文件中聲明屬性:

#import
 
@interface JFHomeNewsTableViewCell : UITableViewCell
 
/** cell的類型(0、1、2)*/
@property (nonatomic, copy) NSString *cellType;
/** 配圖*/
@property (nonatomic, copy) NSString *newsImageName;
/** 標題*/
@property (nonatomic, copy) NSString *newsTitle;
/** 副標題*/
@property (nonatomic, copy) NSString *subhead;
 
/**
*  新聞類型(設計、智能、娛樂等)
*/
@property (nonatomic, copy) NSString *newsType;
/** 該條新聞的評論數*/
@property (nonatomic, copy) NSString *commentCount;
/** 點贊數*/
@property (nonatomic, copy) NSString *praiseCount;
/** 新聞發布時間*/
@property (nonatomic, assign) NSIntegertime;
 
@end

動態的設置cell的樣式(frame)是在 - (void)layoutSubviews; 方法中,這里我把相關代碼都放在了 - (void)customUI; 方法中,這里使用了 Masonry 自動布局。

- (void)layoutSubviews {
    [super layoutSubviews];
    [self customUI];
}

使用 Masonry 時有一點一定要 注意 ,必須先把子控件添加到父控件上才能用 Masonry 去自動布局,否則父控件上沒有相應的子控件何談布局呢。

在JFHomeViewController.m文件中重寫UITableView的 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; 代理方法來動態設置cell的高度;同樣是根據類型判斷。

/// 根據cell類型返回cell高度
- (CGFloat)tableView:(UITableView *)tableViewheightForRowAtIndexPath:(NSIndexPath *)indexPath {
    JFHomeNewsTableViewCell *cell = self.cell;
    if ([cell.cellTypeisEqualToString:@"0"]) {
        return 330;
    }else if ([cell.cellTypeisEqualToString:@"2"]) {
        return 360;
    }else {
        return 130;
    }
}

3.2 懸浮按鈕(JFSuspensionView)

3.2.1 懸浮按鈕實現的原理:

  • 其實很簡單,就是在JFHomeViewController控制器的View上添加一個JFSuspensionView,只是要在homeNewsTableView之上,使用下面方法或者使用 - (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview; 方法,此時當你滑動UITableView的時候按鈕就是懸浮不動的。
- (void)loadView {
    [super loadView];
 
    [self.viewaddSubview:self.homeNewsTableView];
    [self.viewaddSubview:self.jfSuspensionView];
}

3.2.2 在JFSuspension.h文件中用枚舉定義了懸浮按鈕的四種Tag類型和四種block回調方法,其實四種Tag類型一一對應四個回調函數,通過判斷Tag類型來執行相應的block函數。

#import
 
/// 懸浮按鈕種類(tag)枚舉
typedef NS_ENUM(NSInteger, JFSuspensionButtonStyle) {
    JFSuspensionButtonStyleQType = 1,  //  Qlogo樣式 (彈出JFMenuView)
    JFSuspensionButtonStyleCloseType,  //  關閉樣式(關閉JFMenuView)
    JFSuspensionButtonStyleBackType,    //  返回樣式(返回到JFHomeViewController根View)
    JFSuspensionButtonStyleBackType2    //  返回樣式2(返回到JFMenuView)
};
 
typedef void(^JFSuspensionViewBlock)();
 
@interface JFSuspensionView : UIView
 
/** 懸浮按鈕,設置按鈕樣式(tag)*/
@property (nonatomic, assign) NSIntegerJFSuspensionButtonStyle;
 
/** 彈出菜單界面*/
@property (nonatomic, copy) JFSuspensionViewBlockpopupMenuBlock;
 
/** 關閉菜單界面*/
@property (nonatomic, copy) JFSuspensionViewBlockcloseMenuBlock;
 
/** 返回到homeNewsViewController*/
@property (nonatomic, copy) JFSuspensionViewBlockbackBlock;
 
/** 返回到JFMenuView*/
@property (nonatomic, copy) JFSuspensionViewBlockbackToMenuViewBlock;
 
- (void)popupMenuBlock:(JFSuspensionViewBlock)block;
 
- (void)closeMenuBlock:(JFSuspensionViewBlock)block;
 
- (void)backBlock:(JFSuspensionViewBlock)block;
 
- (void)backToMenuViewBlock:(JFSuspensionViewBlock)block;
 
@end

懸浮按鈕綁定的 - (void)clickSuspensionButton:(UIButton *)sender 點擊事件處理方法,通過tag判斷需執行的事件。

- (void)clickSuspensionButton:(UIButton *)sender {
    if (_suspensionButton.tag == JFSuspensionButtonStyleQType || _suspensionButton.tag == JFSuspensionButtonStyleCloseType) {
          //需要做的事情...
    }
 
    //彈出菜單界面
    if (_suspensionButton.tag == JFSuspensionButtonStyleQType) {
          //需要做的事情...
    }
 
    //關閉菜單界面
    if (_suspensionButton.tag == JFSuspensionButtonStyleCloseType) {
      //需要做的事情...
    }
 
    //返回到homeNewsViewController
    if (_suspensionButton.tag == JFSuspensionButtonStyleBackType) {
          //需要做的事情...
    }
 
    //返回到JFMenuView
    if (_suspensionButton.tag == JFSuspensionButtonStyleBackType2) {
          //需要做的事情...
    }
}

3.3.3 上滑隱藏懸浮按鈕,下滑顯示懸浮按鈕。

在JFHomeViewController中實現UIScrollDelegate代理方法

#pragma mark --- UIScrollDelegate
/// 滾動時調用
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView.contentOffset.y > _contentOffset_Y + 80) {
        [self suspensionWithAlpha:0];
    } else if (scrollView.contentOffset.y 

3.3 菜單(JFMenuView)界面布局

下圖層級關系對照源碼看,清晰明了!(JFNewsClassificationView層級關系和JFMenuView一樣)

懶加載模糊層:

- (UIVisualEffectView *)blurEffectView {
    if (!_blurEffectView) {
        UIBlurEffect *blurEffect = [UIBlurEffecteffectWithStyle:UIBlurEffectStyleDark];
        _blurEffectView = [[UIVisualEffectViewalloc] initWithEffect:blurEffect];
        _blurEffectView.frame = self.frame;
    }
    return _blurEffectView;
}

四、彈簧動畫效果

彈簧動畫效果是用 非死book 開源的 pop動畫引擎 ,簡單使用的話推薦看: POP介紹與使用實踐(快速上手動畫) ,下面這段代碼就是實例化了一個彈簧動畫(POPPropertyAnimation),用來實現菜單界面的彈出效果。

/** pop動畫
*  POPPropertyAnimation    動畫屬性
*  view                    動畫對象
*  offset                  偏移量
*  speed                   動畫速度
*/
- (void)popAnimationWithView:(UIView *)viewOffset:(CGFloat)offsetspeed:(CGFloat)speed {
    POPSpringAnimation *popSpring = [POPSpringAnimationanimationWithPropertyNamed:kPOPLayerPositionY];
    popSpring.toValue = @(view.center.y + offset);
    popSpring.beginTime = CACurrentMediaTime();
    popSpring.springBounciness = 11.0f;
    popSpring.springSpeed = speed;
    [viewpop_addAnimation:popSpringforKey:@"positionY"];
}

菜單的隱藏動畫使用的是蘋果提供的UIView的動畫,如下隱藏菜單的頂部View和底部View。

/// 動畫隱藏headerView和footerView
- (void)hideMenuViewAnimation {
    [UIViewanimateWithDuration:0.1 animations:^{
        [self headerViewOffsetY:-KHeaderViewH];
        [self footerViewOffsetY:JFSCREENH_HEIGHT];
    } completion:^(BOOL finished) {
        //隱藏JFMenuView
        [self setHidden:YES];
    }];
}

如上用原生的UIView動畫加 pop動畫引擎 就可以實現懸浮按鈕和菜單view的彈簧效果,若想動畫效果更加自然,是需要耐心的調整 pop動畫引擎 屬性。

總結:這篇文章不是一個細致講解這個項目的文檔,結合這篇文章去看 JFQDaily源碼 相信你會有所收獲的,畢竟是三天寫出來的東西,代碼可能有不規范的地方,歡迎指出糾正,出于好奇心和喜歡,一氣呵成的寫完了主要功能。

來自:http://ios.jobbole.com/90974/

 

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