Post

TableView里的cell里require(toFail:xx)手势冲突

TableView里的cell里require(toFail:xx)手势冲突

参考:

最近做的一个项目,普通的TableView列表里的cell展示内容,在Cell里的子View上增加点击手势,如果点击在添加手势的View上,只会响应tap事件,而TableView的tableView(_, cellForRowAt) 不会响应, 参考:https://github.com/h42330789/StudyDB/blob/main/StudyTableTap/StudyTableTap/TableVC2.swift demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class ImageCellB: UITableViewCell {
    let container = UIView()
    let descLabel = UILabel()
    var data: String? = nil
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.selectionStyle = .none
        self.initSelf()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func initSelf() {
        self.backgroundColor = .white
        container.backgroundColor = .lightGray
        let fullWidth = mainBounds.width
        let padding: CGFloat = 10
        container.frame = CGRectMake(fullWidth-padding-containerWidth, padding, containerWidth, mainRowHeight-padding*2)
        self.contentView.addSubview(container)
        
        descLabel.textColor = .black
        descLabel.frame = CGRect(x: 20, y: 20, width: 100, height: 30)
        container.addSubview(descLabel)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(containerTapClick))
        container.addGestureRecognizer(tapGesture)
        
        let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(containerDoubleTapClick))
        doubleTapGestureRecognizer.numberOfTapsRequired = 2
        doubleTapGestureRecognizer.numberOfTouchesRequired = 1
        container.addGestureRecognizer(doubleTapGestureRecognizer)
    }
    
    func updateData(text: String) {
//        print("old: \(data ?? "") new:\(text)")
        self.data = text
        self.descLabel.text = text
    }
    
    @objc func containerTapClick() {
        print("\(Date.systemDateStr2) containerTapClick: \(data ?? "")")
    }
    
    @objc func containerDoubleTapClick() {
        print("\(Date.systemDateStr2) containerDoubleTapClick: \(data ?? "")")
    }
}

require(toFail:xxx)会导致didSelect和tap一起响应

demo:https://github.com/h42330789/StudyDB/blob/main/StudyTableTap/StudyTableTap/TableVC22.swift 手势依赖后就会产生重大变化,除了tap事件会响应外

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//
//  TableVC22.swift
//  StudyTableTap
//
//  Created by flow on 9/14/24.
//

import UIKit

class TableVC22: BaseTableVC {
    
    override func viewDidLoad() {
        self.tableView.register(ImageCellB2.self, forCellReuseIdentifier: "Cell")
        super.viewDidLoad()
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? ImageCellB2 {
            let data = self.dataList[indexPath.row]
            cell.updateData(text: data)
            let oldFrame = cell.container.frame
            let x = (indexPath.row % 2 == 0) ? 10 : (mainBounds.width-10-containerWidth)
            cell.container.frame = CGRect(x: x, y: oldFrame.minY, width: oldFrame.width, height: oldFrame.height)
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
            return cell
        }
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if let cell = tableView.cellForRow(at: indexPath) as? ImageCellB2 {
            print("\(cell.selfCell) \(Date.systemDateStr2) didSelect: \(dataList[indexPath.row])")
            self.inputMenuView.hideMenu()
        }
    }
}


class ImageCellB2: UITableViewCell {
    let container = UIView()
    let descLabel = UILabel()
    var data: String? = nil
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.selectionStyle = .none
        self.initSelf()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func initSelf() {
        self.backgroundColor = .white
        container.backgroundColor = .lightGray
        let fullWidth = mainBounds.width
        let padding: CGFloat = 10
        container.frame = CGRectMake(fullWidth-padding-containerWidth, padding, containerWidth, mainRowHeight-padding*2)
        self.contentView.addSubview(container)
        
        descLabel.textColor = .black
        descLabel.frame = CGRect(x: 20, y: 20, width: 100, height: 30)
        container.addSubview(descLabel)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(containerTapClick))
        container.addGestureRecognizer(tapGesture)
        
        let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(containerDoubleTapClick))
        doubleTapGestureRecognizer.numberOfTapsRequired = 2
        doubleTapGestureRecognizer.numberOfTouchesRequired = 1
        container.addGestureRecognizer(doubleTapGestureRecognizer)
        tapGesture.require(toFail: doubleTapGestureRecognizer)
        
        
        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(containerLongPress(_:)))
        container.addGestureRecognizer(longPressGesture)
    }
    var selfCell: String {
        let selfCell = "\(self)".components(separatedBy: ";").first?.components(separatedBy: ".").last ?? ""
        return selfCell
    }
    func updateData(text: String) {
        print("\(selfCell) old: \(data ?? "") new:\(text)")
        self.data = text
        self.descLabel.text = text
    }
    
    @objc func containerTapClick() {
        print("\(selfCell) \(Date.systemDateStr2) containerTapClick: \(data ?? "")")
    }
    
    @objc func containerDoubleTapClick() {
        print(" \(selfCell) \(Date.systemDateStr2) containerDoubleTapClick: \(data ?? "")")
    }
    
    @objc func containerLongPress(_ gesture: UILongPressGestureRecognizer) {
        print("\(selfCell) \(Date.systemDateStr2) containerLongPress: \(data ?? "")")
    }
}


由于当改变TableView的frame后重新reload,会导致Cell复用修改,再集合前面同时响应的问题,在键盘升起的状态下,点击Cell里的内容,didSelect先响应,然后执行更新动画和刷新数据,直到执行完tap事件才会执行,在tap事件执行时,点击cell的属于已经从点击时的A变成了执行时的B了 效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
ImageCellB2: 0x112056a00 2024-09-16 21:29:19.054 didSelect: G
2024-09-16 21:29:19.054 showBtnClick-close-start
2024-09-16 21:29:19.054 updateTableHeight-start
ImageCellB2: 0x112034000 old: D new:B
ImageCellB2: 0x10f00d000 old: A new:C
ImageCellB2: 0x10d842600 old: F new:D
ImageCellB2: 0x112033200 old: C new:E
ImageCellB2: 0x11200aa00 old: E new:F
ImageCellB2: 0x112034600 old: B new:G
ImageCellB2: 0x112056a00 old: G new:A
2024-09-16 21:29:19.261 showBtnClick-close-complete
2024-09-16 21:29:19.262 updateTableHeight-complete
ImageCellB2: 0x112056a00 2024-09-16 21:29:19.404 containerTapClick: A

image


解决方案: 由于touchesEndeddidSelect和tap事件之前执行,可以在touchesEnded里记录点击时的数据,等执行tap事件时,使用touchesEnded记录的数据才执行操作,而不是tap回调时Cell里持有的数据,demo如下

参考: https://github.com/h42330789/StudyDB/blob/main/StudyTableTap/StudyTableTap/TableVC4.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
class TableVC4: BaseTableVC {
    
    override func viewDidLoad() {
        self.tableView.register(ImageCellD.self, forCellReuseIdentifier: "Cell")
        super.viewDidLoad()
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? ImageCellD {
            let data = self.dataList[indexPath.row]
            cell.updateData(text: data)
            let oldFrame = cell.container.frame
            let x = (indexPath.row % 2 == 0) ? 10 : (mainBounds.width-10-containerWidth)
            cell.container.frame = CGRect(x: x, y: oldFrame.minY, width: oldFrame.width, height: oldFrame.height)
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
            return cell
        }
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if let cell = tableView.cellForRow(at: indexPath) as? ImageCellD {
            print("\(cell.selfCell) \(Date.systemDateStr2) didSelect: \(dataList[indexPath.row])")
            self.inputMenuView.hideMenu()
        }
    }
}

class ImageCellD: UITableViewCell {
    let container = UIView()
    let descLabel = UILabel()
    var data: String? = nil
    var touchData: String? = nil
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.selectionStyle = .none
        self.initSelf()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func initSelf() {
        self.backgroundColor = .white
        container.backgroundColor = .lightGray
        let fullWidth = mainBounds.width
        let padding: CGFloat = 10
        container.frame = CGRectMake(fullWidth-padding-containerWidth, padding, containerWidth, mainRowHeight-padding*2)
        self.contentView.addSubview(container)
        
        descLabel.textColor = .black
        descLabel.frame = CGRect(x: 20, y: 20, width: 100, height: 30)
        container.addSubview(descLabel)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(containerTapClick))
        container.addGestureRecognizer(tapGesture)
        
        let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(containerDoubleTapClick))
        doubleTapGestureRecognizer.numberOfTapsRequired = 2
        doubleTapGestureRecognizer.numberOfTouchesRequired = 1
        container.addGestureRecognizer(doubleTapGestureRecognizer)
        tapGesture.require(toFail: doubleTapGestureRecognizer)
        
        
        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(containerLongPress(_:)))
        container.addGestureRecognizer(longPressGesture)
    }
    var selfCell: String {
        let selfCell = "\(self)".components(separatedBy: ";").first?.components(separatedBy: ".").last ?? ""
        return selfCell
    }
    
    func updateData(text: String) {
        print("\(selfCell) old: \(data ?? "") new:\(text)")
        self.data = text
        self.descLabel.text = text
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.touchData = nil
        print("\(selfCell) \(Date.systemDateStr2) touchesBegan: \(data ?? "")")
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("\(selfCell) \(Date.systemDateStr2) touchesEnded: \(data ?? "")")
        self.touchData = data
        super.touchesEnded(touches, with: event)
    }
    
    @objc func containerTapClick() {
        print("\(selfCell) \(Date.systemDateStr2) containerTapClick: touchData:\(touchData ?? "") data:\(data ?? "")")
        self.touchData = nil
    }
    
    @objc func containerDoubleTapClick() {
        print("\(selfCell) \(Date.systemDateStr2) containerDoubleTapClick: touchData:\(touchData ?? "") data:\(data ?? "")")
        self.touchData = nil
    }
    
    @objc func containerLongPress(_ gesture: UILongPressGestureRecognizer) {
        print("\(selfCell) \(Date.systemDateStr2) containerLongPress: touchData:\(touchData ?? "") data:\(data ?? "")")
        self.touchData = nil
    }
}

执行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ImageCellD: 0x108009400 2024-09-16 21:38:30.240 touchesBegan: G
ImageCellD: 0x108009400 2024-09-16 21:38:30.240 touchesEnded: G
ImageCellD: 0x108009400 2024-09-16 21:38:30.241 didSelect: G
2024-09-16 21:38:30.241 showBtnClick-close-start
2024-09-16 21:38:30.242 updateTableHeight-start
ImageCellD: 0x109859000 old: D new:B
ImageCellD: 0x10985ae00 old: A new:C
ImageCellD: 0x109852200 old: F new:D
ImageCellD: 0x109859600 old: C new:E
ImageCellD: 0x109846c00 old: E new:F
ImageCellD: 0x109859c00 old: B new:G
ImageCellD: 0x108009400 old: G new:A
2024-09-16 21:38:30.448 showBtnClick-close-complete
2024-09-16 21:38:30.449 updateTableHeight-complete
ImageCellD: 0x108009400 2024-09-16 21:38:30.590 containerTapClick: touchData:G data:A

image

This post is licensed under CC BY 4.0 by the author.