iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
Mobile Development

我將點燃Swiftの大海系列 第 29

Day29. Swift一定要會の天氣API實作篇 (3)

  • 分享至 

  • xImage
  •  

AreaView

今天教大家內部的功能怎麼設計!

functions

首先我們要先處理好 Url 的編碼
接著需要讓我們的 LifeCycle 包進去能夠呼叫我們設定的 API
所以要設計一個 function 去填入
重點是我們會將 Url 分為兩個不同區塊去設計

  • baseURL 用來表示最基礎不會改動的 Url
  • cityParam 用來填入使用者選擇的地區
    // 處理URL編碼
    func LegitimateURL(requestURL: String) -> URL? {
        guard let legitimateURL = requestURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
              let url = URL(string: legitimateURL) else {
            print("無法創建有效的 URL")
            return nil
        }
        return url
    }
    
    // 呼叫天氣API
    func callAPI() {
        guard let selectedArea = selectedArea else {
            print("沒有選擇的地區")
            return
        }
        
        let baseURL = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001?Authorization = 這裡要填入你的授權碼"
        let cityParam = "&locationName=\(selectedArea)"
        let fullURLString = baseURL + cityParam
        
        guard let requestURL = LegitimateURL(requestURL: fullURLString) else {
            print("無法創建請求 URL")
            return
        }
        
        // 顯示載入指示器
        let activityIndicator = UIActivityIndicatorView(style: .medium)
        activityIndicator.center = view.center
        activityIndicator.hidesWhenStopped = true
        view.addSubview(activityIndicator)
        activityIndicator.startAnimating()
        
        // 執行API請求
        URLSession.shared.dataTask(with: requestURL) { [weak self] (data, response, error) in
            // 停止載入指示器
            DispatchQueue.main.async {
                activityIndicator.stopAnimating()
                activityIndicator.removeFromSuperview()
            }
            
            // 錯誤處理
            if let error = error {
                print("請求失敗: \(error.localizedDescription)")
                self?.showAlert(title: "請求失敗", message: "無法獲取天氣數據")
                return
            }
            
            // 檢查HTTP狀態碼
            if let httpResponse = response as? HTTPURLResponse {
                if !(200...299).contains(httpResponse.statusCode) {
                    print("伺服器回應錯誤: \(httpResponse.statusCode)")
                    self?.showAlert(title: "伺服器錯誤", message: "HTTP狀態碼: \(httpResponse.statusCode)")
                    return
                }
            }
            
            guard let data = data else {
                print("沒有接收到數據")
                self?.showAlert(title: "數據錯誤", message: "沒有接收到任何數據")
                return
            }
            
            // 解析JSON資料
            let decoder = JSONDecoder()
            do {
                let weatherData = try decoder.decode(weatherData.self, from: data)
                
                // 更新UI
                DispatchQueue.main.async {
                    self?.weatherData2 = weatherData
                    self?.TView.reloadData()
                }
            } catch {
                print("JSON 解析失敗: \(error.localizedDescription)")
                self?.showAlert(title: "數據解析錯誤", message: "無法解析天氣數據")
            }
        }.resume()
    }

extension

extension 部分就沒什麼要注意的了
相信我們已經教了好幾次的 tableView 設定大家都有肌肉記憶了吧!

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return weatherData2?.records.location.first?.weatherElement.first?.time.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "AreaTableViewCell", for: indexPath) as? AreaTableViewCell else {
            return UITableViewCell()
        }
        
        // 取得天氣資料
        let minT = weatherData2?.records.location[0].weatherElement[2].time[indexPath.row].parameter.parameterName ?? " "
        let maxT = weatherData2?.records.location[0].weatherElement[4].time[indexPath.row].parameter.parameterName ?? " "
        
        // 獲取並格式化時間
        let startTimeString = weatherData2?.records.location[0].weatherElement[0].time[indexPath.row].startTime ?? " "
        let formattedTime = formatTime(startTimeString)
        
        // 設定單元格內容
        cell.lbTime.text = formattedTime  // 使用格式化後的時間
        cell.lbWx.text = weatherData2?.records.location[0].weatherElement[0].time[indexPath.row].parameter.parameterName ?? " "
        cell.lbMinT.text = "\(maxT)°C " // 最高溫度
        cell.lbCl.text = weatherData2?.records.location[0].weatherElement[3].time[indexPath.row].parameter.parameterName ?? " " // 舒適度
        cell.lbMaxT.text = "\(minT)°C" // 最低溫度
        
        // 設定天氣圖示
        if let weatherCode = weatherData2?.records.location[0].weatherElement[0].time[indexPath.row].parameter.parameterValue {
            setWeatherIcon(cell: cell, weatherCode: weatherCode)
        }
        
        return cell
    }

extension 進階運用 - 隨著天氣變換 Icon

    // 設定天氣圖示
    private func setWeatherIcon(cell: AreaTableViewCell, weatherCode: String) {
        if let code = Int(weatherCode) {
            let imageName: String
            switch code {
            case 1:
                imageName = "sun.max" // 晴天
            case 2, 3:
                imageName = "cloud.sun" // 多雲
            case 4, 5, 6, 7:
                imageName = "cloud" // 陰天
            case 8, 9, 10, 11, 12, 13, 14:
                imageName = "cloud.rain" // 雨天
            case 15, 16, 17, 18:
                imageName = "cloud.bolt.rain" // 雷雨
            case 19, 20, 21, 22:
                imageName = "snowflake" // 雪
            default:
                imageName = "cloud"
            }
            
            if #available(iOS 13.0, *) {
                cell.imgCloud.image = UIImage(systemName: imageName)
            }
        }
    }

上一篇
Day28. Swift一定要會の天氣API實作篇 (2)
下一篇
Day30. 完賽感言和總結
系列文
我將點燃Swiftの大海30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言