往夜 -- 原来世界如此性感

往夜 -- 原来世界如此性感

头像

往夜预览

项目简介

  • 项目为纯代码编写, 项目周期为14天。
  • APP分为三个大模块
    • 往夜模块
    • 精选模块
    • 专题模块

项目展示分析

  • 整体结构
    Snip20170221_1

  • 用到的三方轮子
    Snip20170221_2

项目时间轴

2017年2月7日

Snip20170221_3

  • 网络工具类编写
#import <Foundation/Foundation.h>
@interface BaseNetManager : NSObject
//GET
+ (id)GET:(NSString *)path param:(NSDictionary *)param completionHandler:(void(^)(id obj, NSError *error))completionHandler;
//POST
+ (id)POST:(NSString *)path param:(NSDictionary *)param completionHandler:(void(^)(id obj, NSError *error))completionHandler;
@end
//GET 
@implementation BaseNetManager
+ (id)GET:(NSString *)path param:(NSDictionary *)param completionHandler:(void (^)(id, NSError *))completionHandler
{
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.requestSerializer.timeoutInterval = 10;
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", @"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
    return [manager GET:path parameters:param progress:^(NSProgress * _Nonnull downloadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@", task.currentRequest.URL.absoluteString);
        !completionHandler ?: completionHandler(responseObject, nil);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@", error);
    }];
}

//POST
+ (id)POST:(NSString *)path param:(NSDictionary *)param completionHandler:(void (^)(id, NSError *))completionHandler
{
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.requestSerializer.timeoutInterval = 15;
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", @"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
    return [manager POST:path parameters:param progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@", task.currentRequest.URL.absoluteString);
        !completionHandler ?: completionHandler(responseObject, nil);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@", error);
        !completionHandler ?: completionHandler(nil, error);
    }];
}
@end

2017年2月8日

Snip20170221_4

#import "YGTabBarController.h"
#import "YGHomeController.h"
#import "YGEssenceController.h"
#import "YGListController.h"
#import "YGListFlowLayout.h"
#import "YGPageController.h"
@interface YGTabBarController ()

@end

@implementation YGTabBarController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self allPropertySetup];
    [self setupAllControllers];
}

#pragma mark - 全局属性
- (void)allPropertySetup
{
    [UITabBar appearance].tintColor = YGRGBColor(67, 67, 67);
    [UINavigationBar appearance].tintColor = YGRGBColor(67, 67, 67);
    [[UITabBarItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: YGRGBColor(67, 67, 67)} forState:UIControlStateSelected];
    
    [UIImageView appearance].contentMode = UIViewContentModeScaleAspectFill;
    [UIImageView appearance].clipsToBounds = YES;
    [UICollectionView appearance].backgroundColor = YGBgColor;
    [UIImageView appearance].contentMode = UIViewContentModeScaleAspectFill;
    [UIImageView appearance].clipsToBounds = YES;
}

#pragma mark - 创建所有tabBar子控制器
- (void)setupAllControllers
{
    YGPageController *pageVC = [[YGPageController alloc] init];
    pageVC.tabBarItem.image = @"nav_ic_home_default".yg_image;
    pageVC.tabBarItem.selectedImage = @"nav_ic_home_selected".yg_image;
    pageVC.title = @"往夜";
    UINavigationController *homeNavi = [[UINavigationController alloc] initWithRootViewController:pageVC];
    
    YGEssenceController *essenceVC = [[YGEssenceController alloc] initWithStyle:UITableViewStylePlain];
    essenceVC.title = @"精选";
    essenceVC.tabBarItem.image = @"tab_btn_list_default".yg_image;
    essenceVC.tabBarItem.selectedImage = @"tab_btn_list_select".yg_image;
    UINavigationController *essenceNavi = [[UINavigationController alloc] initWithRootViewController:essenceVC];
    
    YGListController *listVC = [[YGListController alloc] initWithCollectionViewLayout:[[YGListFlowLayout alloc] init]];
    listVC.title = @"专题";
    listVC.tabBarItem.image = @"nav_ic_columns_default".yg_image;
    listVC.tabBarItem.selectedImage = @"nav_ic_columns_selected".yg_image;
    UINavigationController *listNavi = [[UINavigationController alloc] initWithRootViewController:listVC];
    
    self.viewControllers = @[homeNavi, essenceNavi, listNavi];
}


#pragma mark - 关闭设备自动旋转, 然后手动监测设备旋转方向来旋转avplayerView
-(BOOL)shouldAutorotate{
    return NO;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

2017年2月9日 - 2017年2月13日

Snip20170221_9

  • 往夜资讯, 我从稍微简单的页面入手 -- 精华模块
    Snip20170221_6

  • 整体为TableViewController, 里面分为三种Cell

    • 整体用masonry进行Layout布局
    • 界面整体比较简单, 其中Cell自适应高度, 我使用的UITableView-FDTemplateLayoutCell进行高性能自适应高度, 前百度forkingdog团队, 现在的滴滴打车的iOS大神Sunnyxx发布的高性能Cell自动高度计算框架.
//高性能计算行高
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    YGEssenceResponseFeedsItem *feedsItem = self.essenceArr[indexPath.section];
    if (feedsItem.type == 1) {
        return [tableView fd_heightForCellWithIdentifier:@"YGEssenceCommomCell" configuration:^(YGEssenceCommomCell *cell) {
            
            cell.iconIV.imageURL = feedsItem.image.yg_URL;
        }];
    }
    if (feedsItem.type == 0) {
        return [tableView fd_heightForCellWithIdentifier:@"YGEssenceImageCell" configuration:^(YGEssenceImageCell *cell) {
            [cell.iconIV setImageWithURL:feedsItem.image.yg_URL options:YYWebImageOptionIgnoreAnimatedImage];
        }];
    }
    
    return [tableView fd_heightForCellWithIdentifier:@"YGEssenceBigCell" configuration:^(YGEssenceBigCell *cell) {
        cell.titleLB.text = feedsItem.post.title;
        cell.detailLB.text = feedsItem.post.des;
        [cell.iconIV setImageWithURL:feedsItem.image.yg_URL options:YYWebImageOptionIgnoreAnimatedImage];
    }];
  
}
  • 精华模块主要是资讯信息, 里面用到的是UIWebView来显示, 里面有一个退出按钮的小动画, 让我稍微琢磨了一小下。
#pragma mark - 创建悬浮按钮
- (void)creatSuspendButton
{
    self.suspendBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.suspendBtn setBackgroundImage:[UIImage imageNamed:@"homeBackButton"] forState:UIControlStateNormal];
    self.suspendBtn.frame = CGRectMake(0, 0, 54, 54);
    [self.suspendBtn addTarget:self action:@selector(clickSuspendButton) forControlEvents:UIControlEventTouchUpInside];
    //创建悬浮按钮的window
    self.buttonWin = [[UIView alloc] initWithFrame:CGRectMake(25, YGScreenH - 60, 54, 54)];
    self.buttonWin.backgroundColor = [UIColor clearColor];
    //将buttonWin显示出来
    [self.view addSubview:self.buttonWin];
    [self.buttonWin addSubview:self.suspendBtn];
    
    
}
//点击悬浮按钮
- (void)clickSuspendButton
{
    self.buttonWin.hidden = YES;
    self.buttonWin = nil;
    [self.navigationController popViewControllerAnimated:YES];
}

#pragma mark - viewWillAppear
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.navigationController.navigationBarHidden = YES;
}
#pragma mark - viewViewDisappear
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    self.navigationController.navigationBarHidden = NO;
}
//按钮隐藏
#pragma mark - <UIScrollViewDelegate>
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    
    if (scrollView.contentOffset.y > _offsetY + 1) {
        [self suspensionWithAlpha:0];
    }
    else if (scrollView.contentOffset.y < _offsetY)
    {
        [self suspensionWithAlpha:1];
    }
}

//停止滚动式调用
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    _offsetY = scrollView.contentOffset.y;
}

//设置悬浮按钮的透明度
- (void)suspensionWithAlpha:(CGFloat)alpha
{
    [UIView animateWithDuration:0.3 animations:^{
        [self.buttonWin setAlpha:alpha];
    }];
}
  • 具体示例
    往夜资讯 webVie

2017年2月14日 - 2017年2月15日

Snip20170221_10

  • 栏目模块结构
    Snip20170221_8

  • 专题模块分为三次跳转

    • 第一个界面UICollectionViewController
    • 第二个界面UITableViewController
    • 第三个界面播放界面UIViewController
  • 专题模块 和 列表详细整体masonry布局

  • 列表详细播放中的播放我采用的是HcdCachePlayer

    • HcdCachePlayer对于整体播放, 缓存有着良好的集成
#import "YGListDetailMovieController.h"

@interface YGListDetailMovieController ()
{
    //播放器
    HcdCacheVideoPlayer *_play;
    
}
/** 背景 */
@property(nonatomic, strong) UIImageView *bgView;
/** 头像 */
@property(nonatomic, strong) UIImageView *headView;
/** 详细 */
@property(nonatomic, strong) UILabel *detailLb;
/** 影片简介 */
@property(nonatomic, strong) UILabel *constLabel;
/** 播放按钮 */
@property(nonatomic, strong) UIButton *playBtn;
// 计算属性, 屏幕大小
@property (nonatomic, assign) CGSize screenSize;

@end

@implementation YGListDetailMovieController



- (instancetype)initWithBgImageView:(NSString *)bgImageView titleView:(NSString *)titleView detailLabel:(NSString *)titleLabel url:(NSString *)url;
{
    self = [super init];
    if (self) {
        self.titleView = titleView;
        self.bgImageView = bgImageView;
        self.detailLabel = titleLabel;
        self.url = url;
        self.hidesBottomBarWhenPushed = YES;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self configUI];
}
//配置播放界面
- (void)configUI
{
    self.bgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, YGScreenW, YGScreenH)];
    self.bgView.userInteractionEnabled = YES;
    [self.bgView setImageURL:self.bgImageView.yg_URL];
    [self.view addSubview:self.bgView];
    
    self.headView = [[UIImageView alloc] init];
    [self.headView setImageURL:self.titleView.yg_URL];
    self.headView.userInteractionEnabled = YES;
    [self.bgView addSubview:self.headView];
    [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.offset(0);
        make.top.offset(64);
        CGFloat scale = 27 / 32.0;
        make.height.mas_equalTo(self.headView.mas_width).multipliedBy(scale);
    }];
    
    self.constLabel = [[UILabel alloc] init];
    self.constLabel.font = [UIFont boldSystemFontOfSize:18];
    self.constLabel.textColor = [UIColor whiteColor];
    self.constLabel.text = @"影片简介:";
    [self.bgView addSubview:self.constLabel];
    [self.constLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.offset(10);
        make.top.mas_equalTo(self.headView.mas_bottom).offset(10);
    }];
    
    self.detailLb = [[UILabel alloc] init];
    self.detailLb.text = self.detailLabel;
    self.detailLb.textColor = [UIColor whiteColor];
    self.detailLb.numberOfLines = 0;
    [self.bgView addSubview:self.detailLb];
    [self.detailLb mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.offset(10);
        make.right.offset(-10);
        make.top.mas_equalTo(self.constLabel.mas_bottom).offset(10);
    }];
    
    self.playBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.playBtn setImage:[UIImage imageNamed:@"play_button"] forState:UIControlStateNormal];
    [self.headView addSubview:self.playBtn];
    [self.playBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.offset(0);
        make.size.mas_equalTo(60);
    }];
    [self.playBtn addTarget:self action:@selector(playMovie) forControlEvents:UIControlEventTouchUpInside];
}

//播放视频
- (void)playMovie
{
    self.headView.hidden = YES;
    //    self.constLabel.hidden = YES;
    //    self.detailLb.hidden = YES;
    //点击后创建播放界面
    _play = [[HcdCacheVideoPlayer alloc] init];
    UIView *videoView = [[UIView alloc] initWithFrame:CGRectMake(0, 64, YGScreenW, YGScreenW * 27 / 32.0)];
    
    [self.view addSubview:videoView];
    //播放
    [_play playWithUrl:self.url.yg_URL showView:videoView andSuperView:self.view withCache:YES];
    NSLog(@"%@", NSHomeDirectory());
    
    NSLog(@"%f", [HcdCacheVideoPlayer allVideoCacheSize]);

    
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - 生命周期方法
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [_play stop];
#warning WAIT TODO:在这里先清除全部缓存
    [HcdCacheVideoPlayer clearAllVideoCache];
    
}

- (CGSize)screenSize
{
    return [UIScreen mainScreen].bounds.size;
}

@end
  • 视频展示
    往夜视频

2017年2月16日

  • 开始往夜模块编写
  • 插曲
    • 将Model层, View层全部写完, Controller层全部写完, 就差播放视频, 发现视频加密, 显示的全部垃圾广告。一晚白忙。此时凌晨3:35。

2017年2月17日 - 2月19日

Snip20170221_11

  • 往夜 “往” 模块分为两种Cell
    Snip20170221_13

    • 一种Cell为资讯展示
    • 一种Cell为视频展示

Snip20170221_12

  • 往夜 “夜” 模块

Snip20170221_14

  • ”夜“模块
    • 第一个界面为TableviewController
  • 点入”更多专题“为UICollectionViewController
  • 点入专题详细列表, 表头为拉伸图片, 下面是tableview

往夜资讯 更过栏目

  • 更多专题中有显示图片的专题
    • 我用的是MWPhotoBrowser 这个框架
      往夜资讯 栏目图片

2017年2月20日

Snip20170221_16

  • 收尾

整体分析

  • 项目主要以展示资讯,新闻,影视短片为主。
  • 项目架构简约,整体色调灰黑色。
  • 整个项目没有xib 或者 sb参与

项目中细节梳理

  • 头部无限轮播
  • 使用iCarousel三方搭建。
    • 本来有考虑使用自己写的一个无限轮播 框架,但是我没有别的封装,仅仅只是实现了高性能轮播。所以我是用了 iCarousel三方搭建。
#pragma mark - <ic Delegate>
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
    return self.loopArr.count;
}

- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
    if (!view) {
        view = [[UIView alloc] initWithFrame:carousel.bounds];
        UIImageView *iconIV = [[UIImageView alloc] init];
        [view addSubview:iconIV];
        [iconIV mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.left.right.offset(0);
            CGFloat scale = 38 / 64.0;
            make.height.mas_equalTo(iconIV.mas_width).multipliedBy(scale);
        }];
        iconIV.tag = 100;
    }
    YYAnimatedImageView *iconIV = [view viewWithTag:100];
    [iconIV setImageWithURL:[NSURL URLWithString:self.loopArr[index]] options:YYWebImageOptionIgnoreAnimatedImage];
    return view;
}
//只有变化时候才会来到这个方法
- (void)carouselCurrentItemIndexDidChange:(iCarousel *)carousel
{
    self.titleLb.text = self.bannersArr[carousel.currentItemIndex].post.title;
    self.pc.currentPage = carousel.currentItemIndex;
}

- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index
{
        YGEssenceWebController *webVC = [[YGEssenceWebController alloc] initWithAppView:self.bannersArr[index].post.appview];
        [self.navigationController pushViewController:webVC animated:YES];
}

- (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value
{
    if (option == iCarouselOptionWrap) {
        value = YES;
    }
    return value;
}

  • “往夜”模块头部横向滚动
    • 使用的是一款WMPageController的三方控件
@interface YGPageController ()

@end

@implementation YGPageController

//初始化方法
- (instancetype)init
{
    if (self = [super init]) {
        self.menuBGColor = YGRGBColor(249, 249, 249);
        
        self.menuViewStyle = WMMenuViewStyleLine;
        self.menuViewLayoutMode = WMMenuViewLayoutModeCenter;
        self.titleSizeNormal = 20;
        self.titleSizeSelected = self.titleSizeNormal;
        
        self.titleColorSelected = self.titleColorNormal;
        self.automaticallyCalculatesItemWidths = YES; //根据题目的内容自动算宽度
        self.itemMargin = 30; //题目的间距
        self.menuHeight = 44;
        self.showOnNavigationBar = YES;
    }
    return self;
}

- (NSArray<NSString *> *)titles
{
    return @[@"往", @"夜"];
}


- (NSInteger)numbersOfChildControllersInPageController:(WMPageController *)pageController
{
    return self.titles.count;
}

- (UIViewController *)pageController:(WMPageController *)pageController viewControllerAtIndex:(NSInteger)index
{
    if (index == 0) {
        YGHomeController *homeVC = [[YGHomeController alloc] init];
        return homeVC;
    }
    YGCategoryController *cateVC = [[YGCategoryController alloc] init];
    return cateVC;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


YG -- 阳光

业余时间的作品,每天晚上11点开始码代码,平均凌晨4点左右结束,早上9点还要忙自己的事情。
如果觉得写得还不错请给予一颗小⭐️⭐️

GitHub地址
博客园地址
往夜 项目地址

posted @ 2017-02-21 17:33  当黑夜爱上阳光。  阅读(1234)  评论(4编辑  收藏  举报