ARKit教程17_第十三章:位置跟蹤和信標(biāo)

前言

對(duì)ARKit感興趣的同學(xué),可以訂閱ARKit教程專題
源代碼地址在這里

正文

在前三章中,我們學(xué)習(xí)了如何使用ARKit實(shí)現(xiàn)虛擬廣告牌,該虛擬廣告牌首先是通過掃描矩形顯示出來拖陆,然后是掃描QR碼顯示出來。

廣告牌的第一個(gè)版本使用視圖控制器和附加到ARKit平面材料的視圖來顯示圖像輪播和視頻播放器懊亡。

在第二個(gè)修訂版中依啰,我們使用故事板替換了視圖控制器并添加了Web視圖;這使得在ARKit會(huì)話中顯示網(wǎng)站成為可能。我們還學(xué)習(xí)了切換全屏模式店枣。

在本章的最后一章中速警,我們將學(xué)習(xí)如何使用位置功能,通過在用戶靠近感興趣的地方時(shí)自動(dòng)啟用功能來增強(qiáng)用戶體驗(yàn)鸯两。

具體來說闷旧,我們將學(xué)習(xí)到:

  • 獲取用戶的位置。
  • 使用地理圍欄來檢測(cè)用戶何時(shí)進(jìn)入或離開特定區(qū)域钧唐。
  • 使用信標(biāo)進(jìn)行接近檢測(cè)鸠匀。

開始

確定用戶位置

雖然我們可能沒有使用它,但我們可能聽說過Core Location逾柿,用于處理設(shè)備位置和信標(biāo)的iOS框架缀棍。我們將在本項(xiàng)目中實(shí)現(xiàn)它。

啟用核心位置

要使用Core Location并遵守用戶權(quán)限要求机错,我們需要向Info.plist文件添加一些Key爬范。

使用Core Location有兩種方法:

  • when in use: Core Location僅在應(yīng)用程序位于前臺(tái)時(shí)才向應(yīng)用程序發(fā)送位置更新。
  • always: Core Location隨時(shí)向應(yīng)用程序發(fā)送位置更新弱匪,無論應(yīng)用程序是在前臺(tái)還是后臺(tái)青瀑。

在本項(xiàng)目中,我們需要使用always萧诫。

iOS 11中已經(jīng)發(fā)生了一些變化斥难。在iOS 10及更早版本中,要求always模式帘饶,我們需要將NSLocationAlwaysUsageDescription鍵添加到Info.plist哑诊,其值應(yīng)包含提示用戶讓用戶決定是否同意。

另一方面及刻,在iOS 11中镀裤,我們需要包含兩個(gè)Key竞阐,要求始終和何時(shí)使用權(quán)限。

我們首先按照下圖作如下操作:

文件以源代碼模式打開暑劝。在文件中看到的第一個(gè)<dict>標(biāo)簽后立即轉(zhuǎn)到第5行骆莹,然后插入以下兩行:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Chapter13 requires access to your phones location to notify when you enter a geofence containing ads</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Chapter13 requires access to your phones location to notify when you enter a geofence containing ads</string>

當(dāng)iOS要求訪問設(shè)備位置的權(quán)限時(shí),這些行指定提示給用戶的文本担猛。

注意:XML代碼已經(jīng)過格式化幕垦,通過在兩個(gè)<string> </ string>標(biāo)記中包含的文本中添加幾個(gè)換行符來提高可讀性。當(dāng)文本顯示給用戶時(shí)會(huì)呈現(xiàn)這些換行符傅联,因此在編寫代碼時(shí)忽略它們會(huì)更好先改,如果使用復(fù)制和粘貼則刪除。

Info.plist中正確設(shè)置密鑰非常重要纺且。如果Core Location沒有找到它所期望的密鑰盏道,那么它就不會(huì)告訴你它們已經(jīng)丟失了。雖然它會(huì)向Xcode控制臺(tái)發(fā)出警告载碌,但它會(huì)默默地拒絕執(zhí)行任何與位置相關(guān)的活動(dòng)猜嘱。

打開LocationManager.swift;這實(shí)現(xiàn)了LocationManager類。我們將在本章的過程中添加更多功能嫁艇。但是現(xiàn)在朗伶,在初始化程序之后添加此方法:

func initialize() {

// 1 
locationManager.delegate = self 
// 2 
locationManager.activityType = .otherNavigation

// Geofencing requires always authorization 
// 3 
locationManager.requestAlwaysAuthorization() 
// 4 
locationManager.startUpdatingLocation()
}

上面代碼作用如下:

  • 1: LocationManagerCore LocationCLLocationManager類的實(shí)例。該類公開了一個(gè)根據(jù)該應(yīng)用的需求量身定制的界面步咪。
  • 2: Core Location需要知道位置跟蹤的目的论皆,以便更好地滿足應(yīng)用程序的需求。像如下應(yīng)用場(chǎng)景:automotive navigation猾漫,?tness点晴,other navigationother。你在這個(gè)應(yīng)用程序中使用otherNavigation悯周。
  • 3: 這要求用戶獲得永久授權(quán)的許可粒督。如果未授予或拒絕授權(quán),則授權(quán)過程通過委托異步執(zhí)行禽翼,因此下一行不會(huì)產(chǎn)生任何影響屠橄。
  • 4: 此行通過指示Core Location發(fā)送位置更新來啟動(dòng)跟蹤過程,除非權(quán)限被拒絕闰挡。

現(xiàn)在我們需要添加下面的方法:

private var locationManager = LocationManager()

找到viewDidLoad()方法锐墙。在將場(chǎng)景設(shè)置為sceneView之后,添加以下代碼行:

// Initialize core location 
locationManager.initialize() 
locationManager.delegate = self

這將調(diào)用初始化方法并設(shè)置位置管理器委托长酗。

在文件的最后溪北,我們添加一個(gè)extension:

// MARK: - LocationManagerDelegate 
extension ViewController: LocationManagerDelegate {

// MARK: Location 
func locationManager(_ locationManager: LocationManager, didEnterRegionId regionId: String) { 
}

func locationManager(_ locationManager: LocationManager, didExitRegionId regionId: String) { 
}

// MARK: Beacons 
func locationManager(_ locationManager: LocationManager, didRangeBeacon beacon: CLBeacon) {
 }

func locationManager(_ locationManager: LocationManager, didLeaveBeacon beacon: CLBeacon) {
     }
}

LocationManagerDelegate是一個(gè)類似于CLLocationManager委托的自定義委托協(xié)議。我們可以將其視為CLLocationManagerDelegate的包裝器,它在LocationManager類中使用刻盐。

實(shí)際上掏膏,LocationManager將實(shí)現(xiàn)CLLocationManagerDelegate中定義的總共九種方法劳翰,其中四種將通過自定義LocationManagerDelegate轉(zhuǎn)發(fā)到ViewController敦锌。

現(xiàn)在,我們需要在采用CLLocationManagerDelegate的擴(kuò)展中的LocationManager中實(shí)現(xiàn)兩個(gè)委托方法佳簸。這是第一個(gè):

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    print("Authorization status changed to: \(status)")
}

當(dāng)授權(quán)狀態(tài)從默認(rèn)的notDetermined更改為任何其他可能的值時(shí)乙墙,將調(diào)用此方法:restricteddenied生均,authorizedAlwaysauthorizedWhenInUse听想。

第二個(gè):

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    print("Location manager failed with error: " + "\(error.localizedDescription)"
}

如果在嘗試檢索位置時(shí)發(fā)生錯(cuò)誤,則會(huì)調(diào)用此方法马胧。實(shí)現(xiàn)此方法很重要汉买,或者Core Location在嘗試使用位置服務(wù)時(shí)會(huì)拋出異常。

添加這兩個(gè)委托方法后佩脊,構(gòu)建并運(yùn)行應(yīng)用程序蛙粘。核心位置將要求我們授權(quán)該應(yīng)用訪問我們的位置。

標(biāo)題下方的描述是我們之前添加到Info.plist的文本威彰。請(qǐng)務(wù)必選擇Always Allow出牧。

地理圍欄

現(xiàn)在已設(shè)置Core Location,我們可以開始跟蹤設(shè)備位置歇盼。不關(guān)心實(shí)際的設(shè)備位置舔痕。當(dāng)我們?cè)谌虑敖榻B時(shí),你會(huì)發(fā)現(xiàn)它應(yīng)該能夠在用戶離目標(biāo)一定距離時(shí)顯示出來豹缀。此功能稱為地理圍欄伯复,它包括監(jiān)視某個(gè)位置周圍的區(qū)域,特別是當(dāng)設(shè)備跨越該區(qū)域的邊界時(shí)邢笙。

項(xiàng)目中有一個(gè)Geo Fencing的按鈕啸如。其目的是啟動(dòng)或停止區(qū)域監(jiān)測(cè)。

開始區(qū)域監(jiān)測(cè)

ViewController.swift中鸣剪,找到toggleLocationTracking();這已經(jīng)連接到Geo Fencing按鈕的tap事件组底。該方法包含注釋的打印語句;暫時(shí)忽略它們。

在方法的開頭添加此代碼:

isLocationTrackingActive = !isLocationTrackingActive
if isLocationTrackingActive { 
} else { 
}

isLocationTrackingActive是指示位置跟蹤當(dāng)前是否處于活動(dòng)狀態(tài)的標(biāo)記筐骇。你將填充if /else語句债鸡。

print語句移動(dòng)到if分支中并取消注釋。它們用于向控制臺(tái)顯示警告铛纬,提醒我們RMK位置是硬編碼的厌均。

仍在if分支內(nèi)部,在print語句之前告唆,添加以下代碼:

// 1 self.toggleLocationTrackingButton.setImage(#imageLiteral(resourceName: "arKit-fence-on"), for: .normal)
// 2
 let rmkLocation = Constants.razewareMobileKioskLocation
// 3
let rmkCoordinates = rmkLocation.location

當(dāng)用戶點(diǎn)擊地理圍欄按鈕棺弊,并且正在激活位置跟蹤時(shí):

  • 1: 我們更新按鈕圖像以指示它已打開晶密。
  • 2: 我們可以讀取RMK所在目標(biāo)點(diǎn)的位置。
  • 3: 我們閱讀該位置的坐標(biāo)模她。

RMK位置使用包含名稱和位置的自定義結(jié)構(gòu)在Constants.swift中進(jìn)行靜態(tài)硬編碼稻艰。

現(xiàn)在你有了目標(biāo)位置。下一步是開始監(jiān)視該目標(biāo)周圍的區(qū)域侈净。在print語句之后尊勿,添加以下代碼:

do {
    try locationManager.startMonitoring( 
        location: rmkCoordinates, 
        radius: Constants.geofencingRadius, 
        identifier: Constants.razewareMobileKioskIdentifier)
} catch (let error as LocationManager.GeofencingError) { 
     print( "An error occurred while monitoring a region: \(error)") 
} catch (let error) { 
    print( "Tracking location error: \(error.localizedDescription)") 
}

startMonitoring()開始監(jiān)視半徑為Constants.geofencingRadius米的rmlCoordinates位置周圍的區(qū)域,當(dāng)前設(shè)置為300畜侦,并在Constants.razewareMobileKioskIdentifier中指定一個(gè)字符串標(biāo)識(shí)符元扔。

監(jiān)控一個(gè)地區(qū)

監(jiān)視啟動(dòng)在LocationManager類中完成。打開LocationManager.swift旋膳。在initialize方法之后澎语,添加以下內(nèi)容:

func startMonitoring(location: CLLocationCoordinate2D, radius: Double, identifier: String) throws {
// 1
guard CLLocationManager.isMonitoringAvailable( for: CLCircularRegion.self)
else { throw GeofencingError.notSupported }
guard CLLocationManager.authorizationStatus() == .authorizedAlways
else { throw GeofencingError.notAuthorized }
trackedLocation = location
// 2
let region = CLCircularRegion(center: location, radius: radius, identifier: identifier)
// 3
region.notifyOnEntry = true 
region.notifyOnExit = true
// 4
locationManager.startMonitoring(for: region)
// 5
// Delay state request by 1 second due to an old bug 
// http://www.openradar.me/16986842
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), qos: .default, flags: []) {
    self.locationManager.requestState(for: region)
    }
}

上面代碼作用如下:

  • 1:我們首先需要檢查:
    ??a: 可以使用區(qū)域監(jiān)視,因?yàn)槟承┰O(shè)備可能不支持它验懊。
    ??b: 用戶授權(quán)始終開啟位置跟蹤擅羞。
  • 2: 這是我們要監(jiān)控的區(qū)域。它以位置為中心鲁森,并具有由radius參數(shù)指定的半徑祟滴。
  • 3: 我們可以啟用這兩個(gè)文件來指示我們有興趣在設(shè)備進(jìn)入和退出受監(jiān)控區(qū)域時(shí)通知的Core Location
  • 4: 這開始了區(qū)域監(jiān)測(cè)歌溉。
  • 5: 在這里垄懂,我們可以請(qǐng)求通過Core Location委托異步傳送的區(qū)域的立即狀態(tài)。

注意:區(qū)域是共享的應(yīng)用程序資源痛垛,這意味著如果我們有多個(gè)CLLocationManager實(shí)例草慧,則每個(gè)實(shí)例都會(huì)在進(jìn)入或退出任何受監(jiān)視區(qū)域時(shí)收到通知。

此外匙头,對(duì)狀態(tài)的請(qǐng)求包含在延遲調(diào)用中漫谷,因?yàn)榕f的錯(cuò)誤導(dǎo)致該方法在同步調(diào)用時(shí)不執(zhí)行任何操作。

現(xiàn)在蹂析,我們需要提供在設(shè)備進(jìn)入和退出受監(jiān)控區(qū)域時(shí)由Core Location調(diào)用的兩個(gè)委托方法舔示。

LocationManager中,轉(zhuǎn)到底部电抚。在最終擴(kuò)展中惕稻,添加進(jìn)入?yún)^(qū)域時(shí)調(diào)用的委托方法:

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    if let region = region as? CLBeaconRegion { } else {
    print("Entered in region: \(region)")
   delegate?.locationManager(
   self, didEnterRegionId: region.identifier) }
}

我們現(xiàn)在可以忽略if分支;它將在以后使用。 else分支是區(qū)域輸入事件被轉(zhuǎn)發(fā)到LocationManagerDelegate中定義的委托作為locationManager(_:didEnterRegionId :)并在ViewController中實(shí)現(xiàn)蝙叛。雖然它現(xiàn)在已經(jīng)空了俺祠,但我們很快就會(huì)照顧到它。我們將區(qū)域標(biāo)識(shí)符傳遞給該方法。

didEnterRegion之后蜘渣,添加其鏡面反射didExitRegion方法:

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { 
    print("Left region \(region)") 
    delegate?.locationManager( self, didExitRegionId: region.identifier) 
}

這類似于通過調(diào)用locationManager(_:didExitRegionId :)來通知何時(shí)設(shè)備離開受監(jiān)視區(qū)域淌铐。

注意:我們可以在一臺(tái)設(shè)備上監(jiān)控多達(dá)20個(gè)區(qū)域。

當(dāng)我們對(duì)requestState做一個(gè)延遲調(diào)用(for :)時(shí)蔫缸,請(qǐng)記住startMonitoring(location:radius:identifier :)嗎腿准?結(jié)果通過另一個(gè)CLLocationManagerDelegate方法異步傳遞。在locationManager(_:didFailWithError :)之后添加它:

func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
    switch state { 
    // 1 
    case .inside:
    locationManager(manager, didEnterRegion: region)
    // 2 
    case .outside, .unknown:
        break 
    }
}

上面代碼作用如下:

  • 1: 在這里捂龄,我們有興趣了解設(shè)備何時(shí)已經(jīng)位于受監(jiān)控區(qū)域內(nèi)释涛,在這種情況下加叁,我們將委托給locationManager(_:didEnterRegion :)倦沧,就好像它是從受監(jiān)控區(qū)域外部到內(nèi)部的正常過渡一樣。
  • 2: 在這里它匕,你說你對(duì)其他案件并不感興趣展融。

你在開始監(jiān)控之后立即調(diào)用requestState(for :)的原因是我們想知道 - 在那個(gè)特定的時(shí)刻 - 如果設(shè)備已經(jīng)在被監(jiān)控區(qū)域內(nèi)。區(qū)域監(jiān)視僅在狀態(tài)發(fā)生變化時(shí)觸發(fā)事件豫柬,即從外部轉(zhuǎn)換到內(nèi)部告希,反之亦然。如果我們已經(jīng)在室內(nèi)或室外烧给,它將不會(huì)觸發(fā)任何事件燕偶。

如果設(shè)備已在內(nèi)部,那么我們需要對(duì)信標(biāo)執(zhí)行某些操作础嫡。稍后會(huì)詳細(xì)介紹指么!

還有一個(gè)委托方法可以實(shí)現(xiàn),至少在調(diào)試方面是有用的榴鼎。在locationManager之后添加它(_:didDetermineState:for :)

func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
    print("Geofencing monitoring failed for region " + "\(String(describing: region?.identifier))," + "error: \(error.localizedDescription)")
}

當(dāng)startMonitoring(for :)失敗時(shí)調(diào)用此方法伯诬。將錯(cuò)誤消息打印到控制臺(tái)可能有助于我們了解問題何時(shí)發(fā)生以及發(fā)生的原因。

對(duì)區(qū)域變化做出反應(yīng)

當(dāng)設(shè)備穿過受監(jiān)控區(qū)域時(shí)巫财,Core Location會(huì)通知LocationManager盗似。反過來,LocationManager使用LocationManagerDelegate協(xié)議的兩種方法將通知轉(zhuǎn)發(fā)給ViewController平项。我們將使用這兩個(gè)通知來玩信標(biāo)赫舒。但是,我們可以在用戶進(jìn)入某個(gè)區(qū)域時(shí)提醒用戶闽瓢。

ViewController.swift中接癌,滾動(dòng)到locationManager(_:didEnterRegionId :)并添加以下代碼:

// Notify the user that he’s entered the geofenced zone 
let distance = String(format: "%0.0f", Constants.geofencingRadius)
let message = "\(regionId) is less than \(distance) meters. " + "Come say hi and interact with our e-billboard"
let title = regionId
showAlert(with: "geofencing-notification", title: title, message: message)

這會(huì)提示警報(bào)并通知用戶RMK在附近。

停止區(qū)域監(jiān)測(cè)

當(dāng)我們點(diǎn)擊Geo Fencing按鈕時(shí)鸳粉,到目前為止編寫的代碼負(fù)責(zé)啟動(dòng)區(qū)域監(jiān)視并通知設(shè)備何時(shí)穿過區(qū)域扔涧。要關(guān)閉圓圈,我們必須在相反的方向上工作:停止區(qū)域監(jiān)控。

轉(zhuǎn)到toggleLocationTracking()點(diǎn)擊處理程序枯夜。早些時(shí)候弯汰,如果未實(shí)現(xiàn),你就離開了最外層的else分支湖雹。使用以下代碼填充該空白區(qū)域:

// 1 
self.toggleLocationTrackingButton.setImage( #imageLiteral(resourceName: "arKit-fence-off"), for: .normal) 
// 2 
locationManager.stopMonitoringRegions()

上面代碼作用如下:

  • 1: 恢復(fù)地理圍欄按鈕的關(guān)閉圖標(biāo)咏闪。
  • 2: 停止區(qū)域監(jiān)控。

但是摔吏,該停止方法尚不存在鸽嫂。打開LocationManager.swift并在startMonitoring()之后添加它:

func stopMonitoringRegions() { 
    trackedLocation = nil
    for region in locationManager.monitoredRegions { 
       locationManager.stopMonitoring(for: region) }
}

我們無需跟蹤已開始監(jiān)控的區(qū)域;它們列在CLLocationManagermonitoredRegions屬性中 - 我們只需遍歷它們并停止每個(gè)屬性。

距離計(jì)算

到目前為止征讲,我們已經(jīng)忽略了獲取實(shí)際的設(shè)備位置据某。但是,這是通過實(shí)現(xiàn)單個(gè)委托方法完成的诗箍。

打開LocationManager并在擴(kuò)展的開頭添加此方法以實(shí)現(xiàn)CLLocationManagerDelegate

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // 1 
    guard let currentLocation = locations.last else { return }
    // 2 
    guard let trackedLocation = trackedLocation else { return }
    let location = CLLocation(latitude: trackedLocation.latitude, longitude: trackedLocation.longitude)
    // 3 
    let distance = currentLocation.distance(from: location)
    print("Distance: \(distance)")
}

上面代碼作用如下:

  • 1: 委托方法接收一個(gè)數(shù)組癣籽,該數(shù)組包含自上次調(diào)用以來的位置列表,按從最舊到最新的時(shí)間戳排序滤祖。你拿最后一個(gè);即最近的筷狼。

  • 2: 我們獲得包含目標(biāo)位置的CLLocation - RMK。由于它們存儲(chǔ)為CLLocationCoordinate2D結(jié)構(gòu)匠童,因此我們將轉(zhuǎn)換為CLLocation的實(shí)例埂材。

  • 3: 我們計(jì)算兩個(gè)位置之間的距離并將其打印到Xcode控制臺(tái)。

測(cè)試

我們寫了很多代碼√狼螅現(xiàn)在是時(shí)候運(yùn)行應(yīng)用程序來查看它的全部動(dòng)態(tài)俏险。為方便起見,選擇模擬器首昔,然后構(gòu)建并運(yùn)行應(yīng)用程序寡喝。然后,點(diǎn)擊Geo Fencing按鈕勒奇。

除了按鈕圖標(biāo)改變顏色预鬓,顯然沒有任何反應(yīng)。如果尚未顯示赊颠,請(qǐng)打開Xcode控制臺(tái)格二。在執(zhí)行toggleLocationTracking()時(shí)遇到的打印語句會(huì)打印出警告:

WARNING: ensure that the Razeware Mobile Kiosk is at a location near you, otherwise geofencing and beacon detection won't work The current location is: Pisa (43.7153187, 10.4019739)

如前所述,RMK位置已經(jīng)靜態(tài)地設(shè)置在比薩斜塔附近竣蹦。我們可能想知道:我是否必須去那里測(cè)試應(yīng)用程序顶猜?當(dāng)然,請(qǐng)隨意去痘括,但是在這個(gè)項(xiàng)目中測(cè)試地理圍欄的方法比較簡單一些长窄。

Xcode中滔吠,打開位于res組中的TestLocations.gpx。它應(yīng)該如下所示:

<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
      <wpt lat="43.72281" lon="10.3958585">
            <name>Lungarno, Pisa</name>
            <time>2010-01-01T00:00:00Z</time>
      </wpt>
      <wpt lat="43.7153187" lon="10.4019739">
        <name>Piazza dei Miracoli, Pisa</name>
        <time>2010-01-01T00:00:30Z</time>
       </wpt>
<wpt lat="43.72281" lon="10.3958585">
  <name>Lungarno, Pisa</name>
  <time>2010-01-01T00:01:00Z</time>
    </wpt>
</gpx>

該文件包含由三個(gè)航點(diǎn)定義的路線:起點(diǎn)挠日,目的地和返回起點(diǎn)疮绷。選擇初始位置距離目的地約1,000米的距離,即RMK位置嚣潜。

除了坐標(biāo)和名稱冬骚,每個(gè)航路點(diǎn)也有時(shí)間。絕對(duì)值的重要性并不重要懂算,但相對(duì)差異表示從一個(gè)位置到下一個(gè)位置所需的時(shí)間只冻。如果你仔細(xì)觀察,你會(huì)注意到一個(gè)航點(diǎn)到下一個(gè)航點(diǎn)的差異為30秒计技。

Xcode中喜德,我們可以通過調(diào)試區(qū)域中的“模擬位置”按鈕選擇此文件。這使得Xcode模擬.gpx文件中定義的路由酸役。


如果你看一下Xcode中的控制臺(tái)面板住诸,你應(yīng)該開始看到距離更新,如下所示:

Distance: 935.436896038748 
Distance: 903.575340351579 
Distance: 871.696596559219 
Distance: 839.800076468077 
Distance: 807.885154208941 
Distance: 775.951162495185 
Distance: 743.997388360152 
Distance: 712.023068265481

每次Core Location為剛剛實(shí)現(xiàn)的方法locationManager(_:didUpdateLocations)提供新位置時(shí)涣澡,都會(huì)更新。

一旦距離低于我們正在監(jiān)視的區(qū)域的半徑丧诺,我們應(yīng)該看到如下日志:

Entered in region: CLCircularRegion (identifier:'The Razeware Mobile Kiosk', center:<+43.71531870,+10.40197390>, radius:300.00m)

這證實(shí)了設(shè)備的模擬位置已進(jìn)入地理圍欄區(qū)域入桂。如果稍等一下,當(dāng)設(shè)備退出該區(qū)域時(shí)驳阎,我們會(huì)看到類似的消息抗愁。

Left region CLCircularRegion (identifier:'The Razeware Mobile Kiosk', center:<+43.71531870,+10.40197390>, radius:300.00m)

作為旁注,如果我們想使用iPhone和真實(shí)位置呵晚,我們可以這樣做蜘腌。只有一個(gè)建議:我們可能希望查看數(shù)據(jù)/ Constants.swift并在其中設(shè)置新位置,而不是要求Razeware移動(dòng)信息亭移動(dòng)到我們當(dāng)前位置附近饵隙。

信標(biāo)

既然我們可以檢測(cè)到設(shè)備何時(shí)進(jìn)入或退出某個(gè)區(qū)域撮珠,那么下一步是什么?我們可能已經(jīng)注意到在進(jìn)入時(shí)會(huì)向用戶提示警報(bào)金矛,但僅此一點(diǎn)不應(yīng)該證明這么多工作是正確的芯急。

這是信標(biāo)發(fā)揮作用的地方。但他們是什么驶俊?信標(biāo)是一小塊硬件娶耍,可定期通過藍(lán)牙LE(低能耗)發(fā)出信號(hào)。在最簡單的化身中饼酿,信號(hào)由三個(gè)靜態(tài)數(shù)據(jù)字段組成:Proximity UUID榕酒,MajorMinor胚膊。

就這么簡單。但這對(duì)于接近檢測(cè)也很有用:我們的手機(jī)可以攔截信號(hào)想鹰,識(shí)別信標(biāo)并觸發(fā)向注冊(cè)信標(biāo)的應(yīng)用程序發(fā)出通知澜掩。

注意:如上所述,Proximity杖挣,MajorMinor是靜態(tài)數(shù)據(jù);然而肩榕,它們可以(并且應(yīng)該)在購買信標(biāo)后進(jìn)行更改。更新數(shù)據(jù)的過程取決于供應(yīng)商惩妇。
數(shù)據(jù)可用于任何有助于特定目的的方式;然而株汉,一個(gè)典型的模式是使用鄰近度來識(shí)別我們的公司或應(yīng)用程序,一個(gè)部分的主要部分歌殃,如房間乔妈,建筑物,商店和未成年人氓皱,以區(qū)分同一部分中的信標(biāo)路召。

信標(biāo)是低能耗設(shè)備;出于這個(gè)原因,他們可以在一個(gè)電池上運(yùn)行幾年波材,不間斷地每天24小時(shí)股淡,365天 - 有時(shí)甚至366天 - 廣播他們的數(shù)據(jù)。由于能量低廷区,它們的信號(hào)范圍有限唯灵。因此,它們通常用于近距離檢測(cè)隙轻。

檢測(cè)廣播信號(hào)的設(shè)備可以用一致的近似來確定它與信標(biāo)的距離埠帕。距離分為三個(gè)不同的范圍:

  • Far:超過10米遠(yuǎn)。
  • Near: 在幾米之內(nèi)玖绿。
  • Immediate: 幾厘米遠(yuǎn)敛瓷。

檢測(cè)信標(biāo)

在本項(xiàng)目中,我們將使用信標(biāo)來檢測(cè)何時(shí)接近RMK斑匪,并觸發(fā)自動(dòng)QR碼檢測(cè)呐籽。在iOS中,信標(biāo)由Core Location處理秤标,這是一個(gè)我們應(yīng)該已經(jīng)相當(dāng)熟悉的框架绝淡。

注意:要調(diào)試和測(cè)試信標(biāo), 我們需要一個(gè)信標(biāo)苍姜±谓停可以使用備用iPhone作為信標(biāo)或真正的信標(biāo); App Store中有一些應(yīng)用程序可讓我們將iPhone用作燈塔。我們還需要另一部iPhone來測(cè)試應(yīng)用程序衙猪。

信標(biāo)檢測(cè)不是發(fā)現(xiàn)過程馍乙。當(dāng)任何信標(biāo)出現(xiàn)在雷達(dá)上時(shí)布近,我們不會(huì)要求核心位置通知。

相反丝格,我們要求Core Location告知我們何時(shí)檢測(cè)到特定信標(biāo)撑瞧,由其接近,主要和次要標(biāo)識(shí)显蝌,或者我們也可以在相同的鄰近UUID或接近度和主要信號(hào)下請(qǐng)求一系列信標(biāo)预伺。關(guān)鍵是我們要求Core Location監(jiān)控你已經(jīng)知道的信標(biāo)。

核心位置定義CLBeaconRegion類曼尊,用于指定要監(jiān)視的信標(biāo)酬诀。只有一個(gè)信標(biāo)的區(qū)域已經(jīng)在data/Constants.swift中定義,打包成一個(gè)名為razeadBeacons的數(shù)組骆撇。

我們應(yīng)該將beacon標(biāo)識(shí)替換為我們自己的數(shù)據(jù)瞒御。最好不要重復(fù)使用我們?cè)诖a中找到的相同身份,因此至少應(yīng)該為我們的信標(biāo)生成一個(gè)新的鄰近UUID神郊。

管理信標(biāo)監(jiān)控

打開LocationManager.swift肴裙。在使用MARK: - Beacons標(biāo)識(shí)的擴(kuò)展中,添加以下兩種方法:

// 1 
func startMonitoring(beacons: [CLBeaconRegion]) {
    for beacon in beacons {
        startMonitoring(beacon: beacon)
    } 
}
// 2 
func startMonitoring(beacon: CLBeaconRegion) {
    guard CLLocationManager.isRangingAvailable() else { 
        print("[ERROR] Beacon ranging is not available") 
        return 
    }
    locationManager.startMonitoring(for: beacon)
}

上面的代碼作用如下:

  • 1: 注冊(cè)信標(biāo)區(qū)域陣列涌乳。這循環(huán)遍歷數(shù)組并將注冊(cè)委托給單區(qū)域方法蜻懦。
  • 2: 在檢查該應(yīng)用程序運(yùn)行的設(shè)備上是否有該功能后,注冊(cè)單個(gè)信標(biāo)區(qū)域爷怀。

在此處阻肩,添加用于停止監(jiān)視的方法:

func stopMonitoring(beacons: [CLBeaconRegion]) { 
    for beacon in beacons { 
        stopMonitoring(beacon: beacon) 
    } 
}

func stopMonitoring(beacon: CLBeaconRegion) { 
    locationManager.stopMonitoring(for: beacon) 
}

我們必須在ViewController.swift中調(diào)用這些方法來分別啟動(dòng)和停止信標(biāo)監(jiān)視。我們將在下一部分中執(zhí)行此操作运授。

當(dāng)檢測(cè)信標(biāo)時(shí),核心位置語言被稱為測(cè)距乔煞,實(shí)際上有兩個(gè)不同的階段:

  • Entering the region: 當(dāng)檢測(cè)到屬于該區(qū)域ID的第一個(gè)信標(biāo)時(shí)發(fā)生吁朦。
  • Ranging a single beacon: 在單個(gè)信標(biāo)范圍內(nèi)發(fā)生。

第一種情況是通過之前實(shí)現(xiàn)的locationManager(_:didEnterRegion :)委托方法處理的渡贾。如果還記得逗宜,你把if分支留空了。現(xiàn)在用以下代碼填寫:

print("Entered in beacon region: \(region)") 
locationManager.startRangingBeacons(in: region)

這會(huì)將消息打印到控制臺(tái)并啟動(dòng)屬于我們輸入的區(qū)域的測(cè)距信標(biāo)空骚。

現(xiàn)在纺讲,當(dāng)信標(biāo)在范圍內(nèi)或發(fā)生錯(cuò)誤時(shí),剩下的就是通知囤屹。在最后一個(gè)LocationManager擴(kuò)展的末尾熬甚,添加以下兩個(gè)委托方法:

// MARK: Beacons 
// 1 
func locationManager(_ manager: CLLocationManager,didRangeBeacons beacons: [CLBeacon],in region: CLBeaconRegion) {
    for beacon in beacons { 
        delegate?.locationManager(self, didRangeBeacon: beacon) 
    }
}
// 2 
func locationManager(_ manager: CLLocationManager,rangingBeaconsDidFailFor region: CLBeaconRegion, withError error: Error) {
    print("Beacon ranging failed for region \(region) " + "with error: \(error.localizedDescription)")
}

上面代碼如下:

  • 1: 當(dāng)信標(biāo)數(shù)組中的一個(gè)或多個(gè)信標(biāo)被測(cè)距時(shí),調(diào)用此方法肋坚。每個(gè)信標(biāo)都通過LocationManagerDelegate轉(zhuǎn)發(fā)到ViewController乡括。
  • 2: 如果信標(biāo)范圍失敗肃廓,則調(diào)用此方法。要保持一致诲泌,請(qǐng)向控制臺(tái)輸出錯(cuò)誤消息盲赊。

最后一個(gè)修復(fù)的細(xì)節(jié):在stopMonitoring(beacon :)中,我們調(diào)用了stopMonitoring(for :)敷扫。最好還停止屬于該區(qū)域的測(cè)距信標(biāo)哀蘑,因此在方法開頭添加此行代碼,就在stopMonitoring之前:

locationManager.stopRangingBeacons(in: beacon)

手動(dòng)停止區(qū)域和信標(biāo)監(jiān)控

還記得UI中的按鈕以啟動(dòng)地理圍欄嗎葵第?我們可以使用相同的按鈕來停止它 - 這已經(jīng)在其處理程序方法中完成绘迁。但是,需要進(jìn)行一些清理羹幸。

ViewController.swift中脊髓,滾動(dòng)到toggleLocationTracking()。在最頂層的if語句的else分支中栅受,在locationManager.stopMonitoringRegions()之后将硝,添加:

// 1 
locationManager.stopMonitoring(beacons: Constants.razeadBeacons) 
// 2 
beaconStatusImage.isHidden = true 
beaconStatusLabel.isHidden = true

上面的代碼作用如下:

  • 1: 如果激活,則停止信標(biāo)監(jiān)視屏镊。
  • 2: 隱藏信標(biāo)狀態(tài)圖標(biāo)和標(biāo)簽依疼。

消耗信標(biāo)檢測(cè)

現(xiàn)在,信標(biāo)檢測(cè)是一件事而芥,我們可以觸發(fā)自動(dòng)QR掃描模式律罢。目前,在ViewController中棍丐,當(dāng)用戶點(diǎn)擊屏幕時(shí)误辑,會(huì)運(yùn)行QR代碼掃描嘗試。是時(shí)候修改這種行為了歌逢。這是你需要做的:

  • 保留點(diǎn)擊以關(guān)閉視頻播放器功能巾钉。
  • 將方法的其余部分(負(fù)責(zé)QR碼檢測(cè))移動(dòng)到新的scanQRCode()方法中。

touchesBegan(_:with)開頭的if語句的右括號(hào)之后秘案,添加以下兩行:

// 1 
} 
// 2 
private func scanQRCode() {

上述代碼作用如下:

  • 1: 關(guān)閉touchesBegun方法砰苍。
  • 2: 開始一個(gè)新的scanQRCode方法,它“繼承”以前屬于touchesBegun的代碼阱高。

編譯項(xiàng)目以確保我們?cè)谡_的位置輸入代碼赚导。

響應(yīng)信標(biāo)通知

在響應(yīng)信標(biāo)通知之前,我們應(yīng)確保已開始測(cè)距赤惊。無需檢查代碼 - 我們尚未完成此操作吼旧。設(shè)備進(jìn)入受監(jiān)控區(qū)域后,本應(yīng)用應(yīng)啟動(dòng)測(cè)距信標(biāo)荐捻,這將在ViewController中調(diào)用locationManager(_:didEnterRegionId :)委托方法時(shí)發(fā)生黍少。

在該方法的末尾添加此行:

locationManager.startMonitoring( beacons: Constants.razeadBeacons)

要執(zhí)行自動(dòng)QR掃描寡夹,我們需要重復(fù)調(diào)??用之前創(chuàng)建的scanQRCode()方法,并在后續(xù)調(diào)用之間保持一定的時(shí)間間隔厂置。這個(gè)要求聞起來像一個(gè)計(jì)時(shí)器 - 這實(shí)際上就是你要使用的菩掏。 ViewController已經(jīng)包含了處理計(jì)時(shí)器的大部分代碼。我們需要將其與信標(biāo)范圍連接起來昵济。

找到locationManager(_:didRangeBeacon :)委托方法;當(dāng)新信標(biāo)在范圍內(nèi)時(shí)調(diào)用它智绸。添加此實(shí)現(xiàn):

// 1 
beaconStatusImage.isHidden = false 
beaconStatusLabel.isHidden = false
// 2 
switch beacon.proximity { 
case .immediate:
    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-1") 
case .near:
    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-2") 
case .far:
    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-3") 
case .unknown:
    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-4") }
// Start auto scan, but only if the app is in the foreground 
// and there is no active billboard 
// 3 
if UIApplication.shared.applicationState == .active && (billboard == nil || billboard?.hasBillboardNode == false) {
    // 4 
    startAutoscanTimer()
}

上面的代碼作用如下:

  • 1: 默認(rèn)情況下隱藏有一個(gè)圖標(biāo)和標(biāo)簽适篙,用于報(bào)告信標(biāo)測(cè)距狀態(tài)奕谭。因?yàn)樵谶@種委托方法中,信標(biāo)已被范圍化纹蝴,使它們可見海铆。
  • 2: 為圖標(biāo)選擇合適的圖像迹恐。
  • 3: 如果應(yīng)用程序在前臺(tái),并且當(dāng)前沒有顯示廣告牌......
  • 4: ...然后啟動(dòng)計(jì)時(shí)器卧斟,啟用自動(dòng)QR檢測(cè)殴边。

相反,當(dāng)燈塔離開時(shí)珍语,我們需要做相反的事情锤岸。將以下實(shí)現(xiàn)添加到locationManager(_:didLeaveBeacon)

// 1 
stopAutoscanTimer() 
// 2 
beaconStatusImage.isHidden = true 
beaconStatusLabel.isHidden = true

上面代碼作用如下:

  • 1: 這會(huì)停止計(jì)時(shí)器,禁用自動(dòng)QR掃描板乙。
  • 2: 這會(huì)隱藏信標(biāo)狀態(tài)圖標(biāo)和標(biāo)簽是偷。

當(dāng)用戶退出該區(qū)域時(shí),我們希望執(zhí)行相同的操作募逞。將相同的代碼添加到locationManager(_:didExitRegionId :)

stopAutoscanTimer() 
beaconStatusImage.isHidden = true 
beaconStatusLabel.isHidden = true

最后一個(gè)位置應(yīng)該取消定時(shí)器蛋铆,當(dāng)我們手動(dòng)點(diǎn)擊Geo Fencing按鈕停止檢測(cè)時(shí)。這由toggleLocationTracking()方法處理放接。找到它戒职,并在else分支的底部,隱藏圖標(biāo)和標(biāo)簽后透乾,添加以下代碼行:

stopAutoscanTimer()

如果計(jì)時(shí)器處于活動(dòng)狀態(tài),則會(huì)停止計(jì)時(shí)

定時(shí)器和QR碼檢測(cè)

當(dāng)計(jì)時(shí)器為紅色時(shí)磕秤,我們想要開始QR碼檢測(cè)嘗試乳乌。找到didFireTimer(timer :)方法,只需添加:

scanQRCode()

測(cè)試

要測(cè)試信標(biāo)市咆,我們需要使用物理設(shè)備汉操。我們可以使用啟動(dòng)項(xiàng)目附帶的.gpx文件來模擬路徑,從而可以測(cè)試設(shè)備何時(shí)進(jìn)入和離開受監(jiān)控區(qū)域蒙兰。

我們還需要一個(gè)信標(biāo)或備用iPhone磷瘤,我們可以在其上安裝將其變?yōu)樾艠?biāo)的應(yīng)用程序芒篷。在App Store中有幾個(gè)這樣的應(yīng)用程序。

準(zhǔn)備好后采缚,運(yùn)行應(yīng)用程序针炉,然后通過Xcode調(diào)試區(qū)域中的Simulate Locations按鈕加載.gpx文件。等待虛擬位置距離RMK 300米范圍內(nèi)扳抽。

如果不想運(yùn)行模擬路線篡帕,那么有兩種選擇:

  • 選項(xiàng)#1:手動(dòng)重新配置應(yīng)用以使用我們附近的真實(shí)位置:
    1: 我們可以使用Google地圖找到坐標(biāo),這些坐標(biāo)在我們將地圖置于當(dāng)前位置或至少靠近它之后顯示在網(wǎng)址中贸呢。
    2: 獲得坐標(biāo)后镰烧,打開Constants.swift,然后將坐標(biāo)替換為實(shí)際坐標(biāo)楞陷。
  • 選項(xiàng)#2:通過.gpx文件避免模擬路由怔鳖,但改為使用靜態(tài)虛擬位置進(jìn)行配置:
    1: 在Xcode中,找到res組中包含的TestLocations.gpx固蛾。
    2: 右鍵單擊它结执,然后選擇在Finder中顯示。
    3: 彈出時(shí)從Finder中選擇.gpx文件魏铅,然后復(fù)制并粘貼昌犹,或右鍵單擊文件并選擇復(fù)制。
    4: 將重復(fù)的文件重命名為StaticLocation.gpx览芳,或我們選擇的任何其他名稱斜姥。
    5: 在Xcode中,右鍵單擊res組沧竟,選擇Add files to Chapter13”铸敏,然后選擇新創(chuàng)建的文件,并單擊 Add 按鈕悟泵。
    6: 打開文件并刪除第一個(gè)和最后一個(gè) <wpt> ... </ wpt>標(biāo)簽杈笔。
    7: 現(xiàn)在,我們可以在使用
    Xcode的模擬位置功能時(shí)選擇此文件糕非。這會(huì)將我們的設(shè)備放在RMK**旁邊蒙具。

無論我們選擇哪種方法,當(dāng)運(yùn)行應(yīng)用程序朽肥,并且設(shè)備位于受監(jiān)控區(qū)域內(nèi)時(shí)禁筏,都會(huì)激活信標(biāo)監(jiān)控。

只要受監(jiān)控的信標(biāo)有范圍衡招,就應(yīng)該看到啟用自動(dòng)檢測(cè)按鈕:

這表示正在進(jìn)行自動(dòng)QR掃描篱昔。此外,工具欄中央會(huì)顯示一個(gè)圖標(biāo),通知信標(biāo)在范圍內(nèi):

  • 綠色: 非常近
  • 橙色: 近
  • 紅色: 遠(yuǎn)
  • 灰色: 未知

我們可以使用它來手動(dòng)關(guān)閉廣告牌州刽。請(qǐng)注意空执,創(chuàng)建廣告牌時(shí),應(yīng)用會(huì)自動(dòng)停止地理圍欄和自動(dòng)檢測(cè)穗椅。

如果無法對(duì)信標(biāo)進(jìn)行測(cè)距辨绊,請(qǐng)參閱以下列表:

  • 驗(yàn)證信標(biāo)是否已打開。
  • 確認(rèn)我們的iPhone已啟用藍(lán)牙房待。
  • 確認(rèn)我們的iPhone已啟用藍(lán)牙邢羔。驗(yàn)證iPhone是否位于受監(jiān)控區(qū)域內(nèi),即距離Constants.swift文件中指定的位置最多300米桑孩。
  • Xcode的控制臺(tái)中拜鹤,檢查錯(cuò)誤消息或警告。
  • 使用我們的信標(biāo)供應(yīng)商的應(yīng)用程序(如果有)來驗(yàn)證信標(biāo)是否在范圍內(nèi)流椒。
  • 驗(yàn)證信標(biāo)接近度敏簿,主要和次要數(shù)據(jù)是否與Constants.swift文件中指定的數(shù)據(jù)匹配。
上一章 目錄 下一章
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宣虾,一起剝皮案震驚了整個(gè)濱河市惯裕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绣硝,老刑警劉巖蜻势,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鹉胖,居然都是意外死亡握玛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門甫菠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挠铲,“玉大人,你說我怎么就攤上這事寂诱》髌唬” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵痰洒,是天一觀的道長瓢棒。 經(jīng)常有香客問我,道長丘喻,這世上最難降的妖魔是什么音羞? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮仓犬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舍肠。我一直安慰自己搀继,他們只是感情好窘面,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叽躯,像睡著了一般财边。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上点骑,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天酣难,我揣著相機(jī)與錄音,去河邊找鬼黑滴。 笑死憨募,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袁辈。 我是一名探鬼主播菜谣,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼晚缩!你這毒婦竟也來了尾膊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤荞彼,失蹤者是張志新(化名)和其女友劉穎冈敛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸣皂,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抓谴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了签夭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片齐邦。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖第租,靈堂內(nèi)的尸體忽然破棺而出措拇,到底是詐尸還是另有隱情,我是刑警寧澤慎宾,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布丐吓,位于F島的核電站,受9級(jí)特大地震影響趟据,放射性物質(zhì)發(fā)生泄漏券犁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一汹碱、第九天 我趴在偏房一處隱蔽的房頂上張望粘衬。 院中可真熱鬧,春花似錦、人聲如沸稚新。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褂删。三九已至飞醉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屯阀,已是汗流浹背缅帘。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留难衰,地道東北人钦无。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像召衔,于是被迫代替她去往敵國和親铃诬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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