技術筆記-iOS實戰005-加入地圖和即時位置,開始好玩起來了

閱讀時間約 24 分鐘

查景點,美食,導航,這些功能已經深深融入我們的生活了,背後重要的技術支柱就是科技巨頭所提供的龐大全球地圖資料庫,和隨身手機上的 GPS 定位功能,這是 App 的強項,非玩不可。

需求情境:

在陌生的城市探索,最常用到的地圖功能是什麼?找星巴克是我的第一名,第二是享受更多功能的便利商店,再其次就是,當開車時可能要找加油站。雖然現有的地圖 App 功能已經太強大,但若能由自己手刻出來,不僅會得到掌握技術的成就感,也可以做出許多變化式,說不定真能催生商業價值。

解決方案:

搞懂 Apple 官方提供的 MapKit,實作地圖的呈現,平移放大縮小,在不同位置的搜尋,搜尋結果的標示。然後運用手機 GPS 定位功能,標出自己的位置,找到最近的目標,畫出路徑規劃,算出車程,最後用語音把重要資訊念出來。

實作步驟:

與 Map 相見歡,最好從「官方文件」下手,因為版本更新太快,問 ChatGPT 或從網路搜尋技術文章,大多已不適用了,浪費了我一些時間。首先來個 Hello World,要讓地圖從 App 顯示出來,只要一行:Map(),當然開頭需要 import MapKit:

raw-image

原來顯示地圖這麼簡單!那就趕快進行下一步,就是搜尋,假設是搜尋星巴克,怎麼做呢?

先做一個按鈕,按鈕觸發 search(),結果存在 searchResults,searchResults 必須是「狀態變數」以便直接連動 UI,所以 Map 內容必須可顯示 searchResults 的內容,使用 Marker 元件。這樣一下子增加好多東西,詳述如下:

import SwiftUI
import MapKit

extension CLLocationCoordinate2D {
    static let chunan = CLLocationCoordinate2D(latitude: 24.686793691851307, longitude: 120.88055196685008)
}

struct MapView: View {
    @State var queryString: String = ""
    @State var searchResults: [MKMapItem] = []
    @State var visibleRegion: MKCoordinateRegion?

    func search(for query: String) {
        queryString = query
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = .pointOfInterest
        request.region = visibleRegion ?? MKCoordinateRegion(
            center: .chunan,
            span: MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
        )
        Task {
            let search = MKLocalSearch(request: request)
            let response = try? await search.start()
            searchResults = response?.mapItems ?? []
        }
    }
    
    var body: some View {
        Map() {
            ForEach(searchResults, id: \.self) {result in
                Marker(item: result)
            }
            .annotationTitles(.hidden)   
        }
        .safeAreaInset(edge: .bottom) {
            Button {
                search(for: "星巴克")
            } label: {
                Label("星巴克", systemImage: "cup.and.saucer.fill")
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

執行搜尋必須有一個特定範圍,所以先做一個 CLLocationCoordinate2D 的 extension,加入一個靜態欄位 chunan,型別等同於它自己。這個型態是用來表示經緯度的,很重要的基本資料結構。理想情況下,地圖移到哪裡,就該搜尋哪裡;但那等一下再做,因為技術的複雜性,所以用分解動作,按部就班來。

在 search 函示中,引用了 MKLocalSearch 物件的功能,此物件可以接受一段自然語言如「星巴克」這樣的搜尋語句,加上一個描述「搜尋範圍」的參數,就是剛剛定義的 以 chunan 為中心,長寬的度量為經緯度 0.00001。搜尋必須以非同步語法啟動之,結果傳回狀態變數 searchResults,是個陣列 [MKMapItem]。在 Map() 內容中,用一段 ForEach 迴圈產生 Marker 元件,呈現效果如下:

raw-image

事情進行的很順利,然後呢?當我把地圖位置移到台北,按下按鈕,一樣搜尋竹南,這就不對了,如何讓搜尋範圍自動指定為「當前位置」?當前位置可能是在導航情境中,自動定位而得的;也可能是當使用者因特定目的而將地圖移到任何地方,此時不能強行將範圍又改回當前位置,直覺操作的背後其運作機制卻不簡單,而強大的 MapKit 又把它變簡單:

.onMapCameraChange {
context invisibleRegion =
context.region}

關鍵就是 Map 的 onMapCameraChange 事件,此事件在上面兩種情境都會觸發,加入這行就搞定了。

raw-image





移到台北,一按按鈕,真的搜尋出台北的點了,很好。

然後呢?點一下這些地點圖示,沒有反應,直覺的期望應該是,點下去顯示該地點的重點摘要說明,然後規劃路徑並畫出來,以下繼續實作。

raw-image









首先要加入選取功能,必須在 Map 的建構式中傳入 selection 參數,並綁定到狀態變數:

// MapView 開頭處加入這行​
@State var selectedResult: MKMapItem?

// ​Map 初始化時,加入 selection 參數
Map(selection: $selectedResult)

這樣一加,點下任一個地點,它就會「亮起來」,變大顆,這一切的設計都非常直覺。

接下來,我們希望它不只亮起來,也要執行「路徑規劃」,我們把此動作包裝成 getDirection(),裡面再把 getRoute() 切開成另一包,這是有回傳值得 async 函數,之後會單獨用到。路徑規劃用到 MKDirection 物件,source 先固定成 .chunan,之後抓到自己位置之後,再替換掉。


// MapView 開頭處再加入此變數
@State var route: MKRoute?

// 這兩個函數也放在 MapView 裡面​
func getRoute(dest: MKMapItem) async -> MKRoute? {
route = nil
  let request = MKDirections.Request()
  request.source = MKMapItem(placemark: MKPlacemark(coordinate: .chunan))
  request.destination = dest
  let directions = MKDirections(request: request)
  let resp = try? await directions.calculate()
  return resp?.routes.first
}

func getDirection() {
guard let selectedResult else { return }
  Task {
  route = await getRoute(dest: selectedResult)
  }
}

// Map builder 裡面畫出 route
if let route {
MapPolyline(route)
.stroke(.blue, lineWidth: 5)
}
raw-image


畫出路徑了,讚吧!

繼續乘勝追擊,還有程式要做,就是「Where I am 」。因為移動中的使用者,必須動態的以當時位置為出發點,才有實用價值。

首先我們把地圖上系統內建的三個視覺元件叫出來:

.mapControls {
MapUserLocationButton() // 目前位置
MapCompass() // 指北針
MapScaleView() // 比例尺
}

這樣點一下地圖右上角的箭頭,就會把地圖定位在自己所在位置,但為了計算路徑,這樣還不夠,必須用程式抓出內部的經緯度資料,才有辦法客製化出即時的路徑,需要動用到 LocationManager,請看以下解析:


class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
  private var locationManager: CLLocationManager
@Published var currentLocation: CLLocation?
@Published var isUpdatingLocation: Bool = false

override init() {
locationManager = CLLocationManager()
super.init()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}

func startUpdating() {
locationManager.startUpdatingLocation()
isUpdatingLocation = true
}
func stopUpdating() {
locationManager.stopUpdatingLocation()
isUpdatingLocation = false
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let xx = locations.last else { return }
self.currentLocation = xx
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failed to find user's location: \(error.localizedDescription)")
}
}

此類別可控制啟動與暫停抓取位置,啟動狀態時最多一秒一筆資料,當靜止時則自動降低更新頻率。此資料對於後續變化應用很重要,因此設計讓它經常性顯示在畫面上,就放在底部:

// 在 MapView 開頭宣告狀態物件,確保在整個畫面的生命週期,都可取得物件內容 
@StateObject var locationManager = LocationManager()

// 下列視覺元件,顯示在地圖下方:
HStack {
if locationManager.isUpdatingLocation {
Button {
locationManager.stopUpdating()
} label: {
Label("更新中..", systemImage: "play.fill")
}
.buttonStyle(.borderedProminent)
} else {
Button {
locationManager.startUpdating()
} label: {
Label("停止更新", systemImage: "stop.fill")
}
.buttonStyle(.bordered)
}
if let xx = locationManager.currentLocation {
VStack {
HStack {
Text("更新時間:\(getCurrentTime())")
Spacer()
}
HStack {
Text("\(xx.coordinate.latitude) \(xx.coordinate.longitude)")
Spacer()
}
}
}


raw-image

以上程式首先為一個按鈕以啟動或停止偵測位置,旁邊簡單顯示出資料時間和抓到的經緯度。

確認經緯度資料到手後,改寫先前的 getRoute() 函式,判斷當 locationManager 有取得位置時,以它為起點來規劃路徑:

func getRoute(dest: MKMapItem) async -> MKRoute? {
route = nil
let request = MKDirections.Request()
if let xx = locationManager.currentLocation  {
request.source =  MKMapItem(placemark: MKPlacemark(coordinate: xx.coordinate))
} else {
request.source = MKMapItem(placemark: MKPlacemark(coordinate: .chunan))
}
request.destination = dest
let directions = MKDirections(request: request)
let resp = try? await directions.calculate()
return resp?.routes.first
}

呈現效果如左,至此功能架構已經完成大部分,最後稍加捏角捏邊,就要完成此專案了。



錦上添花的部分,可加入 MapKit 提供很強大的 LookAround 功能,觀看目標地點的 3D 實景,我們另做一個 View 的檔案 ItemInfoView,接受兩個參數,一樣放置在地圖下方:

if let selectedResult {
ItemInfoView(selectedResult: selectedResult, route: route)
.frame(height: 128)
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding([.top, .horizontal])
}

ItemInfoView.swift:

import SwiftUI
import MapKit

struct ItemInfoView: View {
    @State private var lookAroundScene: MKLookAroundScene?
    var selectedResult: MKMapItem
    var route: MKRoute?

    private var travelTime: String? {
        guard let route else { return nil}
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .abbreviated
        formatter.allowedUnits = [.hour, .minute]
        return formatter.string(from: route.expectedTravelTime)
    }

    func getLookAroundScene() {
        lookAroundScene = nil
        Task {
            let request = MKLookAroundSceneRequest(mapItem: selectedResult)
            lookAroundScene = try? await request.scene
        }
    }

    var body: some View {
        LookAroundPreview(initialScene: lookAroundScene)
            .overlay(alignment: .bottomTrailing) {
                HStack {
                    Text("\(selectedResult.name ?? "")")
                    if let travelTime {
                        Text(travelTime)
                    }
                }
                .font(.caption)
                .foregroundStyle(.white)
                .padding(10)
            }
            .onAppear {
                getLookAroundScene()
            }
            .onChange(of: selectedResult) {
                getLookAroundScene()
            }
    }
}

呈現效果如下,質感整個提升起來。

raw-image

最後最後再加一個小小的變化,既然我們已經抓到本身位置,可以針對搜尋後的一系列結果,分別計算距離,找出最近的地點,用語音播放出來,這樣走在路上就方便多了。便利商店和加油站都比照辦理,三個按鈕放下去。

// 將搜尋結果的整個陣列傳入,找最近的地點,並唸出重要資訊​
// 掛在 .onChange(of: searchResults) 裡面執行
func findNearestItem(items: [MKMapItem]) {
guard let xx = locationManager.currentLocation
else {return}
if let nearestItem = items.min(
by: {$0.placemark.location!.distance(
from: locationManager.currentLocation!)
< $1.placemark.location!.distance(
from: locationManager.currentLocation!) })
{
let trimedTitle = trimLeadingNumber(s: nearestItem.placemark.title!)
let itemName = nearestItem.placemark.name!
let line1 = "最近的\(queryString) 是 \(itemName) 位於 \(trimedTitle) "
let line2 = "距離 \(String(format: "%.1f", nearestItem.placemark.location!.distance(from: xx)/1000.0)) 公里 "
Task {route = await getRoute(dest: nearestItem)
let line3 = "車程 \(String(format: "%.1f", route!.expectedTravelTime/60.0)) 分鐘"
let msg = "\(line1)\(line2)\(line3)"
print(msg)
speak(text: msg)
}
}

// 為了消除地址之前的郵遞區號,讓念出的語音減少累贅
func trimLeadingNumber(s: String) -> String {
var numberOfDigit: Int = 0
let sArray = Array(s)
for i in 0..<sArray.count {
if !sArray[i].isNumber {
numberOfDigit = ibreak
}
}
return String(s.dropFirst(numberOfDigit))
}

// 念出中文語音​,注意要執行在 main thread
func speak(text: String) {
DispatchQueue.main.async {
let utterance = AVSpeechUtterance(string: text)
utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
utterance.rate = AVSpeechUtteranceDefaultSpeechRate
utterance.pitchMultiplier = 1.0
synthesizer.speak(utterance)
}
}

最後貼出在手機上的實際執行畫面:

raw-image

搜尋完會播放以下形式的語音:「最近的便利商店 是 全家 位於 台灣苗栗縣竹南鎮中山路166號2樓 距離 0.0 公里 車程 0.1 分鐘」。

以上,終於大功告成,真的可以拿著手機到處玩了。乾貨很多吧?誠心誠意,完整分享,敬請笑納。

Newman 2024/7/1

導覽頁:紐曼的技術筆記-索引







11會員
96內容數
漫步,悠閒自在的隨意行走!是行為,是態度,也是一種境界。
留言0
查看全部
發表第一個留言支持創作者!
newman的沙龍 的其他內容
需求情境: 為了讓多人使用 App,必須有驗證程序,以識別特定使用者,存取各自擁有的資源。 解決方案: 引用 google 所提供的雲端服務平台 Firebase,其中有多種驗證功能可選用。基於個人對 google 的偏愛,決定先採用 google signin 的方法,實作 login lo
需求情境: 一般的看盤軟體,雖然都能針對一籃子自選股票,列出其即時行情和當天漲幅,但若要看「五日漲幅」呢?那就少見了,但這對我很重要。因為小部位的波段性價差交易是個好策略,這時候若能排序好一整排看下來,可以節省大量點來點去的成本,很有價值,所以就來自己刻。 解決方案: 從大處著眼,UI 最外層
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
技術筆記-用 python 操作 google firestore 的基本方法 (2023/12/26) 技術筆記-以 nodejs 為後台,以 google sheet 充當資料庫 (2022/11/29)
firestore 是 google 所提供的雲端文件式資料庫服務,為各種開發工具提供了方便使用的 sdk,python 的套件名稱為 firebase-admin,用 pip 安裝後就可操作了。 pip install firebase-admin
需求情境: 為了讓多人使用 App,必須有驗證程序,以識別特定使用者,存取各自擁有的資源。 解決方案: 引用 google 所提供的雲端服務平台 Firebase,其中有多種驗證功能可選用。基於個人對 google 的偏愛,決定先採用 google signin 的方法,實作 login lo
需求情境: 一般的看盤軟體,雖然都能針對一籃子自選股票,列出其即時行情和當天漲幅,但若要看「五日漲幅」呢?那就少見了,但這對我很重要。因為小部位的波段性價差交易是個好策略,這時候若能排序好一整排看下來,可以節省大量點來點去的成本,很有價值,所以就來自己刻。 解決方案: 從大處著眼,UI 最外層
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Part.1 搞定基本的 UI 開始開發 iOS App。 首先準備一台 Mac,然後安裝 Xcode,新增專案,系統即刻生成基本的專案結構。coding 的起點在檔案 ContentView.swift: import SwiftUI struct ContentView: View {  
技術筆記-用 python 操作 google firestore 的基本方法 (2023/12/26) 技術筆記-以 nodejs 為後台,以 google sheet 充當資料庫 (2022/11/29)
firestore 是 google 所提供的雲端文件式資料庫服務,為各種開發工具提供了方便使用的 sdk,python 的套件名稱為 firebase-admin,用 pip 安裝後就可操作了。 pip install firebase-admin
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
[曼陀號領航計畫講座心得]筆記系統太複雜難用?電腦玩物站長 Esor 分享任務導向筆記術,簡化筆記管理,輕鬆找到所需資料,發揮筆記的真正價值。
大家是否有撰寫的心血因為不明原因,網路異常而造成心血全都消失,這篇純粹是一個抒發文...
Thumbnail
提到「做筆記」你會想到什麼?如果說知識的消化可以分為「輸入、處理和輸出」三個階段,那麼你又認為筆記扮演的任務角色會落到哪一個階段?做筆記只是「輸入」成效不高,想要「輸出」又耗時費力。要如何解決這個問題呢?卡片盒筆記法,就是目前最有效的解方,特別是在數位工具智能化之後,更顯得這套方法的強大。
Thumbnail
數位筆記術的改變力量 您有曾經煩惱過學習的效率嗎?或者是覺得難以捕捉那些稍縱即逝的靈感?在我們的日常生活和學習中,這些問題都是常見的挑戰。但幸運的是,有一種工具可以幫助我們改變這些情況,那就是數位筆記術。接下來,我將會講解三個使用數位筆記術的重要原則,這些原則能幫助我們更有效地學習和捕捉靈感。 原則
Thumbnail
《花漾年華》四連幅是我用當代水墨的抽象語彙,表現臺灣春天茂盛的新綠,充滿生命力,山櫻花盛開。傳統花鳥畫的創作哲學是「一花一世界,一葉一如來」,透過描繪大自然的生機呈現藝術家空靈的精神世界,心靈與自然合一,既超越自然又貼近自然。 在手工宣紙,揮灑充滿想像力的純淨世界,留白,紓解現代人過量的影音刺激。
Thumbnail
藝術家江心靜投入創作第二年,內在巨大無比的能量爆發,完成了 2015《藍色空間》三連幅當代水墨創作。 詩人的心找到了視覺語言,用深邃迷離的藍,與宇宙的能量結合,與早年創作的詩句呼應,內外合一的文人走向天命之路。
Thumbnail
雲海流動如一首神秘的歌,興起一股衝動,想透過創作表達心中感悟,第一個想法是強調「氣韻生動」的水墨,2012年採訪現代水墨之父劉國松,高齡八十的畫家精神奕奕,親自示範技法。 看著成品,在廚房燈下微笑,黑白構成的畫面,有一股力量,從山間飛到城市,推動著自己繼續,不在意繁華如夢,一步一步,往前走。
Thumbnail
兩年多前,開始有了重新架構網站的想法,所以把 Blogger 架設的網站,移轉至到了新的平台,當年我也做過了一些優缺點評析,剛好最近又再重新整理網站的所有架構,順便也重新啟動了一次 Hugo 架設網站的循環。
Thumbnail
因為要轉換一份新工作,認為自己在工作上必需更提高效率,剛好週圍有些朋友使用電子筆記,對於喜歡寫紙本筆記的人來說,是一個新的嘗試,原因有二:其一,筆記上有很多資料,在用完整本筆記之後,隨之石沉大海;其二,現在越來越多資訊是透過網路傳送,如果沒有好好收藏,資料就"放在"硬碟內了。 我是先去Youtube
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
[曼陀號領航計畫講座心得]筆記系統太複雜難用?電腦玩物站長 Esor 分享任務導向筆記術,簡化筆記管理,輕鬆找到所需資料,發揮筆記的真正價值。
大家是否有撰寫的心血因為不明原因,網路異常而造成心血全都消失,這篇純粹是一個抒發文...
Thumbnail
提到「做筆記」你會想到什麼?如果說知識的消化可以分為「輸入、處理和輸出」三個階段,那麼你又認為筆記扮演的任務角色會落到哪一個階段?做筆記只是「輸入」成效不高,想要「輸出」又耗時費力。要如何解決這個問題呢?卡片盒筆記法,就是目前最有效的解方,特別是在數位工具智能化之後,更顯得這套方法的強大。
Thumbnail
數位筆記術的改變力量 您有曾經煩惱過學習的效率嗎?或者是覺得難以捕捉那些稍縱即逝的靈感?在我們的日常生活和學習中,這些問題都是常見的挑戰。但幸運的是,有一種工具可以幫助我們改變這些情況,那就是數位筆記術。接下來,我將會講解三個使用數位筆記術的重要原則,這些原則能幫助我們更有效地學習和捕捉靈感。 原則
Thumbnail
《花漾年華》四連幅是我用當代水墨的抽象語彙,表現臺灣春天茂盛的新綠,充滿生命力,山櫻花盛開。傳統花鳥畫的創作哲學是「一花一世界,一葉一如來」,透過描繪大自然的生機呈現藝術家空靈的精神世界,心靈與自然合一,既超越自然又貼近自然。 在手工宣紙,揮灑充滿想像力的純淨世界,留白,紓解現代人過量的影音刺激。
Thumbnail
藝術家江心靜投入創作第二年,內在巨大無比的能量爆發,完成了 2015《藍色空間》三連幅當代水墨創作。 詩人的心找到了視覺語言,用深邃迷離的藍,與宇宙的能量結合,與早年創作的詩句呼應,內外合一的文人走向天命之路。
Thumbnail
雲海流動如一首神秘的歌,興起一股衝動,想透過創作表達心中感悟,第一個想法是強調「氣韻生動」的水墨,2012年採訪現代水墨之父劉國松,高齡八十的畫家精神奕奕,親自示範技法。 看著成品,在廚房燈下微笑,黑白構成的畫面,有一股力量,從山間飛到城市,推動著自己繼續,不在意繁華如夢,一步一步,往前走。
Thumbnail
兩年多前,開始有了重新架構網站的想法,所以把 Blogger 架設的網站,移轉至到了新的平台,當年我也做過了一些優缺點評析,剛好最近又再重新整理網站的所有架構,順便也重新啟動了一次 Hugo 架設網站的循環。
Thumbnail
因為要轉換一份新工作,認為自己在工作上必需更提高效率,剛好週圍有些朋友使用電子筆記,對於喜歡寫紙本筆記的人來說,是一個新的嘗試,原因有二:其一,筆記上有很多資料,在用完整本筆記之後,隨之石沉大海;其二,現在越來越多資訊是透過網路傳送,如果沒有好好收藏,資料就"放在"硬碟內了。 我是先去Youtube