#25–6 期末< 6>好味小姐罐頭訂購APP-TableView 的Cell裡裝一個Table View

Ethan
彼得潘的 Swift iOS / Flutter App 開發教室
10 min readJul 11, 2024

附上完成圖:

我希望在同一個類別裡面的商品可以出現在一起,要做到這程度就要考慮到的技術有:

  1. 自動調整cell的高度 (因為你要在同一類別裡新增一個新的產品)
  2. cell裡面的值要變化,使用delegate來做變化
  3. TableView + TableView製作。
    或是 TableView + CollectionView製作。
    或是 StackView製作

在這裡我使用TableView + TableView製作,高度調整及cell值變化都是使用delegate。
其實還是有些bug要處理,還不夠完美,但還是把現狀發上來吧~

資料來源:

使用單例模式來確保應用程序中只有一個實例,並提供全局訪問點來共享數據~

class SharedData {
// 靜態常量,用於存儲 SharedData 的唯一實例
static let shared = SharedData()

// 存儲 TitleOrderData 的數組
var titleOrders: [TitleOrderData] = []

// 存儲 DetailOrderData 的數組
var detailOrders: [DetailOrderData] = []

// 存儲 CategoryName 的數組
var categoryTitles: [CategoryName] = []

// 用於緩存數據的字典
var cache: [String: [Record]] = [:]

// 私有的初始化方法,防止外部創建新的實例
private init() {}
}

struct TitleOrderData {
var title: String? // 訂單標題
var titleImageUrl: URL? // 標題圖片的 URL
var detailOrderData: [DetailOrderData] // 包含的詳細訂單數據
}

struct DetailOrderData {
var name: String? // 訂單項目的名稱
var detailPrice: String? // 詳細價格
var price: String? // 價格
var orderQty: String? // 訂單數量
var imageUrl: URL? // 項目圖片的 URL
var sectionIndex: Int? // 所在的 section 索引
var rowIndex: Int? // 所在的 row 索引
}

TableView + TableView:

他麻煩點有兩個:

第一個是因為你有兩個TableView,Xcode不知道要顯示哪一個,沒有設定內容還是不會顯示出來~

第二個是numberOfSections & numberOfRowsInSection
究竟這兩的數量要怎麼確認。

關於第一個,因為有兩個TableView,我們可以把外面的TableView拉一個Outlet來做辨別。

關於資料:

numberOfSections

func numberOfSections(in tableView: UITableView) -> Int {
if tableView == mainTableView {
// 外面的TableView 的 section 數量來自 titleOrders
return SharedData.shared.titleOrders.count
} else {
// 裡面的TableView 只有一個 section
return 1
}
}

numberOfRowsInSection

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == mainTableView {
// 主 TableView 每個 section 只有一個 row,因為每個 cell 會包含一個子 TableView
return 1
} else {
// 子 TableView 的 row 數量來自 detailOrderData
let sectionIndex = tableView.tag
guard sectionIndex < SharedData.shared.titleOrders.count else {
print("無效的 section 索引: \(sectionIndex)")
return 0
}
return SharedData.shared.titleOrders[sectionIndex].detailOrderData.count
}
}

cellForRowAt

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == mainTableView {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "OrderOfTitleViewTableViewCell", for: indexPath) as? OrderOfTitleViewTableViewCell else {
return UITableViewCell()
}
let titleOrder = SharedData.shared.titleOrders[indexPath.section]
if let imageUrl = titleOrder.titleImageUrl {
cell.titleImage.kf.setImage(with: imageUrl)
}
cell.titleLabel.text = titleOrder.title
cell.changeHeightDelegate = self
cell.subTableView.tag = indexPath.section
cell.subTableView.reloadData()
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "OrderOfSubViewTableViewCell", for: indexPath) as? OrderOfSubViewTableViewCell else {
return UITableViewCell()
}

// 確保 tableView.tag 有效
guard tableView.tag < SharedData.shared.titleOrders.count else {
print("無效的 tableView.tag: \(tableView.tag)")
return UITableViewCell()
}

let detailOrderData = SharedData.shared.titleOrders[tableView.tag].detailOrderData

// 確保 indexPath.row 有效
guard indexPath.row < detailOrderData.count else {
print("Section of \(tableView.tag) 的無效行索引: \(indexPath.row)")
return UITableViewCell()
}
let detailOrder = detailOrderData[indexPath.row]
if let imageUrl = detailOrder.imageUrl {
cell.productImageView.kf.setImage(with: imageUrl)
}
cell.nameLabel.text = detailOrder.name
cell.packageLabel.text = detailOrder.detailPrice
cell.priceLabel.text = "\(detailOrder.price ?? "")"
cell.countLabel.text = detailOrder.orderQty
cell.detailData = DetailOrderData(
name: detailOrder.name,
detailPrice: detailOrder.detailPrice,
price: detailOrder.price,
orderQty: detailOrder.orderQty,
imageUrl: detailOrder.imageUrl,
sectionIndex: tableView.tag,
rowIndex: indexPath.row
)
cell.changeHeightDelegate = self
cell.alertDelegate = self // 設置 alertDelegate
cell.changeQtyDelegate = self
return cell
}
}

willDisplay

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
tableView.invalidateIntrinsicContentSize()
tableView.layoutIfNeeded()
updateTotalPrice()
}

關於自動調整高度:
因為兩個TableView都要自動調高,所以記得要在這兩個TableView加上
var changeHeightDelegate: ChangeHeightDelegate?
並且在override func awakeFromNib() {} 加上.layoutIfNeeded()

protocol ChangeHeightDelegate {
func heightChanged (index: Int)
}


extension OrderViewController: ChangeHeightDelegate {
func heightChanged (index: Int) {
mainTableView.performBatchUpdates(nil)
}
}

extension UITableView {
public override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return contentSize
}

public override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
}

最後記得在

cellForRowAt裡面加上cell.changeHeightDelegate = self

func viewWillAppear()、 viewDidAppear()、
tableview func的willDisplay裡面
加上tableView.layoutIfNeeded()

大概就這樣吧~☕☕☕

--

--