【问题标题】:Swift - display specific data for each tableview sectionSwift - 显示每个 tableview 部分的特定数据
【发布时间】:2020-11-04 15:07:45
【问题描述】:

我正在使用 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 结构:

【问题讨论】:

    标签: ios json swift api uitableview


    【解决方案1】:

    我的建议是创建一个自定义结构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()
    }
    

    为避免命名混乱(drinks 太多),将根结构命名为 Response

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

    加载与某个类别相关的数据,并将drinks数组分配给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()
    }
    

    【讨论】:

    • 哇!!非常感谢你!有用。您能否也给我一些建议:我有一个类别数组,我想在表格视图中仅显示来自该数组的饮料部分。我该怎么做?
    • 对于单个数组,您实际上不需要多个部分。
    • 这是一个类别数组 = 部分
    • 我不明白。除了最后一个代码 sn-p 我的解决方案仅加载给定部分的数据(但显示所有部分标题)。如果您不想看到所有部分标题,我重复一遍:对于单个数组,您实际上不需要多个部分
    【解决方案2】:

    这只是一个伪代码,它将让您了解如何进一步进行。代码未经测试。

    创建要加载的部分数组。

    var sections: [Sections] = []
    

    在您的 tableview 委托中,您可以为需要加载的部分创建一个结构,这将帮助您识别单元格中的部分以用于行索引路径,您可以在其中根据类别调用 API。

    extension ViewController: UITableViewDataSource, UITableViewDelegate {
        
        struct Sections {
            static var count = 0
            // In stantiate table view headers index order
            enum SectionType {
                case SoftDrink
                case OrdinaryDrink
                case MilkShake
            }
            
            var type: SectionType?
            var section: Int?
            var rows: Int?
        }
        
        func setUpTableView() {
            // Set Up Tableview Data
            if check if Drink is type of SoftDrink /*If you sections are loaded dynamic u can add condition*/ {
                sections.append(Sections(type: .SoftDrink, section: Sections.count, rows: 1))
                Sections.count += 1
            }
            Sections.count = 0
        }
    
        
        func numberOfSections(in _: UITableView) -> Int {
            sections.count
        }
        
        func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
            sections[section].rows ?? 0
        }
        
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            var tableCell: UITableViewCell = UITableViewCell()
            guard let type = sections[indexPath.section].type else {
                tableCell.selectionStyle = .none
                return tableCell
            }
            switch type {
            case .SoftDrink: break
            // Instantiate cell and API calls.
            case .OrdinaryDrink: break
            // Instantiate cell and API calls.
            case .MilkShake: break
                // Instantiate cell and API calls.
            }
            tableCell.selectionStyle = .none
            
            return tableCell
        }
        
    }
    

    setUpTableView()可以在viewDidLoad方法中调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-23
      • 2022-12-17
      • 1970-01-01
      • 2015-12-14
      • 2015-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多