【问题标题】:Multiple Horizontal ScrollViews In One Vertical ScrollView in Swift?Swift 中一个垂直 ScrollView 中的多个水平 ScrollView?
【发布时间】:2021-01-28 11:54:54
【问题描述】:

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

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

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

类似这样的:

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

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

任何指针将不胜感激。

【问题讨论】:

标签: ios swift


【解决方案1】:

我会做以下事情。

  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

【讨论】:

    【解决方案2】:

    使用 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

    【讨论】:

    • 兄弟,他在 swift 中而不是在 swiftUI 中提出问题
    • @Wings 这两个不是对立的或不同的语言。您可以使用相同的组件在故事板或使用 UIKit 的代码中复制它。这样写更容易
    • 我可以在 UIHostingController 中使用它吗?
    • 如果您打算使用 SwiftUI 或将其集成到 UIKit 中,这是一个很好的解决方案。请记住,SwiftUI 与 iOS 13+ 兼容。
    • @mahan,是的,我更倾向于在我的 UIKit 项目中使用 SwiftUI,因为 SwiftUI 似乎比 UIKit 更容易实现。
    【解决方案3】:
    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 {
    
    
    
    }
    

    通过这种方式,您可以轻松管理来自服务器的数据。因为它可以让您在单独的 ViewController 中显示特定的单元格数据等等。

    【讨论】:

    • @user2056633....是的,请再次检查我的答案;)
    • @roozfar....你能展示一下你在做什么的代码吗?
    • @Wings,我的代码与您的示例相同。 let firstVC = FirstViewController() 不应该是let firstVC = UIViewController()吗?
    • 好吧,你做错了伙伴......只需创建另一个 ViewController,其名称为 UIViewController() 的 FirstViewController 子类
    • 在性能方面,如果您为每个水平滚动视图创建一个 UIViewController,这是一个糟糕的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2011-11-08
    • 2015-11-30
    • 1970-01-01
    • 2020-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多