開源項(xiàng)目Running Life 源碼分析(一)

項(xiàng)目簡介

基于HeathKit和高德地圖開發(fā)健康跑步App,實(shí)現(xiàn)實(shí)時繪畫運(yùn)動軌跡杀狡、健康數(shù)據(jù)管理功能颜骤。
做這個項(xiàng)目出于兩個原因:
1、喜歡跑步(也為了減減肥<( ̄3 ̄)>)捣卤;
2忍抽、喜歡運(yùn)用自己的知識實(shí)際,里面有我自己寫的一些開源組件( 技術(shù)有限董朝,設(shè)計得不好的地方鸠项,大家多多指導(dǎo));

運(yùn)行效果如下:

圖片標(biāo)題
圖片標(biāo)題

項(xiàng)目目錄

title
title

Config目錄:接口配置文件子姜、宏定義和頭文件配置文件祟绊;
AppINit目錄:關(guān)于App的啟動設(shè)置,如第三方SDK初始化哥捕、界面初始化牧抽、HeathKit初始化配置;
Module目錄:業(yè)務(wù)模塊遥赚,由以下這幾個模塊組成:公共模塊扬舒、跑步模塊、記錄模塊凫佛、個人模塊讲坎、設(shè)置模塊、登陸注冊模塊愧薛;
Resource目錄:圖片資源和字體資源晨炕;
RunKit目錄:一些類的拓展、工具類毫炉、網(wǎng)絡(luò)層方案瓮栗、持久化存儲層方案;
Vendor目錄:一些不支持Cocoapod第三方庫;
Pod:支持Cocoapod第三方庫费奸;

業(yè)務(wù)層架構(gòu)

title
title

MVVM架構(gòu)(使用Facebook的KVOController實(shí)現(xiàn)view和viewModel的綁定鲸郊,項(xiàng)目往ReactCocoa遷移中)

viewModel如何設(shè)計?

viewModel負(fù)責(zé)從原始數(shù)據(jù)源獲取原始數(shù)據(jù)货邓,運(yùn)用對應(yīng)的數(shù)據(jù)處理邏輯秆撮,轉(zhuǎn)化為view層顯示的數(shù)據(jù)。他不引入UIKit相關(guān)類换况,所以他與UI無關(guān)职辨,也方便我們進(jìn)行單元測試。實(shí)際上戈二,它就是一層function core舒裤,理想上對于相同的輸入會導(dǎo)出相同的結(jié)果。所以viewmodel得設(shè)計主要包含三部分內(nèi)容:輸入觉吭、輸出腾供、命令,簡化成函數(shù)表達(dá)就是y = f(x),f函數(shù)指的是命令鲜滩,x是輸入伴鳖,y就是輸出,這里要注意輸出對外界來說只是一個只讀屬性徙硅。示例如下:

@interface ResultViewModel : NSObject

/**
 *  跑步距離
 */
@property (nonatomic, copy, readonly) NSString *distanceLabelText;

/**
 *  跑步時間
 */
@property (nonatomic, copy, readonly) NSString *timeLabelText;

/**
 *  跑步步數(shù)
 */
@property (nonatomic, copy, readonly) NSString *paceLabelText;

/**
 *  卡路里
 */
@property (nonatomic, copy, readonly) NSString *kcalLableText;

/**
 *  消耗雞腿數(shù)
 */
@property (nonatomic, copy, readonly) NSString *countLabelText;

/**
 *  運(yùn)動軌跡(不同顏色)
 */
@property (nonatomic, copy, readonly) NSArray *colorSegmentArray;

/**
 *  地圖顯示區(qū)域
 */
@property (nonatomic, assign, readonly) MKCoordinateRegion region;

/**
 *  跑步排名
 */
@property (nonatomic, copy, readonly) NSString *rank;


/**
 *  網(wǎng)絡(luò)失敗
 */
@property (nonatomic, strong, readonly) NSNumber *netFail;

/**
 *  構(gòu)造器
 *
 *  @param run 跑步記錄
 *
 *  @return 
 */
- (instancetype)initWithRunModel:(Run *)run;

/**
 *  上傳跑步記錄并獲取排名
 */
- (void)postRunRecordToServerAndGetRank;

/**
 *  僅僅獲取獲取跑步排名
 */
- (void)getRankData;


@end

viewModel與view如何綁定榜聂?

綁定的目的就是為了解決view與viewModel通信的問題。MVVM天然最好的綁定機(jī)制就是Facebook的ReactCocoa嗓蘑,它是函數(shù)式響應(yīng)式編程思想的一個體現(xiàn)须肆,它的核心就是響應(yīng)數(shù)據(jù)的變化、統(tǒng)一異步編程模型桩皿,綁定的具體做法就是view層通過訂閱viewModel上面的信號豌汇,先模擬處理一遍,這里模擬的意思是先從腦海里過一遍邏輯泄隔,實(shí)際不響應(yīng)拒贱,當(dāng)有信號發(fā)過來的時候才實(shí)際觸發(fā)。
但是他需要一定的學(xué)習(xí)成本梅尤,學(xué)習(xí)成本較大柜思,本人也在不斷學(xué)習(xí)當(dāng)中岩调,所有我們換種方式來實(shí)現(xiàn)這種響應(yīng)機(jī)制巷燥。想一下,cocoa中是不是有提供這種監(jiān)聽-響應(yīng)的機(jī)制号枕,沒錯缰揪,就是KVO,但是原生KVO寫起來會惡心死人,所有我們可以借助Facebook提供一個KVO框架(kvoController)來實(shí)現(xiàn)優(yōu)雅的綁定钝腺。(Facebook真是為了iOS的開發(fā)做出很多貢獻(xiàn)抛姑,開源了那么多好用的工具)。
綁定方式就是view層 kvo viewModel層的readonly屬性艳狐,一旦屬性變化就觸發(fā)響應(yīng)的處理邏輯定硝。示例如下:

[self.KVOController observe:self.viewModel keyPath:@"rank" options:NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
        if (self.viewModel.rank) {
            self.recordCardView.rankLabel.text = self.viewModel.rank;
            
            [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        }
    }];

功能實(shí)現(xiàn)

項(xiàng)目搭好條條框框,現(xiàn)在來分析具體的功能實(shí)現(xiàn)毫目。本項(xiàng)目有兩個功能蔬啡,一個是跑步,另外一個就是記錄镀虐,每個大功能點(diǎn)下又分幾個小功能點(diǎn)箱蟆,功能的示意圖如下:


title
title

跑步

這里主要分析跑步過程的具體邏輯,界面如下:


title
title

源碼在這個文件:"NewRunViewModel.m"

跑步數(shù)據(jù)源

跑步數(shù)據(jù)來源定位刮便,這里定位SDK選擇高德SDK空猜,雖然原生也是高德地圖,但經(jīng)過測試發(fā)現(xiàn)原生的定位很不準(zhǔn)恨旱,我也不知道具體原因是什么辈毯。
定義一個定位管理器,設(shè)置好相應(yīng)的配置參數(shù)搜贤,因?yàn)闉榱伺懿綌?shù)據(jù)的精確度漓摩,所以將定位的準(zhǔn)確度設(shè)置為最好,調(diào)用 [self.locationManager startUpdatingLocation]開啟持續(xù)定位入客,具體實(shí)現(xiàn)如下:

   -(AMapLocationManager *)locationManager{
    if (!_locationManager) {
        _locationManager = [[AMapLocationManager alloc] init];
        
        _locationManager.delegate = self;
        
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        
        _locationManager.distanceFilter = kCLDistanceFilterNone;
        
        //設(shè)置允許后臺定位參數(shù)管毙,保持不會被系統(tǒng)掛起
        [_locationManager setPausesLocationUpdatesAutomatically:NO];
        
        if([[[UIDevice currentDevice] systemVersion] floatValue]>9.0){
            [_locationManager setAllowsBackgroundLocationUpdates:YES];//iOS9(含)以上系統(tǒng)需設(shè)置    
        }
        
    }
    return _locationManager;
}

定位成功后會不斷的回調(diào)AMapLocationManagerDelegate的- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location方法,并不是所有定位數(shù)據(jù)都是有效桌硫,需要對數(shù)據(jù)進(jìn)行過濾夭咬,過濾的依據(jù)就是horizontalAccuracy和時間偏差。horizontalAccuracy表示水平準(zhǔn)確度铆隘,這么理解卓舵,它是以定位點(diǎn)為圓心的半徑,返回的值越小膀钠,證明準(zhǔn)確度越好掏湾,如果是負(fù)數(shù),則表示corelocation定位失敗肿嘲,我們知道GPS信號會受地域的影響融击,有時強(qiáng),有時弱雳窟,設(shè)置30是一個中和的做法尊浪,因?yàn)槲覀儾荒鼙WC每次定位回來的數(shù)據(jù)都是絕對精確,如果設(shè)置得太小,可能過濾得到的數(shù)據(jù)很少拇涤,太大就會誤差太大捣作。howRecent用于計算定位結(jié)果與當(dāng)前時間偏差,如果偏差超過2秒就過濾鹅士,這個2秒也是一個中和值券躁。過濾完數(shù)據(jù)就可以計算跑步的距離,保存在_distance這個全局變量中掉盅。

- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location {
        if (location.horizontalAccuracy < 30) {
            NSDate *eventDate = location.timestamp;
            
            NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
            
            if (fabs(howRecent) < 2.0 ) {
                if (self.locations.count > 0) {
                    _distance += [location distanceFromLocation:self.locations.lastObject];
                }
                
                [self.locations addObject:location];
            }
        }
}

獲取數(shù)據(jù)之后怎么實(shí)時刷新UI呢嘱朽?
我的做法是在NewRunViewController開啟一個定時器,時間間隔是1s怔接,每隔1秒往VM傳運(yùn)動時間搪泳,運(yùn)動時間相當(dāng)于函數(shù)的自變量,經(jīng)過VM處理后扼脐,它會給C發(fā)數(shù)據(jù)改變的信號岸军,信號相對于函數(shù)的因變量。實(shí)現(xiàn)如下:

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
                                                      target:self
                                                    selector:@selector(eachSecond:)
                                                    userInfo:nil
                                                     repeats:YES];
                                                     
/**
 *  運(yùn)動計數(shù)器回調(diào)
 *
 *  @param timer
 */
- (void)eachSecond:(NSTimer*)timer {
    _seconds++;
    
    self.viewModel.duration = _seconds;
}

//下面是綁定的代碼瓦侮,監(jiān)聽VM傳過來的信號艰赞,有變化就刷新UI
    [self.KVOController observe:self.viewModel keyPath:@"runDataChange" options:NSKeyValueObservingOptionOld block:^(id observer, id object, NSDictionary *change) {
        if ([self.viewModel.runDataChange boolValue]) {
            [_boardView configureViewWithViewModel:self.viewModel.currentRunData];
        }
    }];

智能判斷跑步狀態(tài)

通過CMMotionManager(是蘋果的運(yùn)動管理器框架,可以獲取設(shè)備加速計肚吏、陀螺儀的即時數(shù)據(jù))來智能判斷跑步狀態(tài)方妖,以決定是否繼續(xù)記錄。這個功能點(diǎn)體現(xiàn)在當(dāng)用戶運(yùn)動幅度變小的時候罚攀,小到一定程度的時候党觅,app就判斷用戶處于休息階段,當(dāng)這個階段持續(xù)超過8秒就暫停跑步記錄斋泄,進(jìn)入以下狀態(tài):


title
title

但用戶又開始運(yùn)動的時候杯瞻,運(yùn)動幅度到達(dá)一定程度的時候,有開啟跑步狀態(tài)炫掐。
它的實(shí)現(xiàn)原理是通過陀螺儀來實(shí)現(xiàn)(暫時還沒適配舊版本手機(jī)魁莉,因?yàn)閕phone5以下沒有陀螺儀),一般我們跑步的時候募胃,手機(jī)拿在手上或者放在褲袋里旗唁,所以y軸和z軸偏移最大也最頻繁,所以通過判斷y軸和z軸的加速度痹束,如果他們的加速度小于2检疫,則用戶不處于跑步狀態(tài),這個2的值是自己試出來- -参袱,如果大家有更好的依據(jù)歡迎到github issue我电谣。具體實(shí)現(xiàn)如下:

    NSOperationQueue* queue = [[NSOperationQueue alloc]init];
    
    /**
     *  陀螺儀是否可用
     */
    if (self.motionManger.gyroAvailable) {
        
        [self.motionManger startGyroUpdatesToQueue:queue withHandler:^(CMGyroData * _Nullable gyroData, NSError * _Nullable error) {
            
            CGFloat y = gyroData.rotationRate.y;
            
            CGFloat z = gyroData.rotationRate.z;
            
            if (fabs(y)>2||fabs(z)>2) {
                
                _stopCount = 0;
                
                if(![self.isRunning boolValue]) self.isRunning = @YES;
                
            }else{
                
                _stopCount++;
                
                if (_stopCount > 8) {
                    
                    if([self.isRunning boolValue]) self.isRunning = @NO;
                }
            }
        }];
    }else{
        NSLog(@"陀螺儀不可用");
    }

運(yùn)動軌跡

我將運(yùn)動軌跡的繪畫邏輯分離到MapViewController中秽梅,里面也有一個定位管理對象抹蚀,定位成功也會不斷的回調(diào)剿牺,相比NewRunController回調(diào)的處理,這里的處理多了對地圖的處理环壤,通過兩個坐標(biāo)確定一條線晒来,并把線添加到地圖上,代碼如下:

- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location {
    
    if (location.horizontalAccuracy < 30) {
        _firstLocate = NO;
        NSDate *eventDate = location.timestamp;
        
        NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
        if (fabs(howRecent) < 2.0 && location.horizontalAccuracy < 30) {
            
            if (self.locations.count > 0) {
                
                CLLocationCoordinate2D coords[2];
                coords[0] = ((CLLocation *)self.locations.lastObject).coordinate;
                coords[1] = location.coordinate;
                
                MKCoordinateRegion region =
                MKCoordinateRegionMakeWithDistance(location.coordinate, 500, 500);
                [self.myMapView setRegion:region animated:YES];
                
                [self.myMapView addOverlay:[MKPolyline polylineWithCoordinates:coords count:2]];
            }
            
            [self.locations addObject:location];
        }
    }else{
        if (_firstLocate) {
            MKCoordinateRegion region =
            MKCoordinateRegionMakeWithDistance(location.coordinate, 500, 500);
            [self.myMapView setRegion:region animated:YES];
            _firstLocate = NO;
        }
        
    }
    
}

通過mapView的一個delegate方法設(shè)置軌跡的相關(guān)屬性

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay {
    if ([overlay isKindOfClass:[MKPolyline class]]) {
        MKPolyline *polyLine = (MKPolyline *)overlay;
        MKPolylineRenderer *aRenderer = [[MKPolylineRenderer alloc] initWithPolyline:polyLine];
        aRenderer.strokeColor = UIColorFromRGB(0x43B5FE);
        aRenderer.lineWidth = 3;
        return aRenderer;
    }
    return nil;
}

保存跑步記錄

數(shù)據(jù)保存在本地數(shù)據(jù)庫中郑现,出于學(xué)習(xí)的目的湃崩,我這邊持久層選擇了CoreData,它是蘋果推薦的持久層存儲框架接箫,底層是sqlite攒读,做了面向?qū)ο蟮姆庋b。上手有點(diǎn)難度辛友,需要一定的學(xué)習(xí)成本薄扁,關(guān)于CoreData的具體使用,大家自行Google或baidu废累,在這里就不展開將邓梅。我們通過.xcdatamodeld可以十分方便地創(chuàng)建我們的實(shí)體對象,該項(xiàng)目主要有兩個實(shí)體對象:跑步記錄邑滨、實(shí)時位置數(shù)據(jù)日缨,兩者是有關(guān)聯(lián)的,一次跑步數(shù)據(jù)關(guān)聯(lián)著一系列實(shí)時位置數(shù)據(jù)掖看。


title
title
title
title

當(dāng)時在設(shè)計數(shù)據(jù)存儲方案的時候匣距,遇到這樣一個問題:
如果用戶沒登錄就發(fā)起跑步,跑步結(jié)束后數(shù)據(jù)插入到數(shù)據(jù)庫哎壳,這些數(shù)據(jù)是沒有用戶認(rèn)領(lǐng)的墨礁。當(dāng)用戶登陸的時候,這部分無用戶態(tài)的數(shù)據(jù)該如何處理耳峦。當(dāng)用戶退出登陸的時候恩静,原有記錄的數(shù)據(jù)是保存還是清除?保存又該如何處理呢蹲坷?
后來我參考了Nike的Running的處理邏輯驶乾,一旦登陸用戶,這些無用戶態(tài)的數(shù)據(jù)就被登陸用戶認(rèn)領(lǐng)循签,退出登錄數(shù)據(jù)保存在本地级乐。
既然處理邏輯想好了,這么數(shù)據(jù)存儲方案要如何讓設(shè)計呢县匠?
大家可以看我基于CoreData封裝的CoreDataManager:

@interface CoreDataManager : NSObject

/**
 *  臨時管理上下文對象
 */
@property (readonly, strong, nonatomic) NSManagedObjectContext *tempManagedObjectContext;

/**
 *  管理上下文對象
 */
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

/**
 *  全局管理類
 *
 *  @return 
 */
+ (CoreDataManager *)shareManager;

/**
 *  切換數(shù)據(jù)庫风科,如果沒有就新建
 *
 *  @param name 數(shù)據(jù)庫名字
 */
- (void)switchToDatabase:(NSString *)name;

/**
 *  切換到臨時數(shù)據(jù)庫
 */
- (void)switchToTempDatabase;

/**
 *  保存上下文對象
 */
- (void)saveContext;

/**
 *  保存臨時上下文對象
 */
- (void)saveTempContext;

@end

為了讓大家更好地了解這個方案撒轮,我普及一點(diǎn)點(diǎn)CoreData的知識,CoreData框架包含三層內(nèi)容:
1贼穆、底層數(shù)據(jù)庫题山;
2、持久化存儲助手故痊,作為業(yè)務(wù)層與持久層的協(xié)調(diào)對象顶瞳,負(fù)責(zé)從數(shù)據(jù)庫獲取數(shù)據(jù)并返回適合的數(shù)據(jù)給業(yè)務(wù)層:
3、管理上下文對象愕秫,參與具體的業(yè)務(wù)交互慨菱;
一個數(shù)據(jù)庫對應(yīng)一個上下文對象,所以我的方案設(shè)計了兩個上下文對象戴甩,一個對應(yīng)著存放臨時數(shù)據(jù)的數(shù)據(jù)庫符喝,另一個對應(yīng)存放用戶數(shù)據(jù)的數(shù)據(jù)。tempManagedObjectContext主要作用是為了獲取臨時數(shù)據(jù)用于合并數(shù)據(jù)庫甜孤,平時業(yè)務(wù)交互直接用managedObjectContext就行协饲,因?yàn)榈讓訒鶕?jù)當(dāng)前活躍的數(shù)據(jù)庫切換相應(yīng)的上下文對象。切換數(shù)據(jù)庫的實(shí)現(xiàn)原理:

    DBNAME = name;
    _managedObjectContext = nil;
    _persistentStoreCoordinator = nil;

用一個static變量存放數(shù)據(jù)庫的名字课蔬,數(shù)據(jù)庫的命名規(guī)則是以用戶的賬戶名的MD5哈希值作為用戶的數(shù)據(jù)庫名囱稽。因?yàn)榍袚Q了數(shù)據(jù)庫,上下文對象改變了二跋,持久化存儲助手也改變战惊,因?yàn)閮蓚€都是懶加載,置為nil扎即,到時會重新調(diào)用他們的getter方法吞获,getter方法內(nèi)部根據(jù)對應(yīng)的DBNAME創(chuàng)建相應(yīng)的對象。
切換數(shù)據(jù)庫的應(yīng)用場景有三個:app初始化的時候谚鄙、登陸的時候各拷、退出登陸的時候。

跑步結(jié)果

效果如下:

title
title

這邊有個功能點(diǎn)就是根據(jù)不同速度繪畫不同顏色的運(yùn)動軌跡闷营。
實(shí)現(xiàn)原理:創(chuàng)建一個MKPolyline(地圖軌跡類)的派生類MultiColorPolyline烤黍,該類多了一個屬性color,用來記錄當(dāng)前軌跡的顏色傻盟。將普通的軌跡轉(zhuǎn)化為帶顏色的軌跡實(shí)現(xiàn)邏輯放在MathController這個轉(zhuǎn)換的工具類中速蕊,具體代碼如下:

+ (NSArray *)colorSegmentsForLocations:(NSArray *)locations {
    NSMutableArray *speeds = [NSMutableArray array];
    double slowestSpeed = DBL_MAX;
    double fastestSpeed = 0.0;
    
    //獲取最慢速度和最快速度
    for (int i = 1; i < locations.count; i++) {
        Location *firstLoc = [locations objectAtIndex:(i-1)];
        Location *secondLoc = [locations objectAtIndex:i];
        
        CLLocation *firstLocCL = [[CLLocation alloc] initWithLatitude:firstLoc.latitude.doubleValue longitude:firstLoc.longtitude.doubleValue];
        CLLocation *secondLocCL = [[CLLocation alloc] initWithLatitude:secondLoc.latitude.doubleValue longitude:secondLoc.longtitude.doubleValue];
        
        double distance = [secondLocCL distanceFromLocation:firstLocCL];
        double time = [secondLoc.timestamp timeIntervalSinceDate:firstLoc.timestamp];
        double speed = distance/time;
        
        slowestSpeed = speed < slowestSpeed ? speed : slowestSpeed;
        fastestSpeed = speed > fastestSpeed ? speed : fastestSpeed;
        
        [speeds addObject:@(speed)];
        
    }
    
    double midSpeed = (slowestSpeed + fastestSpeed)/2;
    
    // 慢的用紅色
    CGFloat s_red = 139/255.0f;
    CGFloat s_green = 254/255.0f;
    CGFloat s_blue = 132/255.0f;
    
    // 不快不慢的用黃色
    CGFloat m_red = 101/255.0f;
    CGFloat m_green = 254/255.0f;
    CGFloat m_blue = 249/255.0f;
    
    // 快的用綠色
    CGFloat f_red = 67/255.0f;
    CGFloat f_green = 181/255.0f;
    CGFloat f_blue = 254/255.0f;
    
    NSMutableArray *colorSegments = [NSMutableArray array];
    
    for (int i = 1; i < locations.count; i++) {
        Location* firstLoc = [locations objectAtIndex:(i-1)];
        Location* secondLoc = [locations objectAtIndex:i];
        
        CLLocationCoordinate2D coords[2];
        coords[0].latitude = firstLoc.latitude.doubleValue;
        coords[0].longitude = firstLoc.longtitude.doubleValue;
        
        coords[1].latitude = secondLoc.latitude.doubleValue;
        coords[1].longitude = secondLoc.longtitude.doubleValue;
        
        NSNumber * speed = [speeds objectAtIndex:(i-1)];
        UIColor * color = [UIColor blackColor];
        

        if (speed.doubleValue < midSpeed) {
            double ratio = (speed.doubleValue - slowestSpeed) / (midSpeed - slowestSpeed);
            CGFloat red = s_red + ratio * (m_red - s_red);
            CGFloat green = s_green + ratio * (m_green - s_green);
            CGFloat blue = s_blue + ratio * (m_blue - s_blue);
            color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
            

        } else {
            double ratio = (speed.doubleValue - midSpeed) / (fastestSpeed - midSpeed);
            CGFloat red = m_red + ratio * (f_red - m_red);
            CGFloat green = m_green + ratio * (f_green - m_green);
            CGFloat blue = m_blue + ratio * (f_blue - m_blue);
            color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
        }
        
        MultiColorPolyline *segment = [MultiColorPolyline polylineWithCoordinates:coords count:2];
        segment.color = color;
        
        [colorSegments addObject:segment];
    }
    
    return colorSegments;
}

遍歷獲取最大速度和最小速度,根據(jù)速度與最大速度和最小速度比較娘赴,設(shè)置一個比例规哲,根據(jù)比例調(diào)配相應(yīng)的顏色,顏色的計算算法如上诽表,就不展開講了唉锌。

小結(jié)

今天分析了大體框架和跑步模塊一些細(xì)節(jié)的實(shí)現(xiàn)隅肥,關(guān)于記錄模塊的分析我打算放在第二篇來分析,先做下預(yù)告袄简,內(nèi)容主要有三個:
實(shí)現(xiàn)view的復(fù)用機(jī)制解決內(nèi)存暴漲問題腥放、貝塞爾曲線與動畫實(shí)現(xiàn)一個優(yōu)雅的數(shù)據(jù)展示界面、HeathKit框架的使用痘番。
項(xiàng)目地址:github.com/caixindong/Running-Life---iOS捉片,有問題歡迎大家提出討論平痰,大家覺得不錯汞舱,就賞個star。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宗雇,一起剝皮案震驚了整個濱河市昂芜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赔蒲,老刑警劉巖泌神,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舞虱,居然都是意外死亡欢际,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門矾兜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來损趋,“玉大人,你說我怎么就攤上這事椅寺』氩郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵返帕,是天一觀的道長桐玻。 經(jīng)常有香客問我,道長荆萤,這世上最難降的妖魔是什么镊靴? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮链韭,結(jié)果婚禮上偏竟,老公的妹妹穿的比我還像新娘。我一直安慰自己梧油,他們只是感情好苫耸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著儡陨,像睡著了一般褪子。 火紅的嫁衣襯著肌膚如雪量淌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天嫌褪,我揣著相機(jī)與錄音呀枢,去河邊找鬼。 笑死笼痛,一個胖子當(dāng)著我的面吹牛裙秋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缨伊,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼摘刑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刻坊?” 一聲冷哼從身側(cè)響起枷恕,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谭胚,沒想到半個月后徐块,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灾而,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年胡控,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旁趟。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡昼激,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轻庆,到底是詐尸還是另有隱情癣猾,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布余爆,位于F島的核電站纷宇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蛾方。R本人自食惡果不足惜像捶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桩砰。 院中可真熱鬧拓春,春花似錦、人聲如沸亚隅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煮纵。三九已至懂鸵,卻和暖如春偏螺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匆光。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工套像, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人终息。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓夺巩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親周崭。 傳聞我的和親對象是個殘疾皇子柳譬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容