博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(没时间维护,已下架)博客园第三方客户端-i博客园正式发布App Store
阅读量:5295 次
发布时间:2019-06-14

本文共 7343 字,大约阅读时间需要 24 分钟。

(没时间维护,已下架)博客园第三方客户端-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. (新闻)
  2. (博客)
  • 使用到的第三方控件
    • AFNetworking
    • SDWebImage
    • HMSegmentedControl(Segmented Control)
    • RESideMenu (侧滑控制器视图)
    • MJRefresh
    • Masonry (AutoLayout)
    • UITableView+FDTemplateLayoutCell (动态计算UITableViewCell的高度)
    • XMLDictionary (解析XML文件,因为博客园web api传回来的是xml数据)
  • UI资源和工具
    • (图标素材)
    • Sketch
    • PhotoShop

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,实现轮盘旋转的效果(部分代码参考了

解决思路:

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

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

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

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

// 在rect这个区域内有几个cell,返回每个cell的属性- (NSArray
*)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 - (PJXAnimatedTabBarController is a Objective-C version of RAMAnimatedTabBarController())。

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

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

解决思路:

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

关于NSXMLParserDelegate可以参考这篇文章 - 。

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

解决思路:

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

4. 存在问题和TODO


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

5. 后记


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

转载于:https://www.cnblogs.com/polobymulberry/p/5189378.html

你可能感兴趣的文章
wzplayer for android界面
查看>>
测试环境
查看>>
json中的dumps和loads
查看>>
nginx 随笔
查看>>
一次完整的 HTTP 请求过程
查看>>
关于RouteTask的小小bug
查看>>
Group Policy Settings with Silverlight
查看>>
sharepoint站点支持AJAX功能做些简要说明
查看>>
面试题32:从1到n整数中1出现的次数
查看>>
Oracle中sign/decode/nvl/round/trunc/(+)/instr/substr/replace解释
查看>>
加载声音的过程!
查看>>
重载函数
查看>>
Unity3d 引擎原理详细介绍
查看>>
Vijos p1696 数与连分数
查看>>
一个value同时满足两种interface
查看>>
连续子数组的最大和
查看>>
Luogu P1023 税收与补贴问题
查看>>
python note 32 锁
查看>>
web技术工具帖
查看>>
SpringBoot项目中常见的注解
查看>>