Controlling what happens when the view background is tapped

I'm trying to control what happens when the view background is tapped. The view has a field, a button and a list collectionView.

When the user enters a name into the field, the button is enabled and when the button is tapped the name from the field is saved into a list collectionView.

What I want to happen: If the field isFirstResponder when the viewBackGround is tapped then I want the superView_Tapped() to be triggered.

If the collectionView is tapped it works fine. If the view background is tapped it works fine.

When the view backGround is tapped the field text is cleared and sone other things take place. See superView_Tapped() below.

What actually happens: The problem I'm having is that tapping the button also triggers the superView_Tapped(). So what happens is the field is emptied and a row is inserted into the collection view with no name.

The part of the reason is that the superView_Tapped() is fired first and then the button code fires after.

Is there a way to exclude the button tap from triggering the superView_Tapped()?

let tap = UITapGestureRecognizer(target: self, action: #selector(self.superView_Tapped))
tap.numberOfTapsRequired = 1
tap.delegate = self
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)

@objc func superView_Tapped()
{
    if insertFld.isFirstResponder
    {
        insertFld.resignFirstResponder()
        insertFld.text = ""
        insertButton.isEnabled = false
        // print("They emptied the fld when editing", gItemID)
        gItemID = 0
        insertButton.filled_Red_Back(title: K.Titles.add_Btn)
        theOriginal_Text = ""
    }
}

@IBAction func insertButton_Tapped(_ sender: UIButton)
{
    if current_Item_ID != 0
    {
        update()
    } else {
        insertRow()
    }
}

func update()
{
    let theItemName = insertFld.text
    
    do {
        try dbQueue_GRDB.write { db in
            try db.execute(sql: "UPDATE " + theTable + " SET Item_Name = :item_Name WHERE ItemID = :id",
                           arguments: ["item_Name": theItemName, "id": current_Item_ID])
        }
        
        applySnapshot()
        clean_Up()
        
        if ModelData.getTheConfirmation_Bool()
        {
            sendConfirmationAlert(theTitle: "Updated", theMessage: nil, buttonTitle: K.Titles.ok)
        }
        
    } catch {
        let theString = "\(error)"
        if theString.contains("UNIQUE constraint failed")
        {
            sendConfirmationAlert(theTitle: K.Titles.itemAlreadyExists, theMessage: nil, buttonTitle: K.Titles.ok)
        } else {
            print("Updating list failed! \(VC_String) \(error)")
        }
    }
}

func insertRow()
{
    let insertName = insertFld.text?.trimmingCharacters(in: .whitespaces)
    
    do {
        try dbQueue_GRDB.write { db in
            try db.execute(sql: "INSERT INTO " + theTable + " (Item_Name,Practice,Training,Practice_Log) VALUES (?,?,?,?)",
                           arguments: [insertName,"false","",""])
        }
        
        if ModelData.getTheConfirmation_Bool()
        {
            sendConfirmationAlert(theTitle: "Row Created", theMessage: nil, buttonTitle: K.Titles.ok)
        }
        
        applySnapshot()
        clean_Up()
        
    } catch {
        let theString = "\(error)"
        if theString.contains("UNIQUE constraint failed")
        {
            sendConfirmationAlert(theTitle: K.Titles.itemAlreadyExists, theMessage: nil, buttonTitle: K.Titles.ok)
        } else {
            print("Inserting to list failed! \(VC_String) \(error)")
        }
    }
}

Replies

I would try the following:

  • create a subclass of UIControl
  • create a View on top of background
  • change its class to the subclass (that's a subclass of UIView, so it's possible)

https://stackoverflow.com/questions/70052876/when-to-use-uicontrol-instead-of-uiview-for-implementing-custom-components-i

  • implement an IBAction

May be you'll have to do it in code and not storyboard. https://stackoverflow.com/questions/57597947/how-to-add-touch-event-for-custom-uicontrol-and-determine-the-touched-item-in-sw

Couple options, but one approach is to implement touchesBegan instead of adding a tap gesture:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if insertFld.isFirstResponder
    {
        insertFld.resignFirstResponder()
        insertFld.text = ""
        insertButton.isEnabled = false
        // print("They emptied the fld when editing", gItemID)
        gItemID = 0
        insertButton.filled_Red_Back(title: K.Titles.add_Btn)
        theOriginal_Text = ""
    }
}

Any UI element that responds to touch (the field, button, collection view, etc) will consume the touch, so touchesBegan will only be triggered when touching the view itself (or some other non-interactive element).