Peter’s 100 task #3 10YearChallenge
利用date picker & slider控制時間,顯示從 flickr API 抓取的圖片
為紀念Kobe做了一個簡單的回顧APP(2009~2019),圖片來源是flickr,用datepicker及slider選擇年份,並顯示圖片,用switch做自動播放功能。
成品
步驟說明
設定UI介面
建立各元件之物件變數並與程式連結(IBOutlet)
設定datepicker style&mode,設定slider 最大值&最小值及初始值
建立各元件之互動方法
同步slider、datepicker、yearLabel及switch自動播放功能
- slider
// 取得slider年份
let year = sender.value
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
dateComponents.year = Int(year)
datePicker.date = dateComponents.date!
// 同步label年份
yearLabel.text = String(Int(year))
// update image
let index = Int(year) - 2009
showImage(index)
使用DateComponents物件的year屬性存取當前選擇的年份,取得目前的日曆物件(用於計算時間),再使用DateComponents物件的date屬性,將目前設定的年份使用當前的日曆計算成日期,再將其存入datepicker。
- datepicker
let comp = sender.calendar.dateComponents([.year], from: sender.date)
dateSlider.value = Float(comp.year!)
yearLabel.text = String(comp.year!)
// update imageView
let index = comp.year! - 2009
showImage(index)
使用calendar.dateComponents將date拆成只有年份的DateComponents
- switch
用isOn屬性判斷開關,true時建立timer每隔1.5秒自動播放下一張圖片,並重複觸發timer,false時呼叫invalidate()將timer關閉
- timer
宣告一全域變數timer(若宣告在func內,則每次觸發switch便產生一個新的timer變數,將無法關閉timer)
timer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { block in
var currentYear = Int(self.yearLabel.text!)
var index = currentYear! - 2009
// 判斷年份若超過2019則重新回到2009
(index >= 10) ? (index = 0) : (index += 1)
print("index:", index)
print("Auto Play")
self.showImage(index)
// update slider & datepicker
currentYear = index + 2009
self.yearLabel.text = String(currentYear!)
self.dateSlider.setValue(Float(currentYear!), animated: true)
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
dateComponents.year = currentYear
self.datePicker.date = dateComponents.date!
print("update Date done")
用scheduledTimer(withTimeInterval:repeats:block:)產生一個Timer物件
withTimeInterval : 觸發計時器的間隔秒數
repeats: 是否重複觸發計時器
block: 計時器觸發時所要做的事
利用flickr API抓取圖片
希望從flickr抓取2009–2019年間的Kobe照片
宣告apiKey變數存放API Key,宣告一個變數為陣列內存放2009–1019,用flickr.photos.search產生客製化url(參數可參考API文件)
struct Photo: Decodable {
let farm: Int
let secret: String
let id: String
let server: String
let title: String
var imageUrl: URL {
return URL(string: "https://farm\(farm).staticflickr.com/\(server)/\(id)_\(secret)_m.jpg")!
}
}struct PhotoData: Decodable {
let photo: [Photo]
}struct SearchData: Decodable {
let photos: PhotoData
}
分析取得後的JSON資料,建立SearchData物件用於存取所需Data
if let url = URL(string: url) {
// 抓取Data
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// 解析JSON
if let data = data, let searchData = try? JSONDecoder().decode(SearchData.self, from: data) {
self.photos.remove(at: index)
self.photos.insert(contentsOf: searchData.photos.photo, at: index)
self.showImage(0)
}
}
// 啟動任務
task.resume()
}
接著產生URLSession物件呼叫task至url抓取資料,並在completionHandler內呼叫JSONDecoder物件將所抓回的JSON資料解析為我們所需要的資料類型(SearchData),將解析後的資料存到photos陣列內,由於抓取資料時程式在背景執行並不會依照順序,透過index將該年份所對應的Data放進photos陣列
## 最後必須呼叫task.resume()啟動task
程式載入時初始化及呼叫抓取圖片之 func
建立showImage func用於下載圖片並更新imageView
let photo = photos[index]
imageURL = photo.imageUrl
downloadImage(url: imageURL) { (image) in
if self.imageURL == photo.imageUrl, let image = image {
DispatchQueue.main.async {
self.photoImageView.image = image
}
}
}
呼叫downloadImage方法傳入重組後的url參數下載圖片
這次作業原本只是簡單的把照片傳上去做一個datepicker與slider的連動,但看到進階題有API的使用,就很想嘗試看看,第一次接觸API,碰了很多壁看了很多文章,所幸有學姊的程式可以參考抄襲哈哈,其中幾個問題像是URL資料抓取的function、closure的用法、執行緒的問題,都是未來還需要研究的部分,還有讀懂API文件也非常重要,flickr有中文翻譯真的輕鬆許多!
參考文章
-Github