UICollectionViewCell 中的 UITableView 仍然可滚动,但触摸事件应传递给集合视图。

huangapple go评论73阅读模式
英文:

UITableView inside UICollectionViewCell with the UITableView still scrollable, but touches should got to the collection view

问题

看起来是什么样子

这是一个单个CollectionView单元格的外观。

+-------------------------------+
| +-------+                     |
| | 图片  |    一些文本          |
| +-------+                     |
|                               |
| +---------------------------+ |
| + 表格视图行 1               | |
| +---------------------------+ |
| + 表格视图行 2               | |
| +---------------------------+ |
| + 表格视图行 3               | |
| +---------------------------+ |
+-------------------------------+

我想要实现的目标

根据显示的数据,表格中的条目可能是可选择的,也可能不可选择。但总是可能出现超过三行的情况,因此必须能够在垂直方向上滚动表格。但现在我也希望对于那些单行不可选择的表格,点击表格行将被传递给CollectionView,以便它能够识别触摸并触发适当的委托方法。

我到目前为止尝试过的方法

设置 tableView.allowsSelection := false 会导致表格中的行不可触摸。然而,点击似乎不会传递给CollectionView。

设置 tableView.isUserInteractionEnabled = false 会导致点击传递给CollectionView,但表格视图不再可滚动。

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? 中返回 nil 会导致表格中的行不可触摸。然而,点击似乎不会传递给CollectionView。

重写 func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 并在不应选择行的情况下返回 nil 会导致点击传递给CollectionView,但表格视图不再可滚动。

是否有任何方法可以实现这一点?

英文:

What it looks like

This is what a single CollectionView cell looks like.

+-------------------------------+
| +-------+                     |
| | Image |    Some text        |
| +-------+                     |
|                               |
| +---------------------------+ |
| + Table View row 1          | |
| +---------------------------+ |
| + Table View row 2          | |
| +---------------------------+ |
| + Table View row 3          | |
| +---------------------------+ |
+-------------------------------+

What I would like to achieve

Depending on the data displayed, the entries in the table may or may not be selectable.
But it can always happen that there are more than three rows, so you have to be able to scroll the table vertically.
But now I would also like that for the tables where the individual rows are not selectable, a touch on a table row is passed to the CollectionView so that it recognizes the touch and triggers the appropriate delegate method.

What I've tried so far

Setting tableView.allowsSelection := false causes the rows in a table to be not touchable. However, the tap does not seem to be forwarded to the collection view.

Setting tableView.isUserInteractionEnabled = false causes tap to be forwarded to the collection view, but the table view is no longer scrollable.

Returning nil in func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? causes the rows in a table to be not touchable. However, the tap does not seem to be forwarded to the collection view.

Overriding func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? and return nil when the rows should not be selectable, causes tap to be forwarded to the collection view, but the table view is no longer scrollable.

Is there any way to achieve this at all?

答案1

得分: 2

// 离开所有表视图单元格“可选择”
// 设置 `.selectionStyle = .none` 并实现自己的视觉变化以指示选定
// 在你的集合视图类中添加一个闭包到集合视图单元格
// 在集合视图单元格类中,在选择表行时调用该闭包

这是一个完整的示例...

**简单的单标签表视图单元格类** - 仅在 `canSelect`  `true` 时更改 contentView 的背景颜色:

```swift
class SomeTableCell: UITableViewCell {

    static let identifier: String = "SomeTableCell"
    
    var canSelect: Bool = true

    let label = UILabel()

    // ... (略)
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        if canSelect {
            contentView.backgroundColor = selected ? .orange : .clear
        }
    }
}

集合视图单元格类 - 带有图像视图、标签和表视图... 重写 isSelected 以便我们可以看到单元格被选中... gotTap 闭包通知集合视图控制器表行已被选中:

class SomeCollectionCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate {

    // 闭包以便我们可以告诉控制器表行已被点击
    // 无论是否可选择
    var gotTap: ((UICollectionViewCell) -> ())?

    static let identifier: String = "SomeCollectionCell"

    let imgView = UIImageView()
    let label = UILabel()
    let tableView = UITableView()

    var numTableRows: Int = 0 { didSet { tableView.reloadData() } }
    var canSelectRows: Bool = true { didSet { tableView.reloadData() } }

    // ... (略)

    override var isSelected: Bool {
        didSet {
            contentView.backgroundColor = isSelected ? .systemBlue : .systemBackground
        }
    }
}

视图控制器类 - 我们将创建 30 个“数据”元素,每个集合视图单元格的表视图行数都会不同。我们会在每个第三个项目上“禁用”表行选择:

class SomeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    // ... (略)

    override func viewDidLoad() {
        super.viewDidLoad()

        // ... (略)

        // 生成数据...
        // 30个项目,每个随机 3 到 12 个表视图行
        for _ in 0..<30 {
            myData.append(Int.random(in: 3...12))
        }

    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return myData.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let c = collectionView.dequeueReusableCell(withReuseIdentifier: SomeCollectionCell.identifier, for: indexPath) as! SomeCollectionCell
        c.imgView.image = UIImage(systemName: "\(indexPath.item).circle.fill")
        c.label.text = "Item: \(indexPath.item)"
        c.numTableRows = myData[indexPath.item]

        // 每个第三个项目将具有不可选择的表视图行
        c.canSelectRows = indexPath.row % 3 != 0

        // 闭包以便集合视图单元格可以告诉我们表被点击了
        c.gotTap = { [weak self] c in
            guard let self = self,
                  let indexPath = self.collectionView.indexPath(for: c)
            else { return }

            // 告诉集合视图选择该项目
            self.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])

            // 调用自定义函数“做某事”
            self.myCollectionView(self.collectionView, didSelectItemAt: indexPath)
        }
        return c
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // 调用自定义函数“做某事”
        myCollectionView(collectionView, didSelectItemAt: indexPath)
    }

    private func myCollectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("Selected item: \(indexPath.item)")
    }
}
英文:

One approach...

  • leave all the table view cells "selectable"
  • set .selectionStyle = .none and implement your own visual change to indicate selected
  • in your collection view class add a closure to the collection view cell
  • in collection view cell class, call that closure when a table row is selected

Here is a complete example...

simple, single-label table view cell class - change contentView background color only if canSelect is true:

class SomeTableCell: UITableViewCell {
	
	static let identifier: String = &quot;SomeTableCell&quot;
	
	var canSelect: Bool = true

	let label = UILabel()
	
	override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
		super.init(style: style, reuseIdentifier: reuseIdentifier)
		commonInit()
	}
	required init?(coder: NSCoder) {
		super.init(coder: coder)
		commonInit()
	}
	private func commonInit() {
		label.translatesAutoresizingMaskIntoConstraints = false
		contentView.addSubview(label)
		let g = contentView.layoutMarginsGuide
		NSLayoutConstraint.activate([
			label.topAnchor.constraint(equalTo: g.topAnchor),
			label.leadingAnchor.constraint(equalTo: g.leadingAnchor),
			label.trailingAnchor.constraint(equalTo: g.trailingAnchor),
			label.bottomAnchor.constraint(equalTo: g.bottomAnchor),
		])
		
		//label.font = .systemFont(ofSize: 12.0, weight: .light)
		label.font = .italicSystemFont(ofSize: 12.0)
		
		// so we can see the framing
		label.backgroundColor = .yellow
	}
	
	override func setSelected(_ selected: Bool, animated: Bool) {
		super.setSelected(selected, animated: animated)
		if canSelect {
			contentView.backgroundColor = selected ? .orange : .clear
		}
	}
	
}

collection view cell class - with image view, label and table view... override isSelected so we can see that the cell is selected... gotTap closure to inform the collection view controller that a table row was selected:

class SomeCollectionCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate {
	
	// closure so we can tell the controller that a table row was tapped
	//	whether selectable or not
	var gotTap: ((UICollectionViewCell) -&gt; ())?
	
	static let identifier: String = &quot;SomeCollectionCell&quot;
	
	let imgView = UIImageView()
	let label = UILabel()
	let tableView = UITableView()
	
	var numTableRows: Int = 0 { didSet { tableView.reloadData() } }
	var canSelectRows: Bool = true { didSet { tableView.reloadData() } }
	
	override init(frame: CGRect) {
		super.init(frame: frame)
		commonInit()
	}
	required init?(coder: NSCoder) {
		super.init(coder: coder)
		commonInit()
	}
	private func commonInit() {

		[imgView, label, tableView].forEach { v in
			v.translatesAutoresizingMaskIntoConstraints = false
			contentView.addSubview(v)
		}
		let g = contentView.layoutMarginsGuide
		NSLayoutConstraint.activate([
			
			imgView.topAnchor.constraint(equalTo: g.topAnchor),
			imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
			imgView.widthAnchor.constraint(equalToConstant: 60.0),
			imgView.heightAnchor.constraint(equalToConstant: 40.0),
			
			label.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 8.0),
			label.trailingAnchor.constraint(equalTo: g.trailingAnchor),
			label.centerYAnchor.constraint(equalTo: imgView.centerYAnchor),
			
			tableView.topAnchor.constraint(equalTo: imgView.bottomAnchor, constant: 8.0),
			tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
			tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
			tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor),

		])
		
		tableView.register(SomeTableCell.self, forCellReuseIdentifier: SomeTableCell.identifier)
		tableView.dataSource = self
		tableView.delegate = self
		
		// element properties
		imgView.contentMode = .scaleAspectFit
		label.font = .systemFont(ofSize: 13.0, weight: .light)
		contentView.layer.borderWidth = 1
		contentView.layer.borderColor = UIColor.darkGray.cgColor
		
		// so we can see the framing
		imgView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
		label.backgroundColor = .cyan
		tableView.backgroundColor = .green
	}
	
	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&gt; Int {
		return numTableRows
	}
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&gt; UITableViewCell {
		let c = tableView.dequeueReusableCell(withIdentifier: SomeTableCell.identifier, for: indexPath) as! SomeTableCell
		if canSelectRows {
			c.label.text = &quot;Sel row: \(indexPath.row)&quot;
			c.canSelect = true
		} else {
			c.label.text = &quot;NON-Sel row: \(indexPath.row)&quot;
			c.canSelect = false
		}
		c.selectionStyle = .none
		return c
	}
	func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
		// either Always call the closure
		gotTap?(self)
		
		// or, only call it if the table rows are &quot;not selectable&quot;
		//if !canSelectRows {
		//	gotTap?(self)
		//}
	}
	
	override var isSelected: Bool {
		didSet {
			contentView.backgroundColor = isSelected ? .systemBlue : .systemBackground
		}
	}
}

view controller class - we'll create 30 "data" elements, with the number of table view rows for each collection view cell. We will "disable" table row selection on every 3rd item:

class SomeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
	
	var collectionView: UICollectionView!
	
	var myData: [Int] = []
	
	override func viewDidLoad() {
		super.viewDidLoad()
		
		let fl = UICollectionViewFlowLayout()
		fl.scrollDirection = .horizontal
		fl.itemSize = .init(width: 160.0, height: 164.0)
		
		collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
		collectionView.translatesAutoresizingMaskIntoConstraints = false
		view.addSubview(collectionView)
		
		let g = view.safeAreaLayoutGuide
		NSLayoutConstraint.activate([
			collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
			collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
			collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
			collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
		])
		
		collectionView.register(SomeCollectionCell.self, forCellWithReuseIdentifier: SomeCollectionCell.identifier)
		collectionView.dataSource = self
		collectionView.delegate = self
		
		// let&#39;s generate data...
		//	30 items, each with random 3-to-12 table rows
		for _ in 0..&lt;30 {
			myData.append(Int.random(in: 3...12))
		}
		
	}
	
	func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {
		return myData.count
	}
	func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&gt; UICollectionViewCell {
		let c = collectionView.dequeueReusableCell(withReuseIdentifier: SomeCollectionCell.identifier, for: indexPath) as! SomeCollectionCell
		c.imgView.image = UIImage(systemName: &quot;\(indexPath.item).circle.fill&quot;)
		c.label.text = &quot;Item: \(indexPath.item)&quot;
		c.numTableRows = myData[indexPath.item]
		
		// every 3rd item will have non-selectable table rows
		c.canSelectRows = indexPath.row % 3 != 0
		
		// closure so the collection view cell can tell us the table was tapped
		c.gotTap = { [weak self] c in
			guard let self = self,
				  let indexPath = self.collectionView.indexPath(for: c)
			else { return }
			
			// tell the collection view to select that item
			self.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
			
			// call custom func to &quot;do something&quot;
			self.myCollectionView(self.collectionView, didSelectItemAt: indexPath)
		}
		return c
	}
	
	func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
		// call custom func to &quot;do something&quot;
		myCollectionView(collectionView, didSelectItemAt: indexPath)
	}
	private func myCollectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
		print(&quot;Selected item: \(indexPath.item)&quot;)
	}
}

huangapple
  • 本文由 发表于 2023年7月24日 19:04:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76753838.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定