ios – Long press action problem with pan gesture of a table view cell


i created a delete button for tableview that is exposed by swiping with pan gesture. i could have done it with UIContextualAction but i can’t customize the appearance of the button. so i decided to make the button myself. the pan gesture function i created also worked but there is a problem. when i hold down the cell (long press) while the cell is swiped to the left, the cell returns to its previous state instantly. how can i prevent this? there are two classes related to this topic called TableViewCell and ViewController

here is my TableViewCell class



import UIKit
import CoreData

protocol TableViewCellDelegate : AnyObject {
    func didRequestDelete(_ cell: TableViewCell)
}

class TableViewCell: UITableViewCell {

    let titleLabel = UILabel()
    let noteLabel = UILabel()
    let dateLabel = UILabel()
    let timeLabel = UILabel()
    let dateFormatter = DateFormatter()
    let timeFormatter = DateFormatter()
    let deleteButton = UIButton()
    weak var delegate : TableViewCellDelegate?

    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        dateFormatter.dateFormat = "dd/MM/yyyy"
        timeFormatter.dateFormat = "HH:mm"

        selectionStyle = .none
        setupDeleteButton()
        
        contentView.layer.cornerRadius = 15
        contentView.layer.borderWidth = 2
        contentView.clipsToBounds = false
        backgroundColor = UIColor.clear
        backgroundView?.backgroundColor = UIColor.clear
        selectedBackgroundView?.backgroundColor = UIColor.clear
        
        setLabel(label: titleLabel, textColor: .systemGray5, fontSize: 22, numberOfLine: 0).isEnabled = false
        setLabel(label: noteLabel, textColor: .systemGray5, fontSize: 18, numberOfLine: 3).isHidden = false
        setLabel(label: dateLabel, textColor: .systemGray, fontSize: 14, numberOfLine: 1).isHidden = false
        setLabel(label: timeLabel, textColor: .systemGray, fontSize: 14, numberOfLine: 1).isHidden = false
        
        addContentView(views: [titleLabel,noteLabel,dateLabel,timeLabel])
       
        let colorIndex = UserDefaults.standard.integer(forKey: "index")
        setupBasedOnColors(index: colorIndex)
        setupConstraints()
        
        let leftSwipeGesture = UIPanGestureRecognizer(target: self, action: #selector(handleLeftSwipe(_:)))
        contentView.addGestureRecognizer(leftSwipeGesture)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleCellTap(_:)))
        tapGesture.delegate = self
        contentView.addGestureRecognizer(tapGesture)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let pointForTargetView = deleteButton.convert(point, from: self)
        if deleteButton.bounds.contains(pointForTargetView) {
            return deleteButton
        }
        return super.hitTest(point, with: event)
    }

    
    func setupBasedOnColors(index: Int){
        switch index {
        case 0:
            contentView.layer.borderColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1).cgColor
        case 1:
            contentView.layer.borderColor = UIColor.systemPink.cgColor
        case 2:
            contentView.layer.borderColor = UIColor.systemGray3.cgColor
        case 3:
            contentView.layer.borderColor = UIColor.systemBlue.cgColor
        case 4:
            contentView.layer.borderColor = UIColor.systemYellow.cgColor
        case 5:
            contentView.layer.borderColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1).cgColor
        default:
            print("Renk Bulunamadı")
        }
        contentView.setNeedsDisplay()
    }
    
    func setupDeleteButton(){
        contentView.addSubview(deleteButton)
        deleteButton.setImage(UIImage(systemName: "trash"), for: .normal)
        deleteButton.layer.cornerRadius = 15
        deleteButton.tintColor = .white
        deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
        deleteButton.isUserInteractionEnabled = true
    }
    
    func setLabel(label: UILabel, textColor : UIColor, fontSize: CGFloat, numberOfLine: Int) -> UILabel {
        label.textColor = textColor
        label.font = UIFont.systemFont(ofSize: fontSize)
        label.numberOfLines = numberOfLine
        return label
    }
    
    func addContentView(views: [UILabel]){
        views.forEach { view in
            contentView.addSubview(view)
        }
    }

    private func setupConstraints() {
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        noteLabel.translatesAutoresizingMaskIntoConstraints = false
        dateLabel.translatesAutoresizingMaskIntoConstraints = false
        timeLabel.translatesAutoresizingMaskIntoConstraints = false
        deleteButton.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
            
            deleteButton.leadingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 6),
            deleteButton.topAnchor.constraint(equalTo: contentView.topAnchor),
            deleteButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            deleteButton.widthAnchor.constraint(equalToConstant: 100),
            
            noteLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5),
            noteLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            noteLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
            noteLabel.bottomAnchor.constraint(equalTo: dateLabel.topAnchor, constant: -10),
            
            dateLabel.topAnchor.constraint(equalTo: noteLabel.bottomAnchor, constant: 10),
            dateLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            dateLabel.trailingAnchor.constraint(equalTo: timeLabel.leadingAnchor, constant: -15),
            dateLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
            
            timeLabel.topAnchor.constraint(equalTo: dateLabel.topAnchor),
            timeLabel.leadingAnchor.constraint(equalTo: dateLabel.trailingAnchor, constant: 20),
            timeLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -9)
        ])
    }

    func configure(with note: NoteText) {
        titleLabel.text = note.title
        noteLabel.text = note.text
        if let date = note.date {
            timeLabel.text = timeFormatter.string(from: date)
        } else {
            timeLabel.text = "No time"
        }
        
        if let date = note.date {
               configureCellDateLabel(with: date)
           } else {
               dateLabel.text = "No date"
           }
    }
    
    func configureCellDateLabel(with date: Date) {
        let calendar = Calendar.current
        if calendar.isDateInToday(date) {
            dateLabel.text = "Today"
        } else {
            dateFormatter.dateFormat = "dd/MM/yyyy"
            dateLabel.text = dateFormatter.string(from: date)
        }
    }
    
    @objc func handleLeftSwipe(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: contentView)
        if gesture.state == .changed {
            let newPosition = max(translation.x, -(deleteButton.frame.width + 7))
            if newPosition <= 0 {
                contentView.frame.origin.x = newPosition
            }
        } else if gesture.state == .ended {
            let shouldRevealButton = contentView.frame.origin.x < -deleteButton.frame.width / 2
            UIView.animate(withDuration: 0.3) {
                self.contentView.frame.origin.x = shouldRevealButton ? -(self.deleteButton.frame.width + 7) : 0
            }
        }
    }
    
    @objc func handleCellTap(_ gesture: UITapGestureRecognizer) {
        if contentView.frame.origin.x != 0 {
            UIView.animate(withDuration: 0.3) {
                self.contentView.frame.origin.x = 0
            }
        }
    }
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return contentView.frame.origin.x != 0
    }

    @objc func deleteButtonTapped(){
        delegate?.didRequestDelete(self)
    }
}

I tried to add some conditions in didSelectRowAt function in ViewController class but it didn’t work.

import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, TableViewCellDelegate {
    


    var context: NSManagedObjectContext!
    let tableView = UITableView()
    let createNoteButton = UIButton()
    let cellSpacing : CGFloat = 6
    var fetchedNotes: [NoteText] = []
    var titleLabel = UILabel()
    var selectedIndexPath: IndexPath?
    var randomLabel = UILabel()
    var randomColor = UIColor()
    var randomBool = Bool()
    let dateFormatter = DateFormatter()
    let timeFormatter = DateFormatter()
    
    lazy var optionsMarkItem : UIBarButtonItem = {
        let item = UIBarButtonItem(image: UIImage(systemName: "gearshape.fill"), style: .plain, target: self, action: #selector(optionsButtonTapped))
        item.tintColor = .systemYellow
        return item
    }()
    


    //MARK: viewDidLoad
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        setupTableView()
        
        for family in UIFont.familyNames.sorted() {
            print("\(family)")
            for name in UIFont.fontNames(forFamilyName: family).sorted() {
                print("== \(name)")
            }
        }

        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            fatalError("Cannot retrieve app delegate")
        }
        context = appDelegate.persistentContainer.viewContext

        setupCreateNoteButton()
        randomLabel.isHidden = true
        setupTitleLabel()
        tableView.backgroundColor = .black
        tableView.delegate = self
        tableView.dataSource = self
        fetchNotes()
        navigationItem.rightBarButtonItem = optionsMarkItem
        
        let longPressGestureButton = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressCell))
        let longPressGestureCell = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressButton))
        longPressGestureCell.minimumPressDuration = 0.2
        longPressGestureButton.minimumPressDuration = 0.2
        
        tableView.addGestureRecognizer(longPressGestureButton)
        createNoteButton.addGestureRecognizer(longPressGestureCell)
        
        let switchValue = UserDefaults.standard.bool(forKey: "mySwitchValue")
        setupUIBasedOnSwitch(switchValue: switchValue)
        
    }
    
    // MARK: viewWillAppear
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        fetchNotes()
        tableView.delegate = self
        tableView.dataSource = self
        
        let switchValue = UserDefaults.standard.bool(forKey: "mySwitchValue")
        setupUIBasedOnSwitch(switchValue: switchValue)
        dateFormatter.dateFormat = "dd/MM/yyyy"
        timeFormatter.dateFormat = "HH:mm"
        
        let colorIndex = UserDefaults.standard.integer(forKey: "index")
        setupBasedOnColors(index: colorIndex)
    }
    
    // MARK: UserDefaults get functions
    
    func getIndexValue() -> Int{
        let index = UserDefaults.standard.integer(forKey: "index")
        return index
    }

    //MARK: Core Data Fonksiyonları
    
    func fetchNotes() {
        let request: NSFetchRequest<NoteText> = NoteText.fetchRequest()
        do {
            fetchedNotes = try context.fetch(request)
        } catch {
            print("Not çekme hatası: \(error)")
        }
        tableView.reloadData()
    }
    
    func addNoteWithTitle(_ title: String, text: String, date: Date, time: Date) {
        let newNote = NoteText(context: context)
        newNote.title = title
        newNote.text = text
        newNote.date = date
        newNote.time = date
        do {
            try context.save()
            fetchNotes() // Listeyi güncelle
        } catch {
            print("Not kaydetme hatası: \(error)")
        }
    }
    
    func deleteNoteAtIndexPath(_ indexPath: IndexPath) {
        let noteToDelete = fetchedNotes[indexPath.section]
        context.delete(noteToDelete)
        fetchedNotes.remove(at: indexPath.section)
        
        do {
            try context.save()
            tableView.beginUpdates()
            tableView.deleteSections([indexPath.section], with: .automatic)
            tableView.endUpdates()
        } catch {
            print("Not silme hatası: \(error)")
        }
    }
    
    //MARK: setup Fonksiyonları
    
    func setupTableView() {
        view.addSubview(tableView)
        tableView.dataSource = self
        tableView.delegate = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 5),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -5)
        ])
        tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
    }
    
    func setupCreateNoteButton() {
        view.addSubview(createNoteButton)
        let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 45, weight: .regular, scale: .default)
        // "+" işaretini oluşturuyoruz ve konfigürasyonunu ekliyoruz
        let plusSymbol = UIImage(systemName: "plus", withConfiguration: symbolConfiguration)
        createNoteButton.setImage(plusSymbol, for: .normal)
        createNoteButton.tintColor = .systemYellow // "+" işaretinin rengini sarı yapıyoruz
        
        // Buton için içi boş çember oluşturuyoruz
        createNoteButton.backgroundColor = .clear // Butonun arka planını transparan yapıyoruz
        createNoteButton.layer.cornerRadius = 35 // Butonun yarıçapını ayarlıyoruz
        createNoteButton.layer.borderWidth = 2 // Çerçeve genişliği
        createNoteButton.layer.borderColor = UIColor.systemYellow.cgColor
        
        createNoteButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            createNoteButton.heightAnchor.constraint(equalToConstant: 70),
            createNoteButton.widthAnchor.constraint(equalToConstant: 70),
            createNoteButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20),
            createNoteButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -55)
        ])
        createNoteButton.addTarget(self, action: #selector(createNoteButtonTapped), for: .touchUpInside)
    }


    
    func setupTitleLabel() {
        titleLabel.text = "TextCrypt"
        titleLabel.font = UIFont(name: "TimesNewRomanPS-BoldMT", size: 27)
        titleLabel.textColor = .white
        navigationItem.titleView = titleLabel
    }
    
    func setupUIBasedOnSwitch(switchValue: Bool) {
        if switchValue {
            view.backgroundColor = .white
            tableView.backgroundColor = .white
            randomLabel.textColor = .black
            titleLabel.textColor = .black
          //  randomColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1)
            randomColor = .white
        } else {
            view.backgroundColor = .black
            tableView.backgroundColor = .black
            randomLabel.textColor = .white
            titleLabel.textColor = .white
          //  randomColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1)
            randomColor = .black
        }
    }
    
    private func resetCellSize(at indexPath: IndexPath) {
        if let cell = tableView.cellForRow(at: indexPath) {
            UIView.animate(withDuration: 0.2) {
                cell.transform = CGAffineTransform.identity
            }
        }
    }
    
    private func resetButtonSize(){
        UIView.animate(withDuration: 0.1) {
            self.createNoteButton.transform = CGAffineTransform.identity
        }
    }
    
    // MARK: @objc fonksiyonları
        
    @objc func optionsButtonTapped() {
        let settingsVC = SettingsController()
        settingsVC.modalPresentationStyle = .fullScreen
        settingsVC.mainVC = self
        present(settingsVC, animated: true)
    //    settingsVC.dismissAction = { [weak self] in }
        
        }


    
    @objc func handleLongPressButton(gesture: UILongPressGestureRecognizer) {
        if gesture.state == .began {
            UIView.animate(withDuration: 0.1) {
                self.createNoteButton.transform = CGAffineTransform(scaleX: 0.90, y: 0.90)
            }
        } else if gesture.state == .ended || gesture.state == .cancelled{
            resetButtonSize()
        }
    }
    
    @objc func handleLongPressCell(gesture: UILongPressGestureRecognizer) {
        let point = gesture.location(in: tableView)
        if let indexPath = tableView.indexPathForRow(at: point) {
            switch gesture.state {
            case .began:
                if let cell = tableView.cellForRow(at: indexPath) {
                        UIView.animate(withDuration: 0.1) {
                            cell.transform = CGAffineTransform(scaleX: 0.97, y: 0.97)
                    }
                }
            case .ended, .changed:
                resetCellSize(at: indexPath)
            default:
                break
            }
        }
    }

    

    // MARK: tavleView ayarları

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return fetchedNotes.count
    }
    
    func didRequestDelete(_ cell: TableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else {return}
        self.deleteNoteAtIndexPath(indexPath)
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
            return UITableViewCell()
        }
        cell.delegate = self
        cell.contentView.backgroundColor = self.randomColor
        cell.contentView.layer.borderColor = self.createNoteButton.tintColor.cgColor
        cell.titleLabel.textColor = self.randomLabel.textColor
        cell.noteLabel.textColor = self.randomLabel.textColor
        cell.deleteButton.backgroundColor = self.createNoteButton.tintColor

        let note = fetchedNotes[indexPath.section]
        cell.configure(with: note)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let view = UIView()
        view.backgroundColor = .clear
        return view
    }

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return cellSpacing
    }

    // MARK: didSelectRowAt

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.reloadRows(at: [indexPath], with: .none)
        
        if let cell = tableView.cellForRow(at: indexPath) {
            let animation = CABasicAnimation(keyPath: "transform.scale")
            animation.fromValue = 1.0
            animation.toValue = 0.92
            animation.duration = 0.10
            animation.autoreverses = true
            animation.repeatCount = 1

            cell.layer.add(animation, forKey: "bounce")
        }
        
        let noteDetailVC = NoteDetailViewController()
        // Seçilen notu NoteDetailViewController'a geçir
        let selectedNote = fetchedNotes[indexPath.section]
        noteDetailVC.note = selectedNote
        noteDetailVC.noteContent = selectedNote.text
        noteDetailVC.titleContent = selectedNote.title
        noteDetailVC.dateContent = selectedNote.date
        noteDetailVC.timeContent = selectedNote.time
        noteDetailVC.view.backgroundColor = self.view.backgroundColor
        noteDetailVC.textView.backgroundColor = self.view.backgroundColor
        noteDetailVC.titleLabel.textColor = self.randomLabel.textColor
        noteDetailVC.textView.textColor = self.randomLabel.textColor
        noteDetailVC.backMarkItem.tintColor = createNoteButton.tintColor
        noteDetailVC.checkMarkItem.tintColor = createNoteButton.tintColor
        noteDetailVC.redoButton.tintColor = createNoteButton.tintColor
        noteDetailVC.undoButton.tintColor = createNoteButton.tintColor
        noteDetailVC.encryptMarkItem.tintColor = createNoteButton.tintColor
        noteDetailVC.specialButton.backgroundColor = createNoteButton.tintColor
        noteDetailVC.dismissAction = { [weak self] in
            // Kullanıcı notu tamamen silip geri döndüğünde ilgili notu sil
            if let newNote = noteDetailVC.noteContent, newNote.isEmpty,
               let newTitle = noteDetailVC.titleContent, newTitle.isEmpty {
                // Not silme fonksiyonunu çağır
                self?.deleteNoteAtIndexPath(indexPath)
            } else {
                // Not güncellendiyse veya değişiklik olmadıysa güncellemeleri kaydet
                if let newNote = noteDetailVC.noteContent, !newNote.isEmpty,
                   let newTitle = noteDetailVC.titleContent, !newTitle.isEmpty {
                    // Yeni bir Core Data nesnesi oluştur ve kaydet
                }
                // Notları tekrar çekmek için fetchNotes çağrılabilir
                self?.fetchNotes()
            }
        }
        
        let navigationController = UINavigationController(rootViewController: noteDetailVC)
        navigationController.modalPresentationStyle = .fullScreen
        present(navigationController, animated: true)
    }
}


type here

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img