Swift 中一个垂直滚动视图中的多个水平滚动视图?

时间:2021-01-28 11:54:54

标签: ios swift

我正在尝试实现 Swift 应用中最标准的布局之一。

基本上在一个垂直滚动视图中有多个水平滚动视图。

这些子水平滚动视图中的每一个都将包含一些带有图像的视图。

像这样:

enter image description here

实现这一目标的最佳方法是什么?

附言我需要使用代码来执行此操作,因为内容是通过远程 JSON 文件提取的。

任何指针将不胜感激。

3 个答案:

答案 0 :(得分:6)

我会做以下事情。

  1. 使用 UITableView 作为垂直滚动视图。
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. 使用 UICollectionView 作为水平滚动视图。
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. 每个 UITableViewCell 都包含一个 UICollectionView(水平滚动视图)。
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. 使用 UITableViewHeaderFooterView (tableView(_:viewForHeaderInSection:) ) 作为水平滚动视图的标题

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

我添加了一个完整的工作示例的代码。只需使用 TableViewController 即可。


更新

UITableViewCells 应该具有动态单元格高度。

经过测试,我发现最好使用固定单元格大小而不是动态单元格,因为单元格可能具有不同的高度应该使用 UITableView.automaticDimension

答案 1 :(得分:3)

使用 SwiftUI 非常简单。 您应该在 ScrollView 中使用 VStack 作为垂直视图; 以及 ScrollView 中的 HStack 用于水平视图。

这是一个例子:

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)
                            }
                        }
                    }
                }
            }
        }
    }
}

我制作了一个 ForEach 来复制每个堆栈中的示例项目,但您应该将它们替换为您的自定义视图或内容。在您上传的图片中,每个项目都是一个带有图像和文本的 ZStack。

image of compiled code

答案 2 :(得分:2)

最好的方法是实现这一目标:-

  1. 为主 ViewController 创建一个 UITableView

  2. 您必须在其中创建的视图使其成为独立的 ViewController。

     For ex:- for mental fitness - Create separate mental fitness ViewController for that
     for sleep stories - Create separate Sleep Stories ViewController
    
  3. 现在高潮来了,叫做addChild()方法。

访问主 ViewController 类中的所有 ViewController,并将它们添加到 addChild() 方法内的 viewDidLoad() 方法中。

  1. 您需要做的最后一件事就是在您的特定单元格中将该子 ViewController 添加为 view

作为参考,您可以查看这些示例:-

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

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

优势:-

  • 这样您就可以轻松管理来自服务器的数据

例如:-

 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 和委托方法

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 MainCell: UITableViewCell {



}

UITableViewCell Class

这样您就可以轻松管理来自服务器的数据。因为它会给你一个优势,在单独的 ViewController 中显示特定的单元格数据等等。

相关问题