Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Single and double taps on UITableViewCell in Swift 3

I have storyboard segue on TableViewCell, which I use for transfering to another VC on cell click in didSelectRowAt method. Now I made double tap TapGestureRecognizer to handle touble tap on cell. Problem is that on single tap, segue is performing and double tap is not working. Double tap works fine with clicking out of cell. Is possible to solve this somehow with my code so far? Or I need to delete segue and handle single tap and double tap separately. Thanks for any suggestions

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
    doubleTap.numberOfTapsRequired = 2
    view.addGestureRecognizer(doubleTap)
}

func handleDoubleTap(recognizer: UIGestureRecognizer) {
    let p = recognizer.location(in: tableView)

    let indexPath = tableView.indexPathForRow(at: p)

    if let _ = indexPath {
        tableView.deselectRow(at: indexPath!, animated: true)
        update(index: (indexPath?.row)!, isFinished: true)
    }

    print ("doubke")
}


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if (segue.identifier == "showSingleTask") {
        if let indexPath = tableView.indexPathForSelectedRow {
            let nav = segue.destination as! UINavigationController
            let destinationVC = nav.topViewController as! ShowTaskVC
            destinationVC.singleTask = tasks[indexPath.row]
        }
    }
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    tableView.deselectRow(at: indexPath as IndexPath, animated: true)
    self.selectedTask = tasks[indexPath.row]
}
like image 761
bzadm Avatar asked Apr 01 '17 06:04

bzadm


1 Answers

Details

  • Xcode 10.2.1 (10E1001), Swift 5

Solution

protocol MultiTappableDelegate: class {
    func singleTapDetected(in view: MultiTappable)
    func doubleTapDetected(in view: MultiTappable)
}

class ThreadSafeValue<T> {
    private var _value: T
    private lazy var semaphore = DispatchSemaphore(value: 1)
    init(value: T) { _value = value }
    var value: T {
        get {
            semaphore.signal(); defer { semaphore.wait() }
            return _value
        }
        set(value) {
            semaphore.signal(); defer { semaphore.wait() }
            _value = value
        }
    }
}

protocol MultiTappable: UIView {
    var multiTapDelegate: MultiTappableDelegate? { get set }
    var tapCounter: ThreadSafeValue<Int> { get set }
}

extension MultiTappable {
    func initMultiTap() {
        if let delegate = self as? MultiTappableDelegate { multiTapDelegate = delegate }
        let tap = UITapGestureRecognizer(target: self, action: #selector(UIView.multitapActionHandler))
        addGestureRecognizer(tap)
    }

    func multitapAction() {
        if tapCounter.value == 0 {
            DispatchQueue.global(qos: .utility).async {
                usleep(250_000)
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    if self.tapCounter.value > 1 {
                        self.multiTapDelegate?.doubleTapDetected(in: self)
                    } else {
                        self.multiTapDelegate?.singleTapDetected(in: self)
                    }
                    self.tapCounter.value = 0
                }
            }
        }
        tapCounter.value += 1
    }
}

private extension UIView {
    @objc func multitapActionHandler() {
        if let tappable = self as? MultiTappable { tappable.multitapAction() }
    }
}

Usage

class MyView: UIView, MultiTappable {
    weak var multiTapDelegate: MultiTappableDelegate?
    lazy var tapCounter = ThreadSafeValue(value: 0)
    override func awakeFromNib() {
        super.awakeFromNib()
        initMultiTap()
    }
}

Full sample

ViewController.swift

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.tableFooterView = UIView()
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
        cell.label.text = "\(indexPath)"
        cell.delegate = self
        return cell
    }
}

extension ViewController: TableViewCellDelegate {
    func singleTapDetected(in cell: TableViewCell)  {
        if let indexPath = tableView.indexPath(for: cell) { print("singleTap \(indexPath) ") }
    }
    func doubleTapDetected(in cell: TableViewCell) {
        if let indexPath = tableView.indexPath(for: cell) { print("doubleTap \(indexPath) ") }
    }
}

TableViewCell.swift

import UIKit

protocol TableViewCellDelegate: class {
    func singleTapDetected(in cell: TableViewCell)
    func doubleTapDetected(in cell: TableViewCell)
}

class TableViewCell: UITableViewCell, MultiTappable {
    weak var multiTapDelegate: MultiTappableDelegate?
    lazy var tapCounter = ThreadSafeValue(value: 0)

    @IBOutlet weak var label: UILabel!
    weak var delegate: TableViewCellDelegate?

    override func awakeFromNib() {
        super.awakeFromNib()
        initMultiTap()
    }
}

extension TableViewCell: MultiTappableDelegate {
    func singleTapDetected(in view: MultiTappable) { self.delegate?.singleTapDetected(in: self) }
    func doubleTapDetected(in view: MultiTappable) { self.delegate?.doubleTapDetected(in: self) }
}

Main.storyboard

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="g2V-T0-sqD">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_43153530" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="fQm-mQ-a9u">
                                <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                <prototypes>
                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="TableViewCell" id="nsF-ue-0bK" customClass="TableViewCell" customModule="stackoverflow_43153530" customModuleProvider="target">
                                        <rect key="frame" x="0.0" y="28" width="375" height="44"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="nsF-ue-0bK" id="pT6-2N-oTC">
                                            <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
                                            <autoresizingMask key="autoresizingMask"/>
                                            <subviews>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fEK-J3-oqH">
                                                    <rect key="frame" x="8" y="8" width="42" height="21"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                            </subviews>
                                            <constraints>
                                                <constraint firstItem="fEK-J3-oqH" firstAttribute="leading" secondItem="pT6-2N-oTC" secondAttribute="leadingMargin" id="Vfg-Ij-f6c"/>
                                                <constraint firstItem="fEK-J3-oqH" firstAttribute="top" secondItem="pT6-2N-oTC" secondAttribute="topMargin" id="tc0-qJ-N1n"/>
                                            </constraints>
                                        </tableViewCellContentView>
                                        <connections>
                                            <outlet property="label" destination="fEK-J3-oqH" id="YBJ-tG-J5T"/>
                                        </connections>
                                    </tableViewCell>
                                </prototypes>
                            </tableView>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="fQm-mQ-a9u" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="8Vy-l8-jpB"/>
                            <constraint firstItem="fQm-mQ-a9u" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="Wwr-ox-Qbd"/>
                            <constraint firstItem="fQm-mQ-a9u" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="-64" id="xJR-Uk-rbj"/>
                            <constraint firstAttribute="trailing" secondItem="fQm-mQ-a9u" secondAttribute="trailing" id="zxs-ED-Whb"/>
                        </constraints>
                    </view>
                    <navigationItem key="navigationItem" id="pLJ-Bz-NIm"/>
                    <connections>
                        <outlet property="tableView" destination="fQm-mQ-a9u" id="DhZ-jj-zmB"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="1079.2" y="137.18140929535232"/>
        </scene>
        <!--Navigation Controller-->
        <scene sceneID="w7e-Wj-oUR">
            <objects>
                <navigationController automaticallyAdjustsScrollViewInsets="NO" id="g2V-T0-sqD" sceneMemberID="viewController">
                    <toolbarItems/>
                    <navigationBar key="navigationBar" contentMode="scaleToFill" id="7qG-8v-S0O">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
                        <autoresizingMask key="autoresizingMask"/>
                    </navigationBar>
                    <nil name="viewControllers"/>
                    <connections>
                        <segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="yqZ-pK-Yf3"/>
                    </connections>
                </navigationController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="tnz-x0-vDN" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="140" y="137.18140929535232"/>
        </scene>
    </scenes>
</document>

Result

enter image description here

like image 85
Vasily Bodnarchuk Avatar answered Nov 08 '22 21:11

Vasily Bodnarchuk