Photo by Aron Visuals on Unsplash

Peter’s 100 task #3 10YearChallenge

利用date picker & slider控制時間,顯示從 flickr API 抓取的圖片

Tommy
10 min readNov 26, 2020

--

為紀念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

--

--