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:
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.
I would do the following.
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
}
}
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")
}
}
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")
}
}
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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With