问题
So I've been told that my data model for separating my cells into sections is bad.
I have 3 separate collections in my Tableview cartItems, groupedItems, and brandTitle in the CartVC. and I've been told this:
"You need to start over with a single collection representing nothing but sections data (where each piece of section data will hold the corresponding row data), so you can mutate the model without going insane"
and
"...recommended to avoid multiple Arrays for the datasource of a table view and using Dictionary to feed the table view is not a good idea. You should create a Model type."
I just don't know really how to do so, since the collections I've been using to separate the cells into sections took me weeks to generate and figure out, so that I could successfully populate the cells in their appropriate sections in the CartVC
import UIKit
class CartViewController: UIViewController {
var selectedProduct: ItemList! // allows data to be passed into the CartVC
// allows data to be sepearted into sections
var cartItems: [CartItem] = []
var groupedItems: [String: [CartItem]] = [:]
var brandTitle: [String] = []
@IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
groupedItems = Dictionary(grouping: cartItems, by: {$0.itemList.brandName})
brandTitle = groupedItems.map{$0.key}.sorted()
}
}
extension CartViewController: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return brandTitle.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let brand = brandTitle[section]
return groupedItems[brand]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartCell = tableView.dequeueReusableCell(withIdentifier: "CartCell") as! CartCell
let brand = brandTitle[indexPath.section]
let itemsToDisplay = groupedItems[brand]![indexPath.row]
cartCell.configure(withCartItems: itemsToDisplay.productList)
return cartCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeader") as! CartHeader
let headerTitle = brandTitle[section]
cartHeader.brandName.text = "Brand: \(headerTitle)"
return cartHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cartFooter = tableView.dequeueReusableCell(withIdentifier: "CartFooter") as! CartFooter
let brand = brandTitle[section]
let subtotal = groupedItems[brand]?.map { $0.getCartTotal() }.reduce(0, +) ?? 0
cartFooter.cartTotal.text = String(subtotal)
return cartFooter
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 150
}
import Foundation
class CartItem {
var itemList: ItemList!
init(itemList: ItemList) {
self.itemList = itemList
}
func getCartTotal() -> Float {
var subtotal: Float = 0
if itemList.selectedOption == 1 {
subtotal = subtotal + (Float(itemList.price1) * Float(itemList.count))
} else if itemList.selectedOption == 2 {
subtotal = subtotal + (Float(itemList.price2) * Float(itemList.count))
} else if itemList.selectedOption == 3 {
subtotal = subtotal + (Float(itemList.price3) * Float(itemList.count))
} else {
fatalError("The impossible happend")
}
return subtotal
}
}
class ItemList {
var id: String
var name: String
var brand: String
var price1: Float
var price2: Float
var price3: Float
var weight1: String
var weight2: String
var weight3: String
var imageUrl: String
var selectedOption: Int
var count: Int
init(id: String,
name: String,
brand: String,
price1: Float,
price2: Float,
price3: Float,
weight1: String,
weight2: String,
weight3: String,
imageUrl: String,
selectedOption: Int,
count: Int) {
self.id = id
self.name = name
self.brand = brand
self.price1 = price1
self.price2 = price2
self.price3 = price3
self.weight1 = weight1
self.weight2 = weight2
self.weight3 = weight3
self.imageUrl = imageUrl
self.selectedOption = selectedOption
self.count = count
}
convenience init(dictionary: [String : Any]) {
let id = dictionary["id"] as? String ?? ""
let name = dictionary["name"] as? String ?? ""
let brand = dictionary["brand"] as? String ?? ""
let price1 = dictionary["price1"] as? Float ?? 0.0
let price2 = dictionary["price2"] as? Float ?? 0.0
let price3 = dictionary["price3"] as? Float ?? 0.0
let weight1 = dictionary["weight1"] as? String ?? ""
let weight2 = dictionary["weight2"] as? String ?? ""
let weight3 = dictionary["weight3"] as? String ?? ""
let imageUrl = dictionary["imageUrl"] as? String ?? ""
let selectedOption = dictionary["selectedOption"] as? Int ?? 00
let count= dictionary["count"] as? Int ?? 00
self.init(id: id,
name: name,
brand: brand,
price1: price1,
price2: price2,
price3: price3,
weight1: weight1,
weight2: weight2,
weight3: weight3,
imageUrl: imageUrl,
selectedOption: selectedOption,
count: count)
}
}
回答1:
Imagine you have just one section of all your cart items. What would you have as your model?
- A title
- An array of cart items
Right?
Okay if the above makes sense, you then would need multiple of that said model type. Right again? Now break that into more specific terms. Like actual model type. It's always good and recommended to start with value type for the models unless you really need the reference semantics.
struct CartItem {
// intentionally renamed the ItemList to CartItem and stripped out most of the properties
let name: String
let brand: String
}
struct Section {
let title: String
let cartItems: [CartItem]
}
I'm not going to initialize the array of Section
s by myself, it's more of an implementation detail. But if you have to initialize that from external types (such as JSON) you may need the help of map(:), compactMap(:) or reduce(into:_:). Basically you will need the idea of Transforming a Collection
in general. Or maybe Codable too.
So how do you use the above Section
type with your UITableView
? Look closely:
class CartViewController: UIViewController {
. . .
// left out for the reader to initialize on his own
let sections = [Section]()
. . .
. . .
}
extension CartViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
. . .
let cartItem = sections[indexPath.section].cartItems[indexPath.row]
. . .
}
}
Now every other places in your CartViewController
you will access the sections
array and you will always be pointing to the right object.
Diving deep into the model manipulation. You can have different helper functions in your model type itself for different representations. Like:
struct Section {
let title: String
let cartItems: [CartItem]
static func groupedSectionsByBrand(from cartItems: [CartItem]) -> [Section] {
let grouped = Dictionary(grouping: cartItems) { $0.brand }
let sections = grouped.map { Section(title: $0.key, cartItems: $0.value) }
return sections
}
}
Then you will be able to generate your desired array of Section
by passing only the array of CartItem
like:
let cartItems = [CartItems]() // have these values from elsewhere
let sections = Section.groupedSectionsByBrand(from: cartItems)
来源:https://stackoverflow.com/questions/59558904/how-to-fix-the-separation-collections-of-cells-in-my-sections-in-my-tableview