前记
一直都是一个懒散并且缺乏坚持的人。最近工作也不忙,再加上发现自己的开发能力还是很渣,于是暗下决心是否要重新做人,至少要对得起自己的Mac电脑吧。于是思来想去,做一个方便大家查看新闻,查询信息,并且能够方便生活的应用吧。
1, 总体构思
整个app主要的界面结构有些像简书的形式,但是因为功能不同,因此也会做一些改变。在这里我要感谢《iOS仿网易新闻、新浪新闻的新闻客户端》的作者,谢谢他分享了一个很好的模版。我在自己的这个小项目里参考了很多他的代码实现方式。
如果哪位朋友对于UIScrollView不太熟悉,可以参考我写的一篇小白文章《iOS 开发1--UIScrollView》。
2,已完成的部分
现在整个项目完成了新闻界面的展示部分,还处于很初级的阶段。在这一部分主要是通过建立三个scrollView来实现的。这个实现方式可能比较 low,但是基本达到了想要的效果。下面是实现的GIF:
3, 实现方法
1, 顶部轮播图实现
顶部轮播图这种实现方式似乎在这个时代已经烂大街了,但是作为一个菜鸟,我还是用自己的方式去实现了这个轮播图。其实实现的方法并不难,就是UIScrollView的一个实现。关键是要把contentSize和contentOffSize这两个属性设置对。至于自动轮播的实现实际上是使用NSTimer,这里有一个问题是在开发中发现的。那就是在手动拖动UIScrollView的过程中如何能让NSTimer停止,之后正常继续。通过查找相关内容最后发现需要实现UIScrollView的一个相关代理,代码如下:
-(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5.0f]];
}
这个轮播图使用了xib文件,其中一个xib中包含一个UIView,也就是说我只使用这个文件展示图片,另一个xib中包含UIScrollView。因此有关于UIScrollView delegate的内容我会在这一部分实现。下面的图片将说明目录结构:
下面是轮播图片的主要代码:
#import "TopImageView.h"
@interface TopImageView ()
@property (nonatomic,strong) CAGradientLayer *gradientLayer;
@end
@implementation TopImageView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
-(instancetype) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_view = [[[NSBundle mainBundle] loadNibNamed:@"TopImageView" owner:self options:nil] firstObject];
_view.frame = self.bounds;
[self addSubview:_view];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
_gradientLayer = [CAGradientLayer layer];
_gradientLayer.frame = self.imageView.bounds;
_gradientLayer.colors = @[
(id)[UIColor colorWithWhite:0.2 alpha:0.6].CGColor,
(id)[UIColor clearColor].CGColor,
(id)[UIColor clearColor].CGColor,
(id)[UIColor colorWithWhite:0.2 alpha:0.6].CGColor
];
_gradientLayer.locations = @[ @0.0, @0.4, @0.7, @1.0 ];
[self.imageView.layer addSublayer:_gradientLayer];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.gradientLayer.frame = self.imageView.bounds;
[CATransaction commit];
}
@end
#import "TopCarouselView.h"
#import "TopNewsImage.h"
#import "TopImageView.h"
@interface TopCarouselView ()<UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong,nonatomic) NSMutableArray *newsArr;
@property (strong,nonatomic) UIPageControl *pageControl;
@property (strong,nonatomic) NSTimer *timer;
@end
@implementation TopCarouselView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
-(instancetype) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_view = [[[NSBundle mainBundle] loadNibNamed:@"TopCarouselView" owner:self options:nil] firstObject];
_view.frame = self.bounds;
[self addSubview:_view];
_scrollView.delegate = self;
_pageControl.pageIndicatorTintColor = [UIColor grayColor];
_pageControl.currentPageIndicatorTintColor = [UIColor greenColor];
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0.f, self.view.frame.size.height - 20, kScreenWidth,20.f)];
_pageControl.currentPageIndicatorTintColor = [UIColor whiteColor];
_pageControl.pageIndicatorTintColor = [UIColor greenColor];
[self addSubview:_pageControl];
}
return self;
}
-(void) setTopNews:(NSArray *)topNewsArr {
if (!topNewsArr) {
return;
}
self.newsArr = [NSMutableArray arrayWithArray:topNewsArr];
[self.newsArr insertObject:topNewsArr.lastObject atIndex:0];
[self.newsArr addObject:topNewsArr.firstObject];
self.scrollView.contentSize = CGSizeMake(kScreenWidth * self.newsArr.count, self.bounds.size.height);
self.scrollView.contentInset = UIEdgeInsetsZero;
self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
self.scrollView.pagingEnabled = YES;
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = topNewsArr.count;
UIView *lastTopImageView = nil;
for (int i = 0; i < self.newsArr.count; i++) {
TopNewsImage *news = self.newsArr[i];
TopImageView *topImageView = [TopImageView new];
[self.scrollView addSubview:topImageView];
[topImageView.imageView sd_setImageWithURL:[NSURL URLWithString:news.imgsrc]];
// NSLog(@"image url:%@",news.imgsrc);
NSAttributedString *titleStrAttr = [[NSAttributedString alloc] initWithString:news.title attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:18],NSForegroundColorAttributeName:[UIColor whiteColor]}];
CGSize size = [titleStrAttr boundingRectWithSize:CGSizeMake(kScreenWidth - 30, 200) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size;
topImageView.titleLabel.frame = CGRectMake(15, 180, kScreenWidth-30,size.height);
topImageView.titleLabel.attributedText = titleStrAttr;
topImageView.titleLabel.textAlignment = NSTextAlignmentCenter;
[topImageView.titleLabel setTextColor:[UIColor grayColor]];
[topImageView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]];
[topImageView setTag:i+100];
[topImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(self.scrollView.mas_height);
make.width.mas_equalTo(kScreenWidth);
make.top.equalTo(self.scrollView.mas_top);
if (lastTopImageView) {
make.left.equalTo(lastTopImageView.mas_right);
}else {
make.left.equalTo(self.scrollView.mas_left);
}
}];
lastTopImageView = topImageView;
}
_timer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:@selector(displayNextStory) userInfo:nil repeats:YES];
}
#pragma mark -- Scroll View Delegate
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat offsetX = scrollView.contentOffset.x;
if (offsetX == (_newsArr.count-1)* kScreenWidth) {
_scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
self.pageControl.currentPage = 0;
}else if (offsetX == 0) {
_scrollView.contentOffset = CGPointMake((_newsArr.count - 2) *kScreenWidth, 0);
self.pageControl.currentPage = _newsArr.count - 2;
}else {
self.pageControl.currentPage = offsetX / kScreenWidth -1;
}
}
-(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5.0f]];
}
-(void) tap:(UIGestureRecognizer *) recognizer {
[self.delegate didSelectItemWithTag:recognizer.view.tag];
}
-(void) displayNextStory {
[_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x + kScreenWidth, 0)];
}
@end
2, 顶部导航栏的实现
其实这一部分的实现方式和顶部轮播图的实现方式并没有太多实质区别,需要注意的问题时如何实现点击导航栏按钮实现改变下面新闻tableView的改变。在这里我使用了block,在HomeViewController中实现导航栏的block回调,并且通过HomeViewController中的UIScrollView delegate去实现滑动新闻部分改变导航栏按钮。这一部分参考了很多《iOS仿网易新闻、新浪新闻的新闻客户端》的代码。但是在这一部分仍然存在问题,向左滑动新闻部分,无法正确的让导航栏的按钮变成红色。所以如果哪位大神知道原因希望能够帮忙解答。谢谢
#import "TopNavScrollBar.h"
#import "NSString+Extension.h"
static const CGFloat btnGap = 20.0f;
static const CGFloat scrollHeight = 50.0f;
@interface TopNavScrollBar () {
UIScrollView *_scrollView;
NSArray *_itemsWidth;
NSMutableArray *_btnXPostionArr;
UIView *_underscoreLine;
}
@property (nonatomic,strong) UIButton *btn;
@end
@implementation TopNavScrollBar
-(instancetype) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initConfig];
}
return self;
}
-(void) initConfig {
_items = [[NSMutableArray alloc] init];
_itemTitles = [NSArray array];
_lineColor = [UIColor colorWithRed:238.0/255.0 green:99.0/255.0 blue:99.0/255.0 alpha:0.8];
_itemColor = [UIColor clearColor];
_btnXPostionArr = [[NSMutableArray alloc] init];
[self viewConfig];
}
-(void) viewConfig {
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 10, kScreenWidth, scrollHeight)];
_scrollView.backgroundColor = [UIColor clearColor];
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
[self addSubview:_scrollView];
}
-(void) setupNavScrollBarWithItems:(NSArray *)itemsArr {
_itemTitles = itemsArr;
_itemsWidth = [self getItemsWidthWithItemTitles:_itemTitles];
CGFloat btnXPosition = 5.0f;
[_btnXPostionArr addObject:[NSNumber numberWithFloat:btnXPosition]];
int i = 0;
if (_itemsWidth.count) {
for (NSNumber *btnWidth in _itemsWidth) {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
CGFloat width = [btnWidth floatValue];
btn.frame = CGRectMake(btnXPosition, 5.0f, width+btnGap, 40);
btn.backgroundColor = self.itemColor;
[btn setTitle:self.itemTitles[i] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
[btn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(selectedItem:) forControlEvents:UIControlEventTouchUpInside];
if (i == 0) {
btn.selected = YES;
self.btn = btn;
}
btn.tag = i + 200;
// self.btn = btn;
[_scrollView addSubview:btn];
[self.items addObject:btn];
btnXPosition += width + btnGap;
[_btnXPostionArr addObject:[NSNumber numberWithFloat:btnXPosition]];
i++;
}
[_scrollView setContentSize:CGSizeMake(btnXPosition, 0)];
}
[self showLineWithBtnWidth:[_itemsWidth[0] floatValue]];
}
-(void) setCurrentItemIndex:(NSInteger)currentItemIndex {
_currentItemIndex = currentItemIndex;
CGFloat flag = kScreenWidth - 40;
CGFloat xPosiont = [_btnXPostionArr[_currentItemIndex] floatValue];
CGFloat btnWidth = [_itemsWidth[_currentItemIndex] floatValue];
if (xPosiont + btnWidth + 50 >= flag)
{
CGFloat offsetX = xPosiont + btnWidth - flag;
if (_currentItemIndex < [_itemTitles count]-1)
{
offsetX = offsetX + btnWidth;
}
[_scrollView setContentOffset:CGPointMake(offsetX, 0) animated:YES];
}
else
{
[_scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
[UIView animateWithDuration:0.1f animations:^{
_underscoreLine.frame = CGRectMake([_btnXPostionArr[_currentItemIndex] floatValue], 45.0 - 3, [_itemsWidth[_currentItemIndex] floatValue] + btnGap, 2.0f);
}];
UIButton *btn = self.items[currentItemIndex];
btn.selected = YES;
self.btn.selected = NO;
[self.btn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
self.btn = btn;
[self.btn setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
// [self.btn setTitle:@"Hello" forState:UIControlStateSelected];
}
-(NSArray *) getItemsWidthWithItemTitles:(NSArray *) itemTitles {
NSMutableArray *itemsWidth = [[NSMutableArray alloc] init];
for (NSString *itemTitle in itemTitles) {
CGSize maxSize = CGSizeMake(kScreenWidth, MAXFLOAT);
CGSize realSize = [itemTitle sizeWithFont:[UIFont boldSystemFontOfSize:16.0f] maxSize:maxSize];
[itemsWidth addObject:[NSNumber numberWithFloat:realSize.width]];
}
return itemsWidth;
}
-(void) showLineWithBtnWidth:(CGFloat) width {
_underscoreLine = [[UIView alloc] initWithFrame:CGRectMake([_btnXPostionArr[0] floatValue], 45.0 - 3.0, [_itemsWidth[0] floatValue] + btnGap, 2.0f)];
_underscoreLine.backgroundColor = [UIColor redColor];
[_scrollView addSubview:_underscoreLine];
}
-(void) selectedItem:(UIButton *) btn {
if (self.selectedBlock) {
self.selectedBlock(btn);
}
}
@end
3,新闻部分
从上面的动图可以看到,其实新闻列表的展示只是一个UITableView。我HomeViewController中加入了UIScrollView,然后通过自定义一个NewsViewController去展示不同内容的新闻。先看一下目录结构吧:
代码如下:
#import "HomeViewController.h"
#import "TopCarouselView.h"
#import "TopNewsImage.h"
#import "TopNavScrollBar.h"
#import "TopButtonsTitle.h"
#import "NewsViewController.h"
static const CGFloat topCarouselViewHeight = 190.0f;
static const CGFloat topScrollButtonsViewHeight = 50.0f;
static NSString *const urlStr = @"http://c.m.163.com/nc/article/headline/T1348647853363/0-10.html";
@interface HomeViewController ()<UIScrollViewDelegate> {
TopCarouselView *_topCarouselView;
TopNavScrollBar *_topNavScrollBar;
UIScrollView *_mainView;
NSInteger _currentItemIndex;
}
@property (nonatomic,strong) NSMutableArray *topNewsArr;
@property (nonatomic,strong) NSArray *topButtonTitles;
@property (nonatomic,strong) NSMutableArray *viewsArray;
@property (nonatomic,strong) NSArray *subViewControllers;
@property (nonatomic,strong) NSArray *contentArr;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// [self requestFromServer];
[self initConfig];
// [self viewConfig];
}
-(void) viewWillAppear:(BOOL)animated {
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) initConfig {
_viewsArray = [[NSMutableArray alloc] init];
_topButtonTitles = @[@"头条",@"国内",@"国际",@"娱乐",@"体育",@"科技",@"奇闻趣事",@"生活健康"];
_contentArr = @[@"guonei",@"world",@"huabian",@"tiyu",@"keji",@"qiwen",@"health"];
_topNewsArr = [NSMutableArray array];
_subViewControllers = [NSArray array];
[self requestDataWithUrlStr: urlStr];
}
-(void) viewConfig {
_topCarouselView = [[TopCarouselView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, topCarouselViewHeight)];
[_topCarouselView setTopNews:self.topNewsArr];
[self.view addSubview:_topCarouselView];
_topNavScrollBar = [[TopNavScrollBar alloc] initWithFrame:CGRectMake(0, topCarouselViewHeight, kScreenWidth, topScrollButtonsViewHeight)];
[_topNavScrollBar setupNavScrollBarWithItems:_topButtonTitles];
__weak __typeof(self) weakSelf = self;
_topNavScrollBar.selectedBlock = ^(UIButton *btn) {
[weakSelf selectedBtn:btn];
};
[self.view addSubview:_topNavScrollBar];
NSMutableArray *viewsArr = [NSMutableArray array];
for (int i = 0; i < self.topButtonTitles.count; i ++) {
NewsViewController *viewController = [[NewsViewController alloc] init];
viewController.title = _topButtonTitles[i];
if (i != 0) {
viewController.content = self.contentArr[i - 1];
}
[viewsArr addObject:viewController];
}
_subViewControllers = viewsArr;
[self initMainView];
}
-(void) initMainView {
_mainView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, topCarouselViewHeight + topScrollButtonsViewHeight, kScreenWidth, kScreenHeight - topCarouselViewHeight - topScrollButtonsViewHeight)];
_mainView.pagingEnabled = YES;
_mainView.showsVerticalScrollIndicator = NO;
_mainView.showsHorizontalScrollIndicator = NO;
_mainView.delegate = self;
_mainView.contentSize = CGSizeMake(kScreenWidth * _subViewControllers.count, 0);
[self.view addSubview:_mainView];
UIViewController *mainViewController = (UIViewController *) _subViewControllers[0];
mainViewController.view.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight - topCarouselViewHeight - topScrollButtonsViewHeight);
[_mainView addSubview:mainViewController.view];
[self addChildViewController:mainViewController];
}
-(void) requestDataWithUrlStr:(NSString *) urlStr {
// NSMutableArray *topNews = [NSMutableArray array];
[AFNetworkingTools requestWithType:HttpRequestTypeGet withUrlString:urlStr withParameters:nil withSuccessBlock:^(NSDictionary *object) {
[self fetchData:object];
} withFailureBlock:^(NSError *error) {
NSLog(@"Error:%@",error.localizedDescription);
} progress:nil];
}
-(void) fetchData:(NSDictionary *) object {
NSArray *response = [TopNewsImage mj_objectArrayWithKeyValuesArray:object[@"T1348647853363"][0][@"ads"]];
for (TopNewsImage *news in response) {
[self.topNewsArr addObject:news];
}
[self viewConfig];
}
-(void) selectedBtn:(UIButton *) btn {
NSInteger tag = btn.tag - 200;
[self setCurrentItemIndex:tag];
}
-(void) setCurrentItemIndex:(NSInteger) index {
[_mainView setContentOffset:CGPointMake(kScreenWidth * index, 0)];
}
#pragma mark -- UIScrollViewDelegate
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
_currentItemIndex = scrollView.contentOffset.x / kScreenWidth;
NSLog(@"Index: %ld", _currentItemIndex);
[_topNavScrollBar setCurrentItemIndex:_currentItemIndex];
UIViewController *viewController = (UIViewController *)_subViewControllers[_currentItemIndex];
viewController.view.frame = CGRectMake(_currentItemIndex * kScreenWidth, 0, kScreenWidth, _mainView.frame.size.height);
[_mainView addSubview:viewController.view];
[self addChildViewController:viewController];
}
@end
#import "NewsViewController.h"
#import "HeadlineNews.h"
#import "NewsTableViewCell.h"
#import "OtherNews.h"
@interface NewsViewController ()<UITableViewDelegate,UITableViewDataSource> {
NSInteger _page;
}
@property (nonatomic,strong) UITableView *tableView;
@property (nonatomic,strong) NSMutableArray *newsArr;
@end
@implementation NewsViewController
-(void) viewDidLoad {
[self initConfig];
[self initTableView];
// [self setupRefreshView];
[self requestData];
}
-(void) initConfig {
_page = 0;
_newsArr = [NSMutableArray array];
}
-(void) initTableView {
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 10, kScreenWidth, kScreenHeight) style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
}
-(void) setupRefreshView {
MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingTarget:self refreshingAction:@selector(updateData)];
header.lastUpdatedTimeLabel.hidden = YES;
header.stateLabel.hidden = YES;
self.tableView.mj_header = header;
[header beginRefreshing];
self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)];
}
-(void) requestData {
NSString *urlStr;
if (self.content == nil) {
urlStr = [NSString stringWithFormat:@"http://c.m.163.com/nc/article/headline/T1348647853363/%ld-20.html",(long)_page];
self.type = headlineNews;
}else {
urlStr = [NSString stringWithFormat:@"http://api.huceo.com/%@/other/?key=c32da470996b3fdd742fabe9a2948adb&num=20",self.content];
self.type = otherNews;
}
[AFNetworkingTools requestWithType:HttpRequestTypeGet withUrlString:urlStr withParameters:nil withSuccessBlock:^(NSDictionary *object) {
[self fetchData:object withType:self.type];
} withFailureBlock:^(NSError *error) {
NSLog(@"Error:%@",error.localizedDescription);
} progress:nil];
}
-(void) fetchData:(NSDictionary *) object withType:(newsType) type{
switch (self.type) {
case headlineNews:
{
NSArray *response = object[@"T1348647853363"];
NSArray *resultArr = [HeadlineNews mj_objectArrayWithKeyValuesArray:response];
NSInteger i = 0;
for (HeadlineNews *news in resultArr) {
NSLog(@"%ld,%@:%@",i,news.title,news.url_3w);
i++;
if (news.url_3w != nil) {
[self.newsArr addObject:news];
}
}
}
case otherNews:
{
NSArray *response = object[@"newslist"];
NSArray *resultArr = [OtherNews mj_objectArrayWithKeyValuesArray:response];
for (HeadlineNews *news in resultArr) {
[self.newsArr addObject:news];
}
}
break;
default:
break;
}
[self.tableView reloadData];
}
-(void) updateData {
}
-(void) loadMoreData {
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.newsArr.count;
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NewsTableViewCell *cell = [NewsTableViewCell cellWithTableView:tableView];
// cell.newsModel = self.newsArr[indexPath.row];
[cell setNewsModel:self.newsArr[indexPath.row] withType:self.type];
return cell;
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80;
}
@end
4,后记
现在整个项目还只是开始,其中有些功能还很不完善。限于本人水平,很多思路也是借鉴模仿别人的项目。后面会在不断完善这个项目的过程中更多的使用一些自己的想法。希望大家多指教帮助。
GitHub 地址