網路基礎-藝廊篇
-
開發一個App軟體,基本上需要兩個要素,一是程式碼,二是資料內容。
在過去寫程式最困難的地方,不是語法或演算法,而在於缺乏資料,大部分資料都是由企業、政府或機構所掌握,每筆資料都有產出成本,不會輕易與人分享,所以個人很難寫出有內容的軟體,就像俗話說「巧婦難為無米之炊」。現在拜 Open API 所賜,每個人從網路上都能免費取得豐富資料,這對程式設計者來說,是非常幸福的事。
在上一單元,我們用 Open API 連接iTune 音樂資料庫,在本單元則要練習連接「芝加哥藝術博物館」館藏資料庫。
芝加哥藝術博物館(Art Institute of Chicago)成立於1879年,是美國僅次於紐約大都會博物館的藝術殿堂,與著名的芝加哥藝術學院(SAIC, School of the Art Institute of Chicago)是聯屬機構。
博物館收藏超過30萬件藝術作品,著名館藏包括19世紀英國畫家William Turner與John Constable的風景畫,雷諾瓦、莫內等法國印象派畫家,還有秀拉、梵谷、塞尚、畢加索等著名歐洲畫家作品,當然也包括20世紀的美國現代藝術與其他各國藝術品,藏品非常豐富。
如果問說:「Apple iTune 的 5千萬首音樂,與芝加哥藝術博物館的30萬件作品,哪一個比較有價值」?答案肯定是芝加哥藝術博物館,因為每一件收藏都是獨一無二的真跡原件。
有了整個博物館的完整資料,我們就能夠寫出豐富內涵的App軟體,如果有雄心壯志,更可以寫一個涵蓋全世界提供Open API的博物館,說不定會成為最完整的藝術作品App。
和上一單元比較起來,視覺藝術作品與音樂曲目的呈現方式應該有所不同,不過最基本的,我們先從「搜尋」功能開始,將搜尋結果列表出來,以確認Open API連接與JSON解碼正常。
先用上一單元 3-3b iTune 搜尋的範例程式為藍本,改為搜尋芝加哥藝術博物館的館藏資料,需要修改的地方只有兩處:
1. Open API 網址,改成類似這樣: https://api.artic.edu/api/v1/artworks/search?q=van+gogh&limit=100
2. 根據回傳的JSON結構,修改 struct 型態宣告注意上述網址的搜尋參數(query)為 "q=van+gogh&limit=100",包含兩個參數,q=van+gogh 是搜尋字串 "van gogh" 即梵谷,limit=100 是限制最多傳回100筆資料。
若直接用瀏覽器連結上述 Open API 網址,從回傳結果就可以看出 JSON 結構,下圖是透過 Chrome瀏覽器的外掛程式 JSONView 顯示的畫面:
3-4a Open API: 芝加哥藝術博物館(https://api.artic.edu/docs/)
操作步驟如下:
- 選取畫面左方的「+」號新增電子書頁面。
- 將新增的電子書面頁命名為「(20)網路程式基礎-4藝廊篇」。
- 在「Main」模組中撰寫程式:
// 3-4a Open API: 芝加哥藝術博物館(https://api.artic.edu/docs/) // Created by Philip, Heman, Jean 2021/10/02 // Revised by Jean 2024/12/08 import PlaygroundSupport import SwiftUI struct 搜尋結果: Codable { let pagination: 分頁資訊 let data: [搜尋品項] } struct 分頁資訊: Codable { let total: Int let limit: Int let offset: Int let total_pages: Int let current_page: Int } struct 搜尋品項: Codable, Identifiable { let api_link: URL? let id: Int let title: String } struct 更新頁面: View { @State var inputText = "" @State var 搜尋字串 = "van gogh" @State var 作品列表: [搜尋品項]? func 更新作品列表() { // let 網址 = "https://api.artic.edu/api/v1/artworks/search?q=von+gogh&limit=100" // guard let myURL = URL(string: 網址) else { return } var myURLComponent = URLComponents() myURLComponent.scheme = "https" myURLComponent.host = "api.artic.edu" myURLComponent.path = "/api/v1/artworks/search" myURLComponent.query = "q=\(搜尋字串)&limit=100" guard let myURL = myURLComponent.url else { return } URLSession.shared.dataTask(with: myURL) { 回傳資料, 回傳碼 , 錯誤碼 in if let 解碼資料 = 回傳資料 { do { let 解碼結果 = try JSONDecoder().decode(搜尋結果.self, from: 解碼資料) print(回傳碼 ?? "No response") 作品列表 = 解碼結果.data } catch { print("JSON解碼錯誤") } } else { print(錯誤碼 ?? "No error") } }.resume() } var body: some View { VStack { ZStack { Rectangle() .foregroundColor(.orange) .frame(height: 60) HStack { Image(systemName: "magnifyingglass") .font(.title) TextField("Search for artworks", text: $inputText) { 搜尋字串 = inputText 作品列表 = nil } .font(.title) .foregroundColor(.black) .background(Color.white) } .padding() } if 作品列表 == nil { ProgressView() .onAppear { 更新作品列表() } } else { List(作品列表!) { 作品 in Label(作品.title, systemImage: "rectangle.portrait") .font(.title) .lineLimit(1) } } } } } PlaygroundPage.current.setLiveView(更新頁面())
- 程式執行結果,如下圖。
3-4b 搜尋並顯示作品圖片(芝加哥藝術博物館)- 在「Main」模組中撰寫程式:
// 3-4b 搜尋並顯示作品圖片(芝加哥藝術博物館) // Created by Philip, Heman, Jean 2021/10/05 // Revised by Jean 2024/12/08 import PlaygroundSupport import SwiftUI // Refer to https://api.artic.edu/api/v1/artworks/search?q=van+gogh&limit=100 struct 搜尋結果: Codable { let pagination: 分頁資訊 let data: [搜尋品項] } struct 分頁資訊: Codable { let total: Int let limit: Int let offset: Int let total_pages: Int let current_page: Int } struct 搜尋品項: Codable, Identifiable { let api_link: URL? let id: Int let title: String } // Refer to https://api.artic.edu/api/v1/artworks/28560 struct 藝術作品: Codable { let data: 作品資訊 let config: 配置資訊 } struct 作品資訊: Codable, Identifiable { let id: Int let api_link: URL? let title: String let date_display: String let artist_display: String let artist_id: Int let artist_title: String let image_id: String } struct 配置資訊: Codable { let iiif_url: URL? let website_url: URL? } // 顯示「作品圖」陣列 struct 作品圖: Identifiable { var id: Int = 0 var 作品名稱: String = "" var 作者: String = "" var 圖檔網址: URL? } struct 抓圖: View { var myURL: URL @State var 下載圖片: UIImage? init(_ p: URL) {myURL = p} func 下載() { URLSession.shared.dataTask(with: myURL) { 回傳資料, 回應碼, 錯誤碼 in if let 圖檔 = UIImage(data: 回傳資料!) { print(回應碼 ?? "No response") 下載圖片 = 圖檔 } else { print(錯誤碼 ?? "No error") } }.resume() } var body: some View { if 下載圖片 == nil { ProgressView() .onAppear { 下載() } } else { Image(uiImage: 下載圖片!) .resizable() .scaledToFit() } } } struct 芝加哥藝術博物館: View { @State var inputText = "van gogh" @State var 搜尋字串 = "van gogh" @State var 作品列表: [搜尋品項]? @State var 作品圖集: [作品圖] = [] // let 網址 = "https://api.artic.edu/api/v1/artworks/search?q=van+gogh&limit=100" // guard let myURL = URL(string: 網址) else { return } func 更新作品列表(_ 字串參數: String) { var myURLComponent = URLComponents() myURLComponent.scheme = "https" myURLComponent.host = "api.artic.edu" myURLComponent.path = "/api/v1/artworks/search" myURLComponent.query = "q=\(字串參數)&limit=5" guard let myURL = myURLComponent.url else { return } URLSession.shared.dataTask(with: myURL) { 回傳資料, 回傳碼 , 錯誤碼 in if let 解碼資料 = 回傳資料 { do { let 解碼結果 = try JSONDecoder().decode(搜尋結果.self, from: 解碼資料) print(回傳碼 ?? "No response") 作品列表 = 解碼結果.data for 作品 in 作品列表! { 取得作品資訊(作品.api_link!) } } catch { print("JSON解碼錯誤") } } else { print(錯誤碼 ?? "No error") } }.resume() } // let 網址 = "https://api.artic.edu/api/v1/artworks/28560" // guard let myURL = URL(string: 網址) else { return } // 圖檔網址 IIIF v2 standard format: // https://www.artic.edu/iiif/2/25c31d8d-21a4-9ea1-1d73-6a2eca4dda7e/full/843,/0/default.jpg func 取得作品資訊(_ myURL: URL) { var 單項作品: 作品資訊? var 目前作品 = 作品圖() var myURLComponent = URLComponents() URLSession.shared.dataTask(with: myURL) { 回傳資料, 回傳碼 , 錯誤碼 in if let 待解碼資料 = 回傳資料 { do { let 尺寸 = "600," let 解碼結果 = try JSONDecoder().decode(藝術作品.self, from: 待解碼資料) print(回傳碼 ?? "No response") 單項作品 = 解碼結果.data 目前作品.id = 單項作品!.id 目前作品.作品名稱 = 單項作品!.title 目前作品.作者 = 單項作品!.artist_display myURLComponent.scheme = "https" myURLComponent.host = "www.artic.edu" myURLComponent.path = "/iiif/2/\(單項作品!.image_id)/full/\(尺寸)/0/default.jpg" myURLComponent.query = "" 目前作品.圖檔網址 = myURLComponent.url 作品圖集 = 作品圖集 + [目前作品] } catch { print("JSON解碼錯誤") } } else { print(錯誤碼 ?? "No error") } }.resume() } var body: some View { VStack(spacing: 0) { Text("Art Institute of Chicago") .font(.title) .bold() ZStack { Rectangle() .foregroundColor(.orange) .frame(height: 50) HStack { Image(systemName: "magnifyingglass") .font(.title2) TextField("Search for artworks", text: $inputText) { 搜尋字串 = inputText 作品列表 = nil 作品圖集 = [] } .font(.title2) .foregroundColor(.black) .background(Color.white) } .padding() } if 作品列表 == nil { ProgressView() .onAppear { 更新作品列表(搜尋字串) } } else { List(作品圖集) { 作品 in VStack { 抓圖(作品.圖檔網址!) HStack { Text(作品.作品名稱) .font(.title2) .bold() Spacer() Text(作品.作者) .font(.title3) } } } } } } } PlaygroundPage.current.setLiveView(芝加哥藝術博物館())
- 程式執行結果,如下圖。