Swift 4 iOS:如何解析图片链接数组JSON至嵌套集合视图
Hey there! I’ve worked through exactly this kind of nested collection view setup before, so let’s walk through how to get this working smoothly—from updating your data model to wiring up all the views.
1. Update Your Data Model to Handle Image Arrays
First, adjust your Codable model to accept an array of image URLs instead of a single string. This matches the JSON structure you’re now working with:
struct Post: Codable { // Add your other existing properties (title, description, etc.) let imageUrls: [String] // Maps to the ["link1", "link2", ...] array in your JSON }
Make sure the property name (imageUrls here) exactly matches the key in your JSON (e.g., if your JSON uses "images" instead, rename the property to images).
2. Set Up the Nested View Structure
Your core setup is UITableView → UITableViewCell (with embedded UICollectionView) → UICollectionViewCell (with UIImageView). Here’s how to build each part:
Step 2.1: Build the Image Collection View Cell
This is the small cell that holds a single image:
class ImageCollectionCell: UICollectionViewCell { // Reusable image view for displaying the image let imageView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFill iv.clipsToBounds = true iv.layer.cornerRadius = 8 // Optional: adds rounded corners iv.translatesAutoresizingMaskIntoConstraints = false return iv }() override init(frame: CGRect) { super.init(frame: frame) setupViews() } required init?(coder: NSCoder) { super.init(coder: coder) setupViews() } private func setupViews() { contentView.addSubview(imageView) // Pin image view to all edges of the collection cell NSLayoutConstraint.activate([ imageView.topAnchor.constraint(equalTo: contentView.topAnchor), imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } }
Step 2.2: Build the Table View Cell with Embedded Collection View
This cell will hold your collection view and any other content (like titles, descriptions):
class PostTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { // Array to hold image URLs for the collection view var imageUrls: [String] = [] { didSet { collectionView.reloadData() // Refresh collection when data updates } } // Your other UI elements (e.g., title label) let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 16, weight: .semibold) label.translatesAutoresizingMaskIntoConstraints = false return label }() // Embedded collection view for images lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal // Scroll left/right layout.itemSize = CGSize(width: 100, height: 100) // Adjust size as needed layout.minimumLineSpacing = 8 // Space between images layout.minimumInteritemSpacing = 8 let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.translatesAutoresizingMaskIntoConstraints = false cv.dataSource = self cv.delegate = self cv.register(ImageCollectionCell.self, forCellWithReuseIdentifier: "ImageCollectionCell") cv.showsHorizontalScrollIndicator = false // Optional: hide scroll bar return cv }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupCellLayout() } required init?(coder: NSCoder) { super.init(coder: coder) setupCellLayout() } private func setupCellLayout() { // Add title label and collection view to the cell's content view contentView.addSubview(titleLabel) contentView.addSubview(collectionView) // Set up constraints NSLayoutConstraint.activate([ // Title label constraints titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), // Collection view constraints collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12), collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), collectionView.heightAnchor.constraint(equalToConstant: 116), // Match item size + spacing collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16) ]) } // MARK: - Collection View Data Source func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // Show up to 4 images (adjust this number to your needs) return min(imageUrls.count, 4) } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionCell", for: indexPath) as! ImageCollectionCell let imageUrlString = imageUrls[indexPath.item] // Load image using a library like Kingfisher or SDWebImage (recommended for caching) if let url = URL(string: imageUrlString) { cell.imageView.kf.setImage(with: url) // Example with Kingfisher // If you don't use a library: write your own URLSession code to fetch the image } return cell } // Optional: Customize item size if needed func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: 100, height: 100) } }
3. Wire Up the Table View Data Source
In your view controller that manages the table view, pass the image array to each table cell:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { var posts: [Post] = [] // Your array of data models let tableView = UITableView() override func viewDidLoad() { super.viewDidLoad() setupTableView() fetchData() // Call your JSON parsing method here } private func setupTableView() { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) tableView.dataSource = self tableView.delegate = self tableView.register(PostTableViewCell.self, forCellReuseIdentifier: "PostTableViewCell") tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 200 // Helps with auto-sizing cells NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) } // MARK: - Table View Data Source func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return posts.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withReuseIdentifier: "PostTableViewCell", for: indexPath) as! PostTableViewCell let post = posts[indexPath.row] // Populate other cell content cell.titleLabel.text = post.title // Pass the image array to the table cell cell.imageUrls = post.imageUrls return cell } // Your JSON parsing method example private func fetchData() { // Replace with your actual JSON fetch/parsing code guard let url = URL(string: "your-api-url-here") else { return } URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil else { return } do { self?.posts = try JSONDecoder().decode([Post].self, from: data) DispatchQueue.main.async { self?.tableView.reloadData() } } catch { print("JSON parsing error: \(error)") } }.resume() } }
Key Notes to Remember
- Image Loading: Always use a third-party library like Kingfisher or SDWebImage for image loading—they handle caching, placeholder images, and error handling out of the box, which saves you a ton of code.
- Cell Reuse: The
didSetproperty observer onimageUrlsensures the collection view refreshes when the cell is reused with new data. - Auto-Layout: Setting
tableView.rowHeight = .automaticDimensionandestimatedRowHeightlets the table cell resize based on its content, which is crucial for nested views.
内容的提问来源于stack exchange,提问作者George Heints

