简体   繁体   中英

Multiple Horizontal ScrollViews In One Vertical ScrollView in 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:

在此处输入图像描述

what is the best way of achieving this?

PS 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.

  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

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

  1. Create a UITableView for main ViewController.

  2. The views you have to create inside it make their separate ViewController.

     For ex:- for mental fitness - Create separate mental fitness ViewController for that for sleep stories - Create separate Sleep Stories ViewController
  3. Now the climax come here called addChild() method.

Access your all ViewControllers in your main ViewController class and add them in your viewDidLoad() method inside addChild() method.

  1. The last thing you have to do is you just have to add that child ViewControllers in your particular cell as view .

For reference you can check these examples:-

https://www.hackingwithswift.com/example-code/uikit/how-to-use-view-controller-containment

https://www.swiftbysundell.com/basics/child-view-controllers/

Advantage:-

  • This way you can easily manage your data coming from the server

For example:-

 import UIKit

class ViewController: UIViewController {

@IBOutlet weak var tableView: UITableView!

//subViewControllers
let firstVC = FirstViewController()
let secondVC = SecondViewController()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    
    self.addChild(firstVC) //adding childVC here
    self.addChild(secondVC)

}


}

UITableViewDataSource and Delegate Method

extension ViewController: UITableViewDelegate, UITableViewDataSource {



func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return 10
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    
    return 250
}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? MainCell else {
        
        return UITableViewCell()
    }
    
    if indexPath.row == 0 {
        

        cell.contentView.addSubview(firstVC.view)
                    
    }
    
    if indexPath.row == 1 {
         
        cell.contentView.addSubview(secondVC.view)

    }
    
    return cell
}





 }

UITableViewCell Class

class MainCell: UITableViewCell {



}

UITableViewCell 类

This way you can easily manage your data which is coming from server. Because it will give you an advantage for showing particular cell data in separate ViewController and much more.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM