部落格園第三方用戶端-i部落格園正式發布App Store,-iapp

來源:互聯網
上載者:User

部落格園第三方用戶端-i部落格園正式發布App Store,-iapp
部落格園第三方用戶端-i部落格園正式發布App Store1. 前言

算來從15年8月到現在自學iOS已經快7個月了,雖然中間也是斷斷續續的,不過竟然堅持下來了。年後要找實習啦,於是萌生了一個想法 —— 寫一個app練練手。這次我沒弄後台了,直接使用了部落格園的open api(嘿嘿)。之前也做過一個app,叫做魔界-魔術,前後端都是我弄的,不過後端使用的是Bmob後端雲(一個Baas服務),但是作為第一個app,代碼上感覺很混亂,而且基本上都是用的第三方控制項。這次的i部落格園是我完全獨立開發的(包括UI設計),整體使用的是MVC模式,並且盡量不去使用別人第三方控制項(雖然還是用了。後面會提到具體使用)。

先放出幾張app的gif預覽圖片:

i部落格園。或者掃描下面二維碼:

問題一:實現引導頁(不是啟動頁)上的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思想。

問題四:部落格園使用的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上。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.