首页 > 解决方案 > Swift - 显示每个 tableview 部分的特定数据

问题描述

我正在使用 CocktailDB。通过创建一个请求,我得到一个 JSON 文件,使用 Decodable 协议对其进行解析。从 JSON 我得到所有饮料的类别并将它们显示为我的 tableview 的部分。

在每个 tableview 部分中,我想显示特定类别的饮料(部分的标题)。类别中的每个部分单元格一杯饮料(饮料的 strDrink(名称)和 strDrinkThumb(图像))。

我有一个方法可以创建从特定类别获取饮料的请求 - getDrinksFrom(category: String)。
请建议我如何在特定部分调用此方法来获取和显示本部分中特定类别的饮料?

我的代码:

class ViewController: UIViewController {
    
    var drinks = [Drink]()
    var categories = [Category]()
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        getCategories()
        getDrinksFrom(category: "Cocoa")
    }
    
    func getCategories() {
        let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/list.php?c=list")
        
        URLSession.shared.dataTask(with: url!) { (data, response, error) in
            
            if error == nil {
                do {
                    self.categories = try JSONDecoder().decode(Categories.self, from: data!).drinks
                    
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                    print(self.categories)
                    
                } catch {
                    print(error)
                }
            }
        }.resume()
    }
    
    func getDrinksFrom(category: String) {
        let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=\(category)")
        
        URLSession.shared.dataTask(with: url!) { (data, response, error) in
            
            if error == nil {
                do {
                    self.drinks = try JSONDecoder().decode(Drinks.self, from: data!).drinks
                    
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                    print(self.drinks)
                    
                } catch {
                    print(error)
                }
            }
        }.resume()
    }
    
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return categories.count
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return categories[section].strCategory
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "drinkCell") as! DrinkCell
        
        cell.drinkName.text = drinks[indexPath.row].strDrink
        
        let url = drinks[indexPath.row].strDrinkThumb
        cell.drinkImage.downloaded(from: url)
        
        return cell
    }
}

// to download an image from web
extension UIImageView {
    func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
        contentMode = mode
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard
                let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
                let data = data, error == nil,
                let image = UIImage(data: data)
                else { return }
            DispatchQueue.main.async() { [weak self] in
                self?.image = image
            }
        }.resume()
    }
    
    func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
        guard let url = URL(string: link) else { return }
        downloaded(from: url, contentMode: mode)
    }
}

类别型号:

struct Categories:Decodable {
    var drinks: [Category]
}

struct Category:Decodable {
    var strCategory: String
}

饮品型号:

struct Drinks:Decodable {
    var drinks: [Drink]
}

struct Drink:Decodable {
    var strDrink: String
    var strDrinkThumb: String
}

我所知道的:

JSON结构: 在此处输入图像描述

标签: iosjsonswiftapiuitableview

解决方案


我的建议是为这些部分创建一个Category包含名称和饮品的自定义结构。不符合Decodable,这是有意的

struct Category {
    let name : String
    var drinks : [Drink]
}

和适当的数据源数组

var categories = [Category]()

然后使用传统加载和解析类别,JSONSerialization并通过映射名称来填充数组。进一步添加完成处理程序

func getCategories(completion: @escaping () -> Void) {
    let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/list.php?c=list")
    
    URLSession.shared.dataTask(with: url!) { (data, response, error) in
        
        if let error = error { print(error); return }
        do {
            let result = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
            let categoryNames = result["drinks"] as! [[String:String]]
            self.categories = categoryNames.map{ Category(name: $0["strCategory"]!, drinks:[])}
            completion()
            
        } catch {
            print(error)
        }
    }.resume()
}

为了避免命名混乱(太多饮料)命名根结构Response

struct Response : Decodable {
    let drinks: [Drink]
}

加载与类别相关的数据,并将饮料数组分配给相应的数组categories

func getDrinksFrom(category: String) {
    let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=\(category)")
    
    URLSession.shared.dataTask(with: url!) { (data, response, error) in
        
        if let error = error { print(error); return }
        do {
            let drinks = try JSONDecoder().decode(Response.self, from: data!).drinks
            guard let index = categories.firstIndex(where: {$0.name == category}) else { return }
            self.categories[index].drinks = drinks
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
            
        } catch {
            print(error)
        }
    }.resume()
}

并替换viewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()
    getCategories { [weak self] in
        self?.getDrinksFrom(category: "Cocoa")
    }
}

最后更改表视图数据源方法以匹配节结构

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return categories.count
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return categories[section].name
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return categories[section].drinks.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "drinkCell") as! DrinkCell
        
        let category = categories[indexPath.section]
        let drink = category.drinks[indexPath.row]
        cell.drinkName.text = drink.strDrink
        
        let url = drink.strDrinkThumb
        cell.drinkImage.downloaded(from: url)
        
        return cell
    }
}

您还可以将这两个功能放在一起并加载所有类别的所有饮料

func loadAllCategories() {
    let url = URL(string: "https://www.thecocktaildb.com/api/json/v1/1/list.php?c=list")
    
    URLSession.shared.dataTask(with: url!) { (data, response, error) in
        
        if let error = error { print(error); return }
        do {
            let result = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
            let categoryNames = (result["drinks"] as! [[String:String]]).map{$0["strCategory"]!}
            let group = DispatchGroup()
            for category in categoryNames {
                let categoryURLString = "https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=\(category)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
                let categoryURL = URL(string: categoryURLString)!
                group.enter()
                let categoryTask = URLSession.shared.dataTask(with: categoryURL) { (categoryData, _, categoryError) in
                    defer { group.leave() }
                    if let categoryError = categoryError { print(categoryError); return }
                    do {
                        let drinks = try JSONDecoder().decode(Response.self, from: categoryData!).drinks
                        self.categories.append(Category(name: category, drinks: drinks))
                    } catch {
                        print(error)
                    }
                }
                categoryTask.resume()
                
            }
            group.notify(queue: .main) {
                self.tableView.reloadData()
            }
            
        } catch {
            print(error)
        }
    }.resume()
}

推荐阅读