博客园第三方客户端-i博客园正式发布App Store

博客园第三方客户端-i博客园正式发布App Store

1. 前言



算来从15年8月到现在自学iOS已经快7个月了,虽然中间也是断断续续的,不过竟然坚持下来了。年后要找实习啦,于是萌生了一个想法 —— 写一个app练练手。这次我没弄后台了,直接使用了博客园的open api(嘿嘿)。之前也做过一个app,叫做魔界-魔术,前后端都是我弄的,不过后端使用的是Bmob后端云(一个Baas服务),但是作为第一个app,代码上感觉很混乱,而且基本上都是用的第三方控件。这次的i博客园是我完全独立开发的(包括UI设计),整体使用的是MVC模式,并且尽量不去使用别人第三方控件(虽然还是用了。后面会提到具体使用)。

先放出几张app的gif预览图片:

 

Appstore地址:

大家可以在AppStore搜索i博客园。或者扫描下面二维码:

2. 使用的资料和工具


  • 博客园官方open web api网址:
  1. http://wcf.open.cnblogs.com/news/help (新闻)
  2. http://wcf.open.cnblogs.com/blog/help (博客)
  • 使用到的第三方控件

    • AFNetworking
    • SDWebImage
    • HMSegmentedControl(Segmented Control)
    • RESideMenu (侧滑控制器视图)
    • MJRefresh
    • Masonry (AutoLayout)
    • UITableView+FDTemplateLayoutCell (动态计算UITableViewCell的高度)
    • XMLDictionary (解析XML文件,因为博客园web api传回来的是xml数据)
  • UI资源和工具

3. 解决的问题



问题一:实现引导页(不是启动页)上的RippleButton(有水波涟漪动画的按钮,第一张gif图片上的那个粉红色按钮)

解决思路:

1. 使用UIBesierPath构建一个圆形的path

UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:pathFrame cornerRadius:self.layer.cornerRadius];

2. 将上面的path赋值给circleShape(CAShapeLayer对象)的path属性,同时添加该circleShape到RippleButton(UIView类型)上

CAShapeLayer *circleShape = [CAShapeLayer layer];
circleShape.path = path.CGPath;
[self.layer addSublayer:circleShape];

3. 这时,就可以使用Core Animation来操作RippleButton的layer了,细节我就不详细说了,无非是通过动画控制圆圈的scale和alpha

CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.5, 2.5, 1)];

CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
alphaAnimation.fromValue = @1;
alphaAnimation.toValue = @0;

CAAnimationGroup *animation = [CAAnimationGroup animation];
animation.animations = @[scaleAnimation, alphaAnimation];
animation.duration = 1.0f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[circleShape addAnimation:animation forKey:nil];

4. 但是如果仅仅添加一个circleShape,那么不会有多个水波散开的效果。于是我又将上述123步代码封装成createRippleEffect函数,并添加到定时器中

- (void)setupRippleTimer
{
    __weak __typeof__(self) weakSelf = self;
    NSTimeInterval repeatInterval = self.repeatInterval;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, repeatInterval * NSEC_PER_SEC, 0);

    __block NSInteger count = 0;
    dispatch_source_set_event_handler(self.timer, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            count ++;
            // 水波纹重复次数,默认-1,表示永久
            if (self.repeatCount != -1 && count > weakSelf.repeatCount) {
                [weakSelf stopRippleEffect];
                return;
            }
            [weakSelf createRippleEffect];
        });
    });
}

问题二:48小时阅读和十日推荐中使用了UICollectionView,自定义了UICollectionViewLayout,实现轮盘旋转的效果(部分代码参考了AWCollectionViewDialLayout

解决思路:

1. 首先得知道自定义UICollectionViewLayout的具体流程

实现自定义的UICollectionViewLayout的具体流程请参考这篇文章,很详细!

2. 整个自定义UICollectionViewLayout实现过程中,最核心的要数layoutAttributesForElementsInRect这个函数了

2.1 首先根据rect的y值来计算出哪几个cell在当前rect中:

// 在rect这个区域内有几个cell,返回每个cell的属性
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *layoutAttributes = [NSMutableArray array];

    CGFloat minY = CGRectGetMinY(rect);
    CGFloat maxY = CGRectGetMaxY(rect);
    // 获取到rect这个区域的cells的firstIndex和lastIndex,这两个没啥用,主要是为了获取activeIndex
    NSInteger firstIndex = floorf(minY / self.itemHeight);
    NSInteger lastIndex = floorf(maxY / self.itemHeight);
    NSInteger activeIndex = (firstIndex + lastIndex) / 2; // 中间那个cell设为active
    // maxVisiableOnScreeen表示当前屏幕最多有多少cell
    // angularSpacing表示每隔多少度算一个cell,因为这里是轮盘,每个cell其实看做一个扇形
    NSInteger maxVisiableOnScreeen = 180 / self.angularSpacing + 2;

    // firstItem和lastItem就表示哪几个cell处于当前rect
    NSInteger firstItem = fmax(0, activeIndex - (NSInteger)maxVisiableOnScreeen/2);
    NSInteger lastItem = fmin(self.cellCount, activeIndex + (NSInteger)maxVisiableOnScreeen/2);
    if (lastItem == self.cellCount) {
        firstItem = fmax(0, self.cellCount - (NSInteger)maxVisiableOnScreeen);
    }
    // 计算rect中每个cell的UICollectionViewLayoutAttributes
    for (NSInteger i = firstItem; i < lastItem; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attributes= [self layoutAttributesForItemAtIndexPath:indexPath];
        [layoutAttributes addObject:attributes];
    }

    return layoutAttributes;
}

2.2 计算每个cell的UICollectionViewLayoutAttributes

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 默认offset为0
    CGFloat newIndex = (indexPath.item + self.offset);
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    attributes.size = self.cellSize;

    CGFloat scaleFactor, deltaX;
    CGAffineTransform translationT;
    CGAffineTransform rotationT;

    switch (self.wheetAlignmentType) {
        case WheetAlignmentTypeLeft:
            scaleFactor = fmax(0.6, 1 - fabs(newIndex * 0.25));
            deltaX = self.cellSize.width / 2;
            attributes.center = CGPointMake(-self.radius + self.xOffset, self.collectionView.height/2+self.collectionView.contentOffset.y);
            rotationT = CGAffineTransformMakeRotation(self.angularSpacing * newIndex * M_PI / 180);
            translationT = CGAffineTransformMakeTranslation(self.radius + deltaX * scaleFactor, 0);
            break;
        case WheetAlignmentTypeRight:
            scaleFactor = fmax(0.6, 1 - fabs(newIndex * 0.25));
            deltaX = self.cellSize.width / 2;
            attributes.center = CGPointMake(self.radius - self.xOffset  + ICDeviceWidth, self.collectionView.height/2+self.collectionView.contentOffset.y);
            rotationT = CGAffineTransformMakeRotation(-self.angularSpacing * newIndex * M_PI / 180);
            translationT = CGAffineTransformMakeTranslation(- self.radius - deltaX * scaleFactor, 0);
            break;
        case WheetAlignmentTypeCenter:
            // 待实现
            break;
        default:
            break;
    }

    CGAffineTransform scaleT = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
    attributes.alpha = scaleFactor; // alpha和scaleFactor一致
    // 先scale缩小,在translation到对应位置(因为是扇形,每个cell的x值和对应位置有关),最后rotation(形成弧形)
    attributes.transform = CGAffineTransformConcat(scaleT, CGAffineTransformConcat(translationT, rotationT));
    attributes.zIndex = indexPath.item;

    return attributes;
}

问题三:实现带动画的TabBarItem

解决思路:

不详细说了,我将代码提交到了Github - animated-tab-bar-Objective-CPJXAnimatedTabBarController is a Objective-C version of RAMAnimatedTabBarController(https://github.com/Ramotion/animated-tab-bar))。

主要就是自定义UITabBarItem,以及自定义UITabBarItem的AutoLayout构建。代码封装的很好,尤其动画实现部分,结构很清晰,符合OOP思想。

问题四:博客园使用的xml形式的open web api。解析困难。

解决思路:

这里我还是使用MVC思路,使用AFNetworking获取到XML数据,再使用XMLDictionary来解析XML数据(本质是NSXMLParserDelegate),并将其转化为Model(需要自己实现)。

关于NSXMLParserDelegate可以参考这篇文章 - iOS开发之解析XML文件

问题五:设计部分,不是很擅长,每个页面的布局都需要想很久,尽量做得简洁,有科技风。

解决思路:

基本上就是多看别人app设计,模仿,或者自己想啊想。也是第一次用Sketch,话说还挺好用的。

4. 存在问题和TODO


  • 分享到微信微博等等,准备使用友盟。
  • 涉及到UIWebView界面的排版,很丑。不是很懂CSS、JS、HTML5。之前为了一个图片适配搞了半天,其实只要在<head>中加上"img{max-width:100%%;height:auto;}"就行。恳请大家指点一下我。
  • 使用自定义TabBarItem后,隐藏TabBar很麻烦。
  • 离线阅读
  • ……

5. 后记



自己亲手去写代码确实感觉上是不一样,很多细节问题,虽然不难,但是很有挑战性。代码目前很挫,后面修改规范点,准备放到Github上。

时间: 2024-10-13 16:23:08

博客园第三方客户端-i博客园正式发布App Store的相关文章

【原】博客园第三方客户端-i博客园App开源

[原]博客园第三方客户端-i博客园App开源 本文转载请注明出处 —— polobymulberry-博客园 1.前言 目前i博客园App已经更新到2.0.0版本了,使用了最新的博客园Web API.相比于第一个版本,添加了很多新的功能,也修改了很多功能.整体来说改动比较大,代码也比较混乱.所以趁着清明假期,把代码好好整理了一番.目前基本的架构已成型(当然,后期还需要不断优化),但App基本功能方面还有很多需要添加的,后面会集中把App功能完善. 上面简单介绍了下目前App的情况,回到开源的话题

又一款博客园Android客户端低调推出

每天都会逛博客园,业余时间自己做了一款博客园Android客户端,和目前已有的一些第三方博客园客户端类似,数据来源请求的是博客园的开放接口,但是在体验上希望能做的更好,同时设计风格依照Android的原生效果(不是Android L的material design,毕竟现在还没有几款机型), 目前客户端支持的功能还不是非常多,但是涵盖了基本的使用需求,特别适合上下班,晚上使用.根据现有的借口,v1版已实现的具体功能如下有: 最新的首页博客数据分页浏览: 最新的新闻数据: 博客阅读排行榜: 博客园

博客园 Mac客户端 electron 源码

博客园 Mac 客户端 2.0-Beta 的源码已发布在 https://github.com/LunaGao/cnblog-electron https://github.com/LunaGao/cnblog-electron https://github.com/LunaGao/cnblog-electron 重要的事情说三遍 未提交内容 app/webapi/oauthWebApi.js 其中包含账号等敏感信息 app/build 内容由各个文件生成 node_modules 内容是第三方

博客园—Android客户端

如果有一个博客园客户端支持:点赞.支持.反对.评论.@.收藏等等等等,那么博客园的新闻.博文评论区是否能更加活跃?园友能否更加积极?进步能否更加快速?博客园能否更加精彩?一起来看看吧. 笔者业余开发的博客园Android客户端版本首次在博客园公布,希望广大园友多多支持,极速省流稳定,当然阅读界面由于每个人写的博客千差万别,不像新闻一样有专业小编统一的编码,笔者仅能用正则做到尽量格式化,做的不好的地方请见谅. 当前版本支持的功能有以下这些:博客园所有分类查看.支持博客新闻点支持和反对.评论支持和反

基于react-native实现的博客园手机客户端

从五月初开始,中间抽出断断续续的业余时间,基于react-native,在博客园现有开放接口的基础上,实现了一个博客园手机客户端.由于博主没有ios开发环境,所以当前仅适配了android版本.出于学习和实践react-native的目的,UI呈现这一块完全凭自己所好,bug也有一些,更多是希望抛砖引玉,让大家感受到react/react-native为移动端开发带来的全新视觉和可能性. 一,相关链接1,react-nativehttps://github.com/facebook/react-

博客园 Mac客户端 1.0

从11月10号,开始打算写这个客户端,到今天,终于写出来了第一版. 有些显得简陋,有点呆板.欢迎大家批评指正,额···别太刻薄···. 初学osx开发,也算是找个项目来做做~(文章最后有下载链接) 此次版本为:1.0 此版本实现内容: 1. 博客列表展示. 2. 博客内容展示. 3. 新闻列表展示. 4. 新闻内容展示. 下一个版本中,准备实现如下内容: 1. 博客回复展示. 2. 新闻回复展示. 来,上点图片: 下载地址:http://vdisk.weibo.com/s/vA2tfcM2K2R

博客园 Mac客户端 1.0 源码

直接上重点~这里是github的地址.这里是github的地址.这里是github的地址. 此次开源的是mac 1.0客户端的源码.额···也是那个不准备维护的.并且也不再维护的那个版本.之后将会对2.0的客户端进行维护. 先声明:这是我第一次用swift写osx系统软件,代码或者结构有什么问题也请大家不吝赐教,谢谢~~~ 项目结构 CNBlogsForMac从上到下目录解析: - LGWebImage  自己写的,没用到,无用的东西,可以无视.之前是想用它来做Web图片引用的,后来找到了代替的

Windows Live Writer离线博客工具使用教程(适用于博客园、CSDN、51CTO等等博客)

文章背景 写博客不单是一种记录方式,更是一种工作习惯,与朋友一起分享是一件很快乐的事情,以前写博客,我们都会面临博客自带编辑器上各种头疼问题,比如排版.样式.功能局限性等等.但这些阻止不了我写下去的决心.一天突然奇想搜索下是否有那种离线博客发布的工具,没想到一谷歌,结果是,我竟然Out了好多年!泪奔啊- 文章主题 在网上查找离线博客之类的工具,发现各式各样都有!参差不齐!这里我就不一一举例了.最后决定使用微软一款开源日志发布工具Windows Live Writer.之所以用它,主要是原因有三:

【转】Windows Live Writer离线博客工具使用教程(适用于博客园、CSDN、51CTO等等博客)

文章背景 写博客不单是一种记录方式,更是一种工作习惯,与朋友一起分享是一件很快乐的事情,以前写博客,我们都会面临博客自带编辑器上各种头疼问题,比如排版.样式.功能局限性等等.但这些阻止不了我写下去的决心.一天突然奇想搜索下是否有那种离线博客发布的工具,没想到一谷歌,结果是,我竟然Out了好多年!泪奔啊- 文章主题 在网上查找离线博客之类的工具,发现各式各样都有!参差不齐!这里我就不一一举例了.最后决定使用微软一款开源日志发布工具Windows Live Writer.之所以用它,主要是原因有三: