(沒時間維護,已下架)博客園第三方客戶端-i博客園正式發布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的高度)
    • 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上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM