[OS]Table Viewの整理(1)


こんにちは.今日の目標はiOSにおけるtableViewの実現モデルを全面的にまとめ、移行することです!
習ったくせに書くときは覚えてない
このシリーズは、次の順序で作成されます.ちょっと長いかな?

使用するデータ:NewsAPI


1.URLセッションを使用してTable Viewにデータを表示する

  • より効率的なコードの作成

  • AlamoFire、KingFisherの適用
  • この記事では,最も基本的なURLセッション通信とTable Viewの基本的な動作を実現する.

    1.NewsAPIを取得し、モデルを作成する


    まずこのサイトNewsApiに会員登録してapi鍵を受け取ってください
    それから書類を見て、私は最高のタイトルを書きます.
    リンクは「https://newsapi.org/v2/top-headlines?
    キーライン
    country = kr
    apiKey=発行済みapiキー
    使用します.
    PostManでテストします.

    私は2つのarticles配列しか使いません.そのうちの1つはtitleで、1つはurlToImageです.
    では、以下のようにモデルを作成できますか?
    import Foundation
    
    struct NewsList: Decodable {
        let articles: [Article]
    }
    struct Article: Decodable {
        let title: String
        let urlToImage: String?
    }

    2.通信api


    NetWorkという構造を作成し、ここで通信し、結果値を返します.
    エラー処理の列挙
    enum NetworkError: Error {
        case invalidURL
        case unknown
        case statusCodeError
        case noData
        case jsonError
    }
    result typeと@脱出閉パッケージを使用して、次の形式のdownLoadImage関数を作成します.
    static func downLoadImage(completion: @escaping (Result<[Article],NetworkError>) -> Void)
    まずconfiguration、session、URLコンポーネント、requestを作成します
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)
    
    guard var urlComponents = URLComponents(string: link) else {
        completion(.failure(NetworkError.invalidURL))
        return
    }
    
    let country = URLQueryItem(name: "country", value: "kr")
    let apiKey = URLQueryItem(name: "apiKey", value: "967a51965f9e4eec9e13b5ca4adf46f6")
    
    urlComponents.queryItems?.append(country)
    urlComponents.queryItems?.append(apiKey)
    
    guard let requestURL = urlComponents.url else {
        completion(.failure(NetworkError.invalidURL))
        return
    }
    その後taskを作成し、エラー処理を行い、do-catch文を使用して結果値を送信すればよいのではないでしょうか.
    let dataTask = session.dataTask(with: requestURL) { data, response, error in
        guard error == nil else {
            completion(.failure(NetworkError.unknown))
            return
        }
    
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode, (200..<300).contains(statusCode) else {
            completion(.failure(NetworkError.statusCodeError))
            return
        }
    
        guard let data = data else {
            completion(.failure(NetworkError.noData))
            return
        }
    
        do {
            let decoder = JSONDecoder()
            let result = try decoder.decode(NewsList.self, from: data).articles
            print(result)
            completion(.success(result))
        } catch {
            print(error)
            completion(.failure(NetworkError.jsonError))
            return
        }
    }
    dataTask.resume()
    最後に、リストにデータを保存し、tableViewを再ロードすると、すべてのデータが準備されます.
    NetWork.downLoadImage { [weak self] result in
        switch result {
        case .success(let data):
            self?.list = data
            DispatchQueue.main.async {
                self?.tableView.reloadData()
            }
        case .failure(let error):
            print(error)
        }
    }

    3 tableViewで表示


    Customcellの作成
    class NewsCell: UITableViewCell {
        @IBOutlet weak var contentImage: UIImageView!
        @IBOutlet weak var title: UILabel!
        
        override func awakeFromNib() {
            super.awakeFromNib()
            title.numberOfLines = 0
            title.font = .systemFont(ofSize: 20, weight: .bold)
            title.minimumScaleFactor = 0.5
        }
    }
    次に、CellForRow関数でデータをTable Viewに表示します.
    あ、それからまず!画像はurlからダウンロードされず、デフォルトの画像が使用されます.

    よく撮れているか確認できますよね?

    4.画像を表示


    まず、imageURLがあれば、そのURLから画像を受信することを実現します.
    Cellの内容なのでCellForRowで実現しましょう
    cellで次の関数を作成しました.
    バックグラウンドスレッドからurlの画像をダウンロードし、メインスレッドにuiを更新させます!
    func getImage(_ str: String?){
        guard str != nil, let url = URL(string: str!) else {
            contentImage.image = UIImage(systemName: "photo.circle.fill")
            return
        }
    
        DispatchQueue.global().async {
            var tempImg: UIImage?
            if let data = try? Data(contentsOf: url), let img = UIImage(data: data) {
                tempImg = img
            } else {
                tempImg = UIImage(systemName: "photo.circle.fill")
            }
    
            DispatchQueue.main.async {
                self.contentImage.image = tempImg
            }
        }
    }
    そしてcellForRowは対応する関数を呼び出します.
    cell.getImage(target.urlToImage)
    そして運行すれば良いのでは?

    ?????
    うん.ちょっと変な感じ
    まずレイアウトがめちゃくちゃなのは….うん.
    まずプレースホルダ画像を指定してgetImage関数を呼び出すべきだと思います.
    このようにしてこそ、画像とラベルのコンストレイントが正常に実行されます.
    私はまずこの問題を解決します.
    CellForRowはまずplaceHolderに画像を指定します.
    cell.getImage関数が非同期で呼び出されました
    cell.contentImage.image = UIImage(systemName: "photo.circle.fill")
    DispatchQueue.global().async {
        cell.getImage(target.urlToImage)
    }
    getImage関数も次のように変更されました.
    func getImage(_ str: String?){
        guard str != nil, let url = URL(string: str!) else {
            return
        }
        if let data = try? Data(contentsOf: url), let img = UIImage(data: data) {
            DispatchQueue.main.async {
                self.contentImage.image = img
            }
        }
    }
    またimageURLにはhttpの常にエラーメッセージが表示されます.
    このセクションのInfoが表示されます.App Transportセキュリティ設定をplistに追加します.
    サブキーを使用してAllow Arbitrary Loadsを追加し、値をYESに変更します.

    効果が確認できてとても良かったです!
    でも.これは本当に最高ですか?
    最高のレベルではないでしょうか.これでもいいですか.
    今のやり方の問題を考えてみましょう.
    あと文章が長すぎるので続きを書きましょう!
    完全なコードは、次のリンクgithub repoにあります.