Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Horizontal ScrollViews In One Vertical ScrollView in Swift?

Tags:

ios

swift

I'm trying to achieve one of the most standard layouts in the apps in Swift.

which is basically having Multiple Horizontal ScrollViews In One Vertical ScrollView.

Each of these sub-Horizontal ScrollViews Will hold a few views with images.

Something like this:

enter image description here

what is the best way of achieving this?

P.S. I need to do this using code as the content is pulled via a remote JSON file.

any pointers would be appreciated.

like image 714
drago Avatar asked Oct 20 '25 14:10

drago


2 Answers

I would do the following.

  1. Use a UITableView for the vertical scroll-view.
class TableViewController: UITableViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
        self.tableView.register(TableViewHeader.self, forHeaderFooterViewReuseIdentifier: TableViewHeader.identifier)
        
        self.tableView.dataSource = self
        self.tableView.delegate = self
    }
}


extension TableViewController {
    
   override func numberOfSections(in tableView: UITableView) -> Int {
        return 10
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier,
                                                 for: indexPath) as! TableViewCell
        return cell
    }
}


extension TableViewController {
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 250
    }
    
    override  func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: TableViewHeader.identifier)
        header?.textLabel?.text = "Header \(section)"
        return header
    }
    
    
    override   func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50
    }
}


  1. Use a UICollectionView for the horizontal-scroll-view.
class CollectionView: UICollectionView {
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        self.backgroundColor = .white
        self.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
        
        self.dataSource = self
        self.delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


extension CollectionView: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView,
                        cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier,
                                                      for: indexPath) as! CollectionViewCell
        cell.index = indexPath.row
        return cell
    }
}



extension CollectionView: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 200, height: 250)
    }
    
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 20
    }
    
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }
}


class CollectionViewCell: UICollectionViewCell {
    static let identifier = "CollectionViewCell"
    
    var index: Int? {
        didSet {
            if let index = index {
                label.text = "\(index)"
            }
        }
    }
    
    private let label: UILabel = {
        let label = UILabel()
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.contentView.backgroundColor = .red
        self.contentView.addSubview(label)

        let constraints = [
            label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
        ]

        NSLayoutConstraint.activate(constraints)

        label.translatesAutoresizingMaskIntoConstraints = false
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}


  1. Each UITableViewCell contains a UICollectionView (horizontal-scroll-view).
class TableViewCell: UITableViewCell {
    static let identifier = "TableViewCell"
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.backgroundColor = .white
        
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        

        let subView = CollectionView(frame: .zero, collectionViewLayout: layout)

        self.contentView.addSubview(subView)

        let constraints = [
            subView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 1),
            subView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 1),
            subView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 1),
            subView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 1)
        ]

        NSLayoutConstraint.activate(constraints)

        subView.translatesAutoresizingMaskIntoConstraints = false
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


  1. Use a UITableViewHeaderFooterView (tableView(_:viewForHeaderInSection:) ) for the title of the horizontal-scroll-view

class TableViewHeader: UITableViewHeaderFooterView {
    static let identifier = "TableViewHeader"
}

The code that I have added a complete working example. Just use the TableViewController and you are good to go.


Update

UITableViewCells should have dynamic cell height.

Afte testing, I found out that it is better you use fixed cell-size instead of dynamic because cell might have different height should use UITableView.automaticDimension

like image 101
mahan Avatar answered Oct 22 '25 02:10

mahan


Pretty easy with SwiftUI. You should use a the VStack inside a ScrollView for the vertical one; and a HStack inside a ScrollView for the horizontal one.

here's an example:

struct ContentView: View {
    var body: some View {
        ScrollView{
            ForEach(0..<10) {_ in 
                VStack{
                    ScrollView(.horizontal){
                        HStack(spacing: 20) {
                            ForEach(0..<10) {
                                Text("Item \($0)")
                                    .font(.headline)
                                    .frame(width: 100, height: 100)
                                    .background(Color.gray)
                            }
                        }
                    }
                }
            }
        }
    }
}

I made a ForEach to replicate the example items in each stack but you should replace them with your custom views or content. In the picture you uploaded each item is a ZStack with an image and text.

image of compiled code

like image 30
Francisco Montaldo Avatar answered Oct 22 '25 03:10

Francisco Montaldo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!