Swift 4.0中基于TableViewCell值的动态CollectionView实现问题
Hey there! Let's walk through how to build this nested TableView + CollectionView setup for your showtimes data. I'll break it down step by step so it's easy to follow, even if you're new to Swift.
First, we'll create a struct to represent each showtime entry from your JSON. Using Codable makes parsing the JSON super straightforward, and we'll map the uppercase JSON keys to Swift-style lowercase properties:
struct Showtime: Codable { let screentime: String let format: String // Map uppercase JSON keys to our lowercase properties enum CodingKeys: String, CodingKey { case screentime = "SCREENTIME" case format = "FORMAT" } }
Next, we need to group the raw showtimes array by the FORMAT value. This lets us create one TableView row per format, with all matching screentimes in its embedded CollectionView.
In your ViewController, declare a grouped data source property:
// Stores (format name, array of screentimes) pairs var showtimeGroups: [(format: String, times: [String])] = []
Then parse your JSON and group the data in viewDidLoad():
override func viewDidLoad() { super.viewDidLoad() // Your raw JSON string (replace with your actual data source) let jsonString = """ [{"SCREENTIME": "15:00", "FORMAT": "2D"}, {"SCREENTIME": "17:00", "FORMAT": "2D"}, {"SCREENTIME": "21:00", "FORMAT": "2D"}, {"SCREENTIME": "12:00", "FORMAT": "3D"}, {"SCREENTIME": "14:00", "FORMAT": "3D"}] """ guard let jsonData = jsonString.data(using: .utf8) else { print("Failed to convert JSON string to data") return } do { // Parse JSON into an array of Showtime objects let rawShowtimes = try JSONDecoder().decode([Showtime].self, from: jsonData) // Group showtimes by their format let groupedDictionary = Dictionary(grouping: rawShowtimes) { $0.format } // Convert the dictionary into a sorted array for consistent TableView ordering showtimeGroups = groupedDictionary .map { (format: $0.key, times: $0.value.map { $0.screentime }) } .sorted { $0.format < $1.format } // Sort alphabetically (2D comes before 3D) tableView.reloadData() } catch { print("JSON parsing error: \(error.localizedDescription)") } // Register your custom TableViewCell tableView.register(ShowtimeTableViewCell.self, forCellReuseIdentifier: "ShowtimeCell") tableView.dataSource = self }
We'll create a custom TableViewCell that contains a horizontal CollectionView to display screentimes. First, the CollectionView cell:
class TimeCollectionViewCell: UICollectionViewCell { let timeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textAlignment = .center label.font = .systemFont(ofSize: 14) return label }() override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupView() { contentView.addSubview(timeLabel) NSLayoutConstraint.activate([ timeLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), timeLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) ]) // Add some styling for better visibility contentView.layer.cornerRadius = 8 contentView.layer.borderWidth = 1 contentView.layer.borderColor = UIColor.systemGray.cgColor } }
Now the TableViewCell that holds the CollectionView:
class ShowtimeTableViewCell: UITableViewCell { let collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: 80, height: 40) layout.minimumInteritemSpacing = 10 layout.minimumLineSpacing = 10 let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.translatesAutoresizingMaskIntoConstraints = false cv.backgroundColor = .white return cv }() // Data source for the CollectionView var screentimes: [String] = [] { didSet { collectionView.reloadData() } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupCell() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupCell() { contentView.addSubview(collectionView) // Pin the CollectionView to the cell's content with padding NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), collectionView.heightAnchor.constraint(equalToConstant: 56) ]) // Register the CollectionView cell and set delegates collectionView.register(TimeCollectionViewCell.self, forCellWithReuseIdentifier: "TimeCell") collectionView.dataSource = self collectionView.delegate = self } } // MARK: - CollectionView DataSource & Delegate extension ShowtimeTableViewCell: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return screentimes.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TimeCell", for: indexPath) as? TimeCollectionViewCell else { return UICollectionViewCell() } cell.timeLabel.text = screentimes[indexPath.item] return cell } }
Finally, implement the TableView data source methods to populate each row with the correct format and screentimes:
extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return showtimeGroups.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "ShowtimeCell", for: indexPath) as? ShowtimeTableViewCell else { return UITableViewCell() } let group = showtimeGroups[indexPath.row] cell.textLabel?.text = group.format // Show the format in the cell's title cell.screentimes = group.times // Pass screentimes to the embedded CollectionView return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 72 // Match the CollectionView height + padding } }
That's it! This setup will create a TableView where each row shows a format (2D/3D), and a horizontal scrollable CollectionView inside each row displays all matching screentimes. You can tweak the styling (colors, sizes, spacing) to fit your app's design.
内容的提问来源于stack exchange,提问作者vanillatte

