怎么樣創(chuàng)建一個像RunKeeper一樣的App(一)swift版

怎么樣創(chuàng)建一個像RunKeeper一樣的App(一)swift版

</br>
本博將不定期更新外網(wǎng)的iOS最新教程

簡書: @西木

微博: @角落里的monster

本文翻譯自raywenderlich剔应,版權(quán)歸原作者所有,轉(zhuǎn)載請注明出處

原文地址為 http://www.raywenderlich.com/97944/make-app-like-runkeeper-swift-part-1

</br>

原博提供了兩份示例代碼,分別為剛開始項目配置時的把敞、part 1完成時的,地址分別為:

http://cdn4.raywenderlich.com/wp-content/uploads/2015/05/MoonRunner-Starter.zip

http://cdn4.raywenderlich.com/wp-content/uploads/2015/05/MoonRunner-Part1-Final.zip

</br>
這篇教程將向你展示档插,如何做一個類似于RunKeeper一樣乙濒,基于GPS的可以記錄你跑步軌跡的app.

這個新的app.我們就叫它MoonRunner吧.

接下來,你將要完成這個動態(tài)軌跡記錄app的所有功能

  • 追蹤核心位置
  • 當(dāng)你跑步時顯示地圖,并且實時更新行動路徑
  • 在跑步時持續(xù)顯示當(dāng)前的平均速度
  • 根據(jù)距離不同設(shè)置徽章獎勵系統(tǒng)
  • 當(dāng)你這次跑步結(jié)束的時候顯示這次跑步整體的運行軌跡

Getting Started

請下載本教程對應(yīng)的代碼,路徑為: http://cdn4.raywenderlich.com/wp-content/uploads/2015/05/MoonRunner-Starter.zip

打開并運行程序就可以看見一個非常簡潔的布局:

  • 首頁顯示的是3個簡單的導(dǎo)航按鈕
  • 你可以記錄或者開始一次新的跑步記錄的時候所在的那個界面是NewRun界面
  • 在一次跑步的詳情頁可以看見這次跑步的詳細(xì)信息省艳,包括彩色標(biāo)注的地圖

Starting the Run

首先,我們需要對項目做一些設(shè)置

  • 點擊MoonRunner的project navigator
  • 選擇Capabilities tab
  • 打開Background Modes
  • 勾選Location Updates

這個設(shè)置可以保證,即使你零時需要接聽電話嫁审,程序推入后臺時依然保持位置信息的更新

接下來跋炕,選擇Info tab,打開Custom iOS Target Properties律适,將下面兩行加入plist

key type value
NSLocationWhenInUseUsageDescription String MoonRunner wants to track your run
NSLocationAlwaysUsageDescription String MoonRunner wants to track your run

它的作用是辐烂,iOS會彈出提示框詢問用戶是否允許該app使用location data

注意
如果你的app要上傳App Store,你需要在App的discription中注明:在后臺持續(xù)使用GPS會減少電池的壽命

接下來回到代碼中捂贿,打開NewRunViewController.swift加入

import CoreLocation
import HealthKit

因為你需要很多的基于位置信息的API和Health模塊的method

然后纠修,你還需要在文件尾部加入class extension,來遵守CLLocationManagerDelegate協(xié)議

// MARK:- CLLocationManagerDelegate
extension NewRunViewController:CLLocationManagerDelegate{

}

隨后你需要實現(xiàn)一些代理方法厂僧,完成對于位置信息更新的監(jiān)聽

接下來加入一些成員屬性

var seconds = 0.0
var distance = 0.0

lazy var locationManager:CLLocationManager = {
    var _locationManager = CLLocationManager()
    
    _locationManager.deledate = self
    _locationManager.desiredAccuracy = kCLLocationAccuracyBest
    _locationManager.activityType = .Fitness
    
    // Movement threshold for new events
    _locationManager.distanceFilter = 10.0
    return _locationManager
}()

lazy var locations = [CLLocation]()
lazy var timer = NSTimer()

這些屬性的意思

  • seconds : 記錄軌跡的事件間隔扣草,單位是秒
  • distance : 截止當(dāng)前時刻跑了多遠(yuǎn),單位是米
  • locationManager : 對象颜屠,在start或者stop的時刻記錄用戶的位置
  • timer : 記錄時刻辰妙,更新UI

CLLocationManager和它的配置

當(dāng)懶加載的時候,你就會為NewRunViewController設(shè)置代理CLLocationManagerDelegate

緊接著設(shè)置了精確性為best(_locationManager.desiredAccuracy = kCLLocationAccuracyBest)甫窟,在你運動的時候密浑,你可以非常精確地讀取自己的位置信息,同樣蕴坪,你也會消耗比較多的電量

activityType屬性設(shè)置為.Fitness的用途: 例如當(dāng)你過馬路或者停止的時候肴掷,它會只能的更改配置崩你節(jié)省電量

distanceFilter設(shè)置為10米,相對于desireAccuracy背传,這個屬性不會影響電量呆瞻,它是方便顯示其他屬性的值的

如果你做個小小的測試你就會發(fā)現(xiàn),你的位移信息不是一條直線而是有很多鋸齒狀

高精度的distanceFilter就可以減少鋸齒径玖,給你一個更精確地軌跡痴脾,但是,太高的精度值會讓你的軌跡像素化(看到很多馬賽克)梳星,所以10m是一個相對比較合適的值

接下來赞赖,在viewWillAppear(animated: BOOL)方法的最后加上這一行

locationManager.requestAlawayAuthorization()

這個方法是iOS8才有的滚朵,用來請求用戶授權(quán)允許使用位置信息,如果你想讓你的App兼容iOS8之前的版本前域,還需要測試兼容性

接下來在實現(xiàn)中加入這個方法

override func viewWillDisappear(animated: Bool) {
   super.viewWillDisappear(animated)
   timer.invalidate()
}

這個方法的意思是辕近,當(dāng)導(dǎo)航控制器不顯示該頁面時,時間的記錄也會停止

再添加這個方法

func eachSecond(timer:NSTimer) {
        seconds++
        let secondsQuantity = HKQuantity(unit: HKUnit.secondUnit(), doubleValue: seconds)
        timeLabel.text = "Time: " + secondsQuantity.description
        let distanceQuantity = HKQuantity(unit: HKUnit.meterUnit(), doubleValue: distance)
        distanceLabel.text = "Distance: " + distanceQuantity.description
        
        let paceUnit = HKUnit.secondUnit().unitDividedByUnit(HKUnit.meterUnit())
        let paceQuantity = HKQuantity(unit: paceUnit, doubleValue: seconds / distance)
        paceLabel.text = "Pace: " + paceQuantity.description
    }

這個方法每一秒都會調(diào)用匿垄,在調(diào)用的時候移宅,你所有的數(shù)據(jù)值都會跟隨時間變化而更新

在你開始跑步前呢,還有最后一個方法要調(diào)用

func startLocationUpdates() {
        // Here, the location manager will be lazily instantiated
        locationManager.startUpdatingLocation()
    }

這個方法會告訴manager椿疗,需要開始更新位置信息了

在真正奔跑之前呢漏峰,還需要在startPressed(sender: AnyObject)方法中加入這些代碼

    seconds = 0.0
    distance = 0.0
    locations.removeAll(keepCapacity: false)
    timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "eachSecond", userInfo: nil, repeats: true)
    startLocationUpdates()

這個界面會持續(xù)更新所有數(shù)據(jù)

編譯運行,如果你start你就可以看見時間值開始在不斷增加

但是届榄,distance和pace文本框不會更新浅乔,因為你還沒有繪制位移軌跡,接下來我們做這部分

Recording the Run

你已經(jīng)創(chuàng)建了一個CLLocationManager對象铝条,你應(yīng)該去那里拿到要更新的數(shù)據(jù)靖苇,這些可以通過代理來實現(xiàn)

仍然是NewRunViewController.swift這個文件,給我們之前寫的 class extension 實現(xiàn) CLLocationManagerDelegate 代理方法

func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
        for location in locations as! [CLLocation] {
            if location.horizontalAccuracy < 20 {
                // update distance
                if self.locations.count > 0 {
                    distance += location.distanceFromLocation(self.locations.last)
                }
                
                //sace location
                self.locations.append(location)
            }
        }
    }

一旦有位置更新的時候這個方法就會被調(diào)用班缰,通常狀況下顾复,locations這個數(shù)組只有一個元素,如果有多個鲁捏,它們會按照時間先后排序

CLLocation中包含了很多信息,包括經(jīng)緯度等等萧芙。但是在讀取這些信息之前给梅,會有一個horizonAccuracy的核對,如果設(shè)備覺得這項數(shù)據(jù)在20米之內(nèi)的值不是很準(zhǔn)確的時候双揪,會自動的將這項數(shù)據(jù)從數(shù)據(jù)集中移除动羽。這個核對功能在跑步的時候極其重要,用戶在第一次啟動進(jìn)行校準(zhǔn)的時候渔期,這個時候运吓,它可能會更新一些不準(zhǔn)確的數(shù)據(jù)

如果CLLocation通過了檢測,就會開始計算距離疯趟。這時候distaFromLocation(_location: CLLocation)方法就很方便了拘哨,它能夠考慮到各種稀奇古怪的涉及到地球曲面的情況

最后,添加這個位置對象生成一個一段時間內(nèi)位產(chǎn)生的包含多個位置對象的數(shù)組

注意
CLLocation對象也包含了相對應(yīng)的VerticalAccuracy的海拔的數(shù)值信峻,每一個runner都知道倦青,小山坡會給跑步過程添加很多不一樣的感受,因為海拔高度會影響你對氧氣的需求量盹舞,它會給你一些小小的挑戰(zhàn)产镐,當(dāng)然,這個數(shù)據(jù)也會收錄在App里

Send the Simulator on a run

我希望這個教程和App的開放能讓你對運動與健身產(chǎn)生極大的熱情隘庄,但是你開發(fā)的時候不需要按照字面意思逐字地去理解它

你不需要在測試的時候真正的拿著手機(jī)去跑步,模擬器就可以幫你完成這個任務(wù)

在模擬器中啟動程序癣亚,然后選擇Debug ->Location ->City Run丑掺,模擬器就會給你一個虛擬的數(shù)據(jù)

當(dāng)然,這樣比較容易也不需要耗費精力去測試相對于其他的基于位置信息的App

然而述雾,我也會建議你真的拿著手機(jī)做一個實地測試街州,這樣你才有機(jī)會去微調(diào)你的位置管理的參數(shù),去評估你得到的數(shù)據(jù)質(zhì)量

同時也有助于你養(yǎng)成健康的生活習(xí)慣

Saving the Run

你之前已經(jīng)設(shè)計好了UI绰咽,那么現(xiàn)在設(shè)置數(shù)據(jù)吧

加入這個方法到NewRunViewController.swift中

func saveRun() {
    // 1
    let savedRun = NSEntityDescription.insertNewObjectForEntityForName("Run",
      inManagedObjectContext: managedObjectContext!) as! Run
    savedRun.distance = distance
    savedRun.duration = seconds
    savedRun.timestamp = NSDate()

    // 2
    var savedLocations = [Location]()
    for location in locations {
      let savedLocation = NSEntityDescription.insertNewObjectForEntityForName("Location",
        inManagedObjectContext: managedObjectContext!) as! Location
      savedLocation.timestamp = location.timestamp
      savedLocation.latitude = location.coordinate.latitude
      savedLocation.longitude = location.coordinate.longitude
      savedLocations.append(savedLocation)
    }

    savedRun.locations = NSOrderedSet(array: savedLocations)
    run = savedRun

    // 3
    var error: NSError?
    let success = managedObjectContext!.save(&error)
    if !success {
      println("Could not save the run!")
    }
  }

這里做了什么呢菇肃?如果你之前做了Core Data Flow的話,這看起來很像是保存了一條新的跑步記錄:

1.你創(chuàng)建了一個新的記錄取募,記錄了你運動的距離和花費的時間

2.當(dāng)把一系列的CLLocation對象保存到一個Location對象里面的時候琐谤,你就把跑步經(jīng)過的那些坐標(biāo)點都連接起來了

3.保存NSManagedObjectContext

最后,當(dāng)用戶停止跑步并且要把這次跑步的記錄保存下來的時候就會調(diào)用這個方法玩敏。找到

extension NewRunViewController: UIActionSheetDelegate {
}

這個extension,在

if buttonIndex == 1

這個block里地第一行寫上

saceRun()

編譯斗忌、運行,你就可以開始一次新的跑步記錄并且把數(shù)據(jù)保存下來

然而旺聚,跑步詳細(xì)信息的界面仍然是空的织阳,現(xiàn)在我們?nèi)ネ瓿伤?/p>

Revealing the Map

現(xiàn)在,需要我們?nèi)フ{(diào)出地圖砰粹,打開 DetailViewController.swift 并且導(dǎo)入 Healthkit

import HralthKit

然后唧躲,將下面的代碼寫到configView()方法里

func configureView() {
    let distanceQuantity = HKQuantity(unit: HKUnit.meterUnit(), doubleValue: run.distance.doubleValue)
    distanceLabel.text = "Distance: " + distanceQuantity.description

    let dateFormatter = NSDateFormatter()
    dateFormatter.dateStyle = .MediumStyle
    dateLabel.text = dateFormatter.stringFromDate(run.timestamp)

    let secondsQuantity = HKQuantity(unit: HKUnit.secondUnit(), doubleValue: run.duration.doubleValue)
    timeLabel.text = "Time: " + secondsQuantity.description

    let paceUnit = HKUnit.secondUnit().unitDividedByUnit(HKUnit.meterUnit())
    let paceQuantity = HKQuantity(unit: paceUnit, doubleValue: run.duration.doubleValue / run.distance.doubleValue)
    paceLabel.text = "Pace: " + paceQuantity.description

  }

這里設(shè)置了跑步的詳細(xì)信息,可以顯示在屏幕的文本框里

1.首先碱璃,要設(shè)置你所在的地理位置

2.設(shè)置了運動軌跡的起始點

3.設(shè)置了速度的顯示風(fēng)格

將下面的方法加入類中

func mapRegion() -> MKCoordinateRegion {
    let initialLoc = run.locations.firstObject as! Location

    var minLat = initialLoc.latitude.doubleValue
    var minLng = initialLoc.longitude.doubleValue
    var maxLat = minLat
    var maxLng = minLng

    let locations = run.locations.array as! [Location]

    for location in locations {
      minLat = min(minLat, location.latitude.doubleValue)
      minLng = min(minLng, location.longitude.doubleValue)
      maxLat = max(maxLat, location.latitude.doubleValue)
      maxLng = max(maxLng, location.longitude.doubleValue)
    }

    return MKCoordinateRegion(
      center: CLLocationCoordinate2D(latitude: (minLat + maxLat)/2,
        longitude: (minLng + maxLng)/2),
      span: MKCoordinateSpan(latitudeDelta: (maxLat - minLat)*1.1,
        longitudeDelta: (maxLng - minLng)*1.1))
  }

MKCoordinateRegion可以根據(jù)你提供的中心位置弄痹,水平和豎直范圍來確定當(dāng)前屏幕顯示的是哪個區(qū)域

例如,當(dāng)你想讓你的運動軌跡的顯示看起來比較舒服一點的話嵌器,可以拖拽或者縮放地圖肛真。這一點需要明確地告訴用戶,這樣他看到的路線才能顯示在屏幕中心

接著爽航,添加下面這個方法

func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
    if !overlay.isKindOfClass(MKPolyline) {
      return nil
    }
    
    let polyline = overlay as! MKPolyline
    let renderer = MKPolylineRenderer(polyline:polyline)
    renderer.strokeColor = UIColor.blackColor()
    renderer.lineWidth = 3
    return renderer
  }

這個方法表示當(dāng)表示軌跡的曲線重合的時候蚓让,你的軌跡曲線的顏色會加深,讓你看起來更直觀讥珍,顏色加深的那一部分是由一連串的位置點產(chǎn)生的

接下來历极,你需要為polyline定義一個coordinates,添加這個方法

func polyline() -> MKPolyline {
    var coords = [CLLocationCoordinate2D]()

    let locations = run.locations.array as! [Location]
    for location in locations {
      coords.append(CLLocationCoordinate2D(latitude: location.latitude.doubleValue,
        longitude: location.longitude.doubleValue))
    }

    return MKPolyline(coordinates: &coords, count: run.locations.count)
  }

這里你將Location的數(shù)據(jù)傳入到了CLLocationCoordinate2D這個數(shù)組中

然后串述,添加以下方法

func loadMap() {
    if run.locations.count > 0 {
      mapView.hidden = false

      // Set the map bounds
      mapView.region = mapRegion()

      // Make the line(s!) on the map
      loadMap()
    } else {
      // No locations were found!
      mapView.hidden = true

      UIAlertView(title: "Error",
        message: "Sorry, this run has no locations saved",
        delegate:nil,
        cancelButtonTitle: "OK").show()
    }
  }

這個方法中执解,位置點繪制完畢,地圖顯示的區(qū)域為開始跑步前設(shè)置的區(qū)域,重合軌跡的部分做了加深的顏色渲染

最后衰腌,將這個方法添加到configView()的最后

loadMap()

現(xiàn)在新蟆,編譯運行,你就可以在模擬器上看到這樣的地圖顯示了

Finding the Right Color

這個App已經(jīng)很cool了右蕊,但是你還可以顯示用戶跑的到底有多快琼稻,那樣,他們就可以辨別在不同的地形環(huán)境中饶囚,他們有沒有保持在合適的速率上

要做這個功能的話帕翻,你需要擴(kuò)展polyline這個類

新建一個類,叫做MulticolorPolylineSegment萝风,打開嘀掸,刪除里面的內(nèi)容,寫入以下代碼

import UIKit
import MapKit

class MulticolorPolylineSegment: MKPolyline {
  var color: UIColor?
 }

這個自定義的polyline將用來渲染軌跡的每一個片段规惰。顏色的深淺將表示速度的快慢睬塌,如此以外,它和MKPolyline是一樣的歇万。它們都是用來描繪連接兩個位置點支架的線段

接下來你要確定揩晴,在什么樣的線段上面使用什么樣的顏色。添加這個類方法在MulticolorPolylineSegment 這個類中

private class func allSpeeds(forLocations locations: [Location]) -> (speeds: [Double], minSpeed: Double, maxSpeed: Double) {
    // Make Array of all speeds. Find slowest and fastest
    var speeds = [Double]()
    var minSpeed = DBL_MAX
    var maxSpeed = 0.0

    for i in 1..<locations.count {
      let l1 = locations[i-1]
      let l2 = locations[i]

      let cl1 = CLLocation(latitude: l1.latitude.doubleValue, longitude: l1.longitude.doubleValue)
      let cl2 = CLLocation(latitude: l2.latitude.doubleValue, longitude: l2.longitude.doubleValue)

      let distance = cl2.distanceFromLocation(cl1)
      let time = l2.timestamp.timeIntervalSinceDate(l1.timestamp)
      let speed = distance/time

      minSpeed = min(minSpeed, speed)
      maxSpeed = max(maxSpeed, speed)

      speeds.append(speed)
    }

    return (speeds, minSpeed, maxSpeed)
  }

這個方法會返回一個數(shù)組贪磺,這個數(shù)組裝得是一連串的位置點相對應(yīng)的速度值硫兰,其中也就包括了最大速度和最小速度。返回的多個值寒锚,你可以將它們放在一個元組里

首先劫映,你應(yīng)該注意的是輸入的所有位置點是一個環(huán)。你需要將每一個Location轉(zhuǎn)換成CLLocation刹前,這里你可以使用 func distanceFromLocation(_ location: CLLocation!) -> CLLocationDistance 這個方法

根據(jù)物理學(xué)常識苏研,速度 = 路程 / 時間,所以你就可以得到用戶在跑步中每一時刻速度的變化情況

這個方法是私有方法腮郊,只能在類里面調(diào)用。然后筹燕,添加這個方法

class func colorSegments(forLocations locations: [Location]) -> [MulticolorPolylineSegment] {
    var colorSegments = [MulticolorPolylineSegment]()

    // RGB for Red (slowest)
    let red   = (r: 1.0, g: 20.0 / 255.0, b: 44.0 / 255.0)

    // RGB for Yellow (middle)
    let yellow = (r: 1.0, g: 215.0 / 255.0, b: 0.0)

    // RGB for Green (fastest)
    let green  = (r: 0.0, g: 146.0 / 255.0, b: 78.0 / 255.0)

    let (speeds, minSpeed, maxSpeed) = allSpeeds(forLocations: locations)

    // now knowing the slowest+fastest, we can get mean too
    let meanSpeed = (minSpeed + maxSpeed)/2

    return colorSegments
  }

這里轧飞,你定義了三種顏色分別表示慢速、中速撒踪、快速过咬。每一種顏色,分別有它的RGB值得范圍制妄,最慢得部分是全紅色掸绞,最快是全綠色,中速是純黃色,其它時候顏色會根據(jù)速度大小在紅色->黃色->綠色時間漸變衔掸,所以最后顯示出來的結(jié)果一定會很炫目

需要注意的是你怎么樣從allspeeds這個元組中拿到最大值烫幕、最小值和平均值

最后,在剛才的方法的后面到return colorSegments 之前加入這段代碼

for i in 1..<locations.count {
      let l1 = locations[i-1]
      let l2 = locations[i]

      var coords = [CLLocationCoordinate2D]()

      coords.append(CLLocationCoordinate2D(latitude: l1.latitude.doubleValue, longitude: l1.longitude.doubleValue))
      coords.append(CLLocationCoordinate2D(latitude: l2.latitude.doubleValue, longitude: l2.longitude.doubleValue))

      let speed = speeds[i-1]
      var color = UIColor.blackColor()

      if speed < minSpeed { // Between Red & Yellow
        let ratio = (speed - minSpeed) / (meanSpeed - minSpeed)
        let r = CGFloat(red.r + ratio * (yellow.r - red.r))
        let g = CGFloat(red.g + ratio * (yellow.g - red.g))
        let b = CGFloat(red.r + ratio * (yellow.r - red.r))
        color = UIColor(red: r, green: g, blue: b, alpha: 1)
      }
      else { // Between Yellow & Green
        let ratio = (speed - meanSpeed) / (maxSpeed - meanSpeed)
        let r = CGFloat(yellow.r + ratio * (green.r - yellow.r))
        let g = CGFloat(yellow.g + ratio * (green.g - yellow.g))
        let b = CGFloat(yellow.b + ratio * (green.b - yellow.b))
        color = UIColor(red: r, green: g, blue: b, alpha: 1)
      }

      let segment = MulticolorPolylineSegment(coordinates: &coords, count: coords.count)
      segment.color = color
      colorSegments.append(segment)
    }

在這里敞映,你可以拿到預(yù)先計算的速度值较曼、速度的范圍,也就可以由速度變化的快慢程度來確定顏色變化的深淺程度

接下來振愿,你可以根據(jù)兩個像對應(yīng)的坐標(biāo)和顏色創(chuàng)建一個新的MulticolorPolylineSegment捷犹。最后,你收集到所有的顏色片段后冕末,就可以準(zhǔn)備開始渲染了

Applying the Multicolored Segments

想要讓detail View Controller使用新的 multicolor polyline 很簡單萍歉,打開DetailViewController.swift,找到 loadMap() 方法档桃,將

mapView.addOverlay(polyline())

替換成

let colorSegments = MulticolorPolylineSegment.colorSegments(forLocations: run.locations.array as! [Location])

mapView.addOverlays(colorSegments)

這里創(chuàng)建了一個segments的數(shù)組枪孩,并且把所有overlays加到了map上

最后,你要準(zhǔn)備給polyline上面每個segment渲染成特定的顏色胳蛮,所以销凑,用下面的代碼重寫你的mapView方法

func mapView(mapView:MKMapView!, rendererForOverlay Overlay:MKOverlay!)->NKOverlayRenderer!{
    if !overlay.isKindOfClass(MulticolorPolylineSegment) {
      return nil
    }

    let polyline = overlay as! MulticolorPolylineSegment
    let renderer = MKPolylineRenderer(polyline: polyline)
    renderer.strokeColor = polyline.color
    renderer.lineWidth = 3
    return renderer
}

看起來和之前的很像,但是現(xiàn)在仅炊,每一個segment都被渲染成了特定的顏色

再次編譯運行斗幼,你就能看到這樣一個色彩豐富的地圖展示了

Leaving a Trail Of Breadcrumbs

最后生成的地圖看起來很絢麗,但是在跑步過程中它是怎么樣的呢

打開Main.storyboard 找到New Run Scene抚垄,拖一個MapKit View進(jìn)來到“Ready to launch” label和start button之間

然后蜕窿,為它添加約束

  • 頂部距離label 20 point
  • 地步距離button 20 point
  • 左右距離superView都為0

然后打開 NewRunViewController.swift 添加

import MapKit

接著,添加成員屬性

@IBOutlet weak var mapView:MKMapView!

在 viewWillAppear 方法中添加

mapView.hidden = true

使地圖開始時處于hidden狀態(tài)呆馁,在startPressed 方法末尾添加

mapView.hidden = false

點擊start的時候地圖出現(xiàn)

在文件末尾添加 class extension 實現(xiàn)代理方法

// MARK: - MKMapViewDelegate
extension NewRunViewController: MKMapViewDelegate {
  func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
    if !overlay.isKindOfClass(MKPolyline) {
      return nil
    }

    let polyline = overlay as! MKPolyline
    let renderer = MKPolylineRenderer(polyline: polyline)
    renderer.strokeColor = UIColor.blueColor()
    renderer.lineWidth = 3
    return renderer
  }
}

這里和 run details screen 里地很像桐经,但是這里的stroke color仍然是藍(lán)色的

接下來,你需要寫代碼去更新地圖的顯示區(qū)域浙滤,并且在每產(chǎn)生一個有效地Location的時候描繪軌跡阴挣,將你的locationManager(_:didUpdateLocations:)方法的實現(xiàn)更新成

func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
    for location in locations as! [CLLocation] {
      let howRecent = location.timestamp.timeIntervalSinceNow

      if abs(howRecent) < 10 && location.horizontalAccuracy < 20 {
        //update distance
        if self.locations.count > 0 {
          distance += location.distanceFromLocation(self.locations.last)

          var coords = [CLLocationCoordinate2D]()
          coords.append(self.locations.last!.coordinate)
          coords.append(location.coordinate)

          let region = MKCoordinateRegionMakeWithDistance(location.coordinate, 500, 500)
          mapView.setRegion(region, animated: true)

          mapView.addOverlay(MKPolyline(coordinates: &coords, count: coords.count))
        }

        //save location
        self.locations.append(location)
      }
    }
  }

現(xiàn)在人弓,你當(dāng)前的位置始終在地圖的最中心脸侥,同時既穆,藍(lán)色的運動軌跡隨著你的運動在不斷延伸

打開Main.storyboard找到NewRunScene策添,連接mapView 到map View若贮,并且設(shè)置代理為當(dāng)前控制器

編譯運行猪贪,將會看到地圖實時更新

Where To Go From Here

這里有這個例子的完整代碼 http://cdn4.raywenderlich.com/wp-content/uploads/2015/05/MoonRunner-Part1-Final.zip

你可以看看怎么樣用Core Data存儲數(shù)據(jù)诸迟,怎么樣在地圖上顯示詳細(xì)的跑步信息塔鳍,這是這個App最核心的部分

如果你的技術(shù)比較好的話壹粟,你可以試試怎么樣使用海拔高度信息拜隧,怎么樣改變軌跡寬度,怎么樣使用一小段的平均速度使顏色變化比之前更加流暢

in any case,這篇教程還會有第二部分洪添,為你介紹為每個用戶定制的徽章獎勵體制

注:

1.本文翻譯自 http://www.raywenderlich.com/97944/make-app-like-runkeeper-swift-part-1

2.原博part 2 已更新,著急的同學(xué)可先行查看 http://www.raywenderlich.com/97945/make-app-like-runkeeper-swift-part-2

  • 不定期送上iOS最新教程,但能力有限,翻譯不準(zhǔn)的地方還望指正

簡書:@西木

微博:@角落里的monster

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垦页,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子薇组,更是在濱河造成了極大的恐慌外臂,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件律胀,死亡現(xiàn)場離奇詭異宋光,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)炭菌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門罪佳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人黑低,你說我怎么就攤上這事赘艳。” “怎么了克握?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵蕾管,是天一觀的道長。 經(jīng)常有香客問我菩暗,道長掰曾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任停团,我火速辦了婚禮旷坦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佑稠。我一直安慰自己秒梅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布舌胶。 她就那樣靜靜地躺著捆蜀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幔嫂。 梳的紋絲不亂的頭發(fā)上漱办,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音婉烟,去河邊找鬼。 笑死暇屋,一個胖子當(dāng)著我的面吹牛似袁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼昙衅,長吁一口氣:“原來是場噩夢啊……” “哼扬霜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起而涉,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤著瓶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啼县,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體材原,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年季眷,在試婚紗的時候發(fā)現(xiàn)自己被綠了余蟹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡子刮,死狀恐怖威酒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挺峡,我是刑警寧澤葵孤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站橱赠,受9級特大地震影響尤仍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜病线,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一吓著、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧送挑,春花似錦绑莺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至司澎,卻和暖如春欺缘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挤安。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工谚殊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛤铜。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓嫩絮,卻偏偏與公主長得像丛肢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剿干,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

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