Swift 中一个垂直 ScrollView 中的多个水平 ScrollView?

Posted

技术标签:

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

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

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

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

类似这样的:

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

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

任何指针将不胜感激。

【问题讨论】:

检查下面的答案它可能对你有帮助***.com/a/65937540/8079610 添加了可供测试的代码。 【参考方案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
    



    为水平滚动视图使用 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")
    
    



    每个 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")
    



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

    为主 ViewController 创建一个 UITableView

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

     For ex:- for mental fitness - Create separate mental fitness ViewController for that
     for sleep stories - Create separate Sleep Stories ViewController
    

    现在高潮来这里叫做addChild()方法。

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

    您要做的最后一件事就是只需在特定单元格中将子 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,这是一个糟糕的解决方案。

以上是关于Swift 中一个垂直 ScrollView 中的多个水平 ScrollView?的主要内容,如果未能解决你的问题,请参考以下文章

UITableViewCell中的Swift ScrollView不滚动

我的 ScrollView 没有垂直滚动

UIScrollview中的UIWebview向scrollview发送垂直滚动事件

在 Flutter 中的垂直 ScrollView 内的水平 ListView 中显示来自 Firestore 的数据

使用垂直 ScrollView 忽略水平安全区域

ViewPager中的视图在ScrollView中不能垂直滚动