你的位置:首页 > 操作系统

[操作系统]博客园第三方客户端正式发布App Store


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

1. 前言


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

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

1 2 3

Appstore地址:

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

2016-02-18-1009004908

 

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的高度)
  • UI资源和工具
    • http://www.easyicon.net/ (图标素材)
    • 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,实现轮盘旋转的效果(部分代码参考了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-C(PJXAnimatedTabBarController is a Objective-C version of RAMAnimatedTabBarController(https://github.com/Ramotion/animated-tab-bar))。

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

问题四:博客园使用的

解决思路:

这里我还是使用MVC思路,使用AFNetworking获取到

关于NSiOS开发之解析。

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

解决思路:

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

4. 存在问题和TODO


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

5. 后记


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