4
votes

I have written my own function to scroll text fields up when the keyboard shows up. In order to dismiss the keyboard by tapping away from the text field, I've created a UITapGestureRecognizer that takes care of resigning first responder on the text field when tapping away.

However, when selecting one of the entries in the auto completed table, didSelectRowAtIndexPath does not get called. Instead, it seems that the tap gesture recognizer is getting called and just resigns first responder.

I'm guessing there's some way to tell the tap gesture recognizer to keep passing the tap message on down to the UITableView, but I can't figure out what it is. There are only solutions for this in objc and posts from over 9 years ago on stack overflow , I was wondering if there was an updated solution to this! Thank you , Here is my code if you would like to see whats going on :

class ChatLogController : UIViewController, UITextFieldDelegate , UITableViewDelegate , UITableViewDataSource, UIGestureRecognizerDelegate, UIImagePickerControllerDelegate ,UINavigationControllerDelegate  {

var messages = [Message]()

var user : User?{
    didSet{
     observeMessages()
    }
}

@IBOutlet weak var tabelView: UITableView!
@IBOutlet weak var messageTextField: UITextField!
@IBOutlet weak var bottomViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var currentMessageRecieverImage: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()
    configureTableView()
  

    currentMessageRecieverImage.translatesAutoresizingMaskIntoConstraints = false;
    currentMessageRecieverImage.layer.cornerRadius = 30;
    currentMessageRecieverImage.layer.masksToBounds = true
    currentMessageRecieverImage.contentMode = .scaleAspectFill
    if let currentMessageRecieverUser = user{
        currentMessageRecieverImage.loadImageUsingCacheWithUrlString(urlString: currentMessageRecieverUser.picURL!);
        print(currentMessageRecieverUser.userName!)
    }
    
    
    tabelView.delegate = self;
    tabelView.dataSource = self;
    messageTextField.delegate = self;
    
    let dragAwayFromTextGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleTapAwayFromTextEdit))
    dragAwayFromTextGesture.direction = UISwipeGestureRecognizerDirection.down
    dragAwayFromTextGesture.delegate = self;
    tabelView.addGestureRecognizer(dragAwayFromTextGesture)
    
    let dragBackToMessages = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeBackToMessages))
    dragBackToMessages.direction = UISwipeGestureRecognizerDirection.right
    dragBackToMessages.delegate = self;
    tabelView.addGestureRecognizer(dragBackToMessages)
    
    let TapAwayFromTextEditTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapAwayFromTextEdit))
    tabelView.addGestureRecognizer(TapAwayFromTextEditTapGesture)
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

@objc func handleSwipeBackToMessages(){
    dismiss(animated: false, completion: nil)
}

func observeMessages(){
    guard let uid = Auth.auth().currentUser?.uid else{
        return;
    }
    // ref gets user that is logged in
    let ref = Database.database().reference().child("user-messages").child(uid)
    ref.observe(.childAdded) { (snapshot) in
        // gets needed messages
        let messageId = snapshot.key
        let messagesRef = Database.database().reference().child("messages").child(messageId)
        
        messagesRef.observe(.value, with: { (snapshot) in
            guard let dict = snapshot.value as? [String : AnyObject] else{
                return
            }
            let message = Message()
            message.imageUrl = dict["imageurl"] as? String 
            message.fromId = dict["fromid"] as? String
            message.text = dict["text"] as? String
            message.timestamp = dict["timestamp"] as? String
            message.toId = dict["toid"] as? String
            
            
            if message.chatPartnerId() == self.user?.toId{
            self.messages.append(message)
            }
            DispatchQueue.main.async {
                self.tabelView.reloadData()
            }
            
        })
    }
    
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return messages.count;
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellid" , for: indexPath) as! CustomChatTableViewCell;
    let gray = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
    let red = UIColor(red:1.00, green:0.22, blue:0.37, alpha:1.0)
 
    let message = messages[indexPath.item]
    
    if message.toId == user?.toId{
       
    cell.messageBackground.backgroundColor = red
        cell.messageLabel.textColor = UIColor.white
    }
    else{
        cell.messageBackground.backgroundColor = gray
        cell.messageLabel.textColor = UIColor.black
    }

    cell.messageLabel.text = message.text
    if message.imageUrl != nil{
        //print(message.imageUrl!)
       // cell.messageImageView.image = UIImage(named : "user.jpg")
        cell.messageImageView.loadImageUsingCacheWithUrlString(urlString: message.imageUrl!)
        //print(cell.messageImageView.image.debugDescription)
        cell.messageImageView.isHidden = false;
        cell.messageImageView.translatesAutoresizingMaskIntoConstraints = false;
        cell.messageImageView.contentMode = .scaleAspectFill
        cell.messageLabel.isHidden = true
        cell.messageBackground.isHidden = true;
    }
    else
    {
        cell.messageImageView.isHidden = true;
        cell.selectionStyle = UITableViewCellSelectionStyle.none
        
        cell.messageLabel.isHidden = false
        cell.messageBackground.isHidden = false;
    }
    return cell;
}

@objc func handleTapAwayFromTextEdit(){
    //print("handle tap away from text edit running ")
    messageTextField.endEditing(true)
    
}

@IBAction func backToMessageListPressed(_ sender: Any) {
    dismiss(animated: false, completion: nil)
}

@IBAction func infoButtonPressed(_ sender: Any) {

}
// saves text to fire base

@IBAction func sendButtonPressed(_ sender: Any) {
    
  handleSendMessageToDataBase()
}

func handleSendMessageToDataBase(){
    let ref = Database.database().reference().child("messages")
    // needed for making list in firebase for unique texts
    let childRef = ref.childByAutoId()
    
    if messageTextField.text == ""{
        return
    }
    
    if let message = messageTextField.text{
        
        
        
        let toID = user!.toId!
        let fromId = Auth.auth().currentUser!.uid
        let timeStamp : Int =  Int(Int(NSDate().timeIntervalSince1970))
        print(timeStamp)
        let values = ["text" : message , "toid" : toID , "timestamp" : "\(timeStamp)" , "fromid" : fromId]
        
        childRef.updateChildValues(values, withCompletionBlock: { (error, ref) in
            if error != nil{
                print(error!)
                return
            }
            
            let userMessagesref = Database.database().reference().child("user-messages").child(fromId)
            
            let messageID = childRef.key
            userMessagesref.updateChildValues([messageID: 1])
            
            let recipientUserMessageRef = Database.database().reference().child("user-messages").child(toID)
            recipientUserMessageRef.updateChildValues([messageID: 1])
        })
        
        
    }
 
    messageTextField.text = ""
    messageTextField.endEditing(true)
}

@IBAction func sendImageButtonPressed(_ sender: Any) {
    
    handleSendImage()
    
}

func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
    print("You selected cell #\(indexPath.row)!")
}


func handleSendImage(){
    
    let imagePickerController = UIImagePickerController()
    imagePickerController.delegate = self
    present(imagePickerController, animated: true, completion: nil)

}


func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    dismiss(animated: true, completion: nil)
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    var selectedImageFromPicker : UIImage?
    
    if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage{
        selectedImageFromPicker = editedImage
    }
    else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage{
        selectedImageFromPicker = originalImage
    }
    if let selectedImage = selectedImageFromPicker{
        uploadToFireBaseUsingSelectedImage(selectedImage : selectedImage)
    }
    
    dismiss(animated: true, completion: nil)
    
}

   private func uploadToFireBaseUsingSelectedImage(selectedImage : 
UIImage){
    let imageName = NSUUID().uuidString
    let refToStorage = Storage.storage().reference().child("message_images").child(imageName)

if let uploadData = UIImageJPEGRepresentation(selectedImage, 0.2){
    refToStorage.putData(uploadData, metadata: nil, completion: { (metaData, error) in
        if error != nil{
            print("failed to upload firebase image when sending in chatlogcontroller")
            print(error!)
            return
        }
        if let imageURL = metaData?.downloadURL()?.absoluteString{
            self.sendMessageWithImageURL(imageURL: imageURL)
        }
        
    })

}

}

private func sendMessageWithImageURL(imageURL : String){
    let ref = Database.database().reference().child("messages")
    // needed for making list in firebase for unique texts
    let childRef = ref.childByAutoId()
  
        let toID = user!.toId!
        let fromId = Auth.auth().currentUser!.uid
        let timeStamp : Int =  Int(Int(NSDate().timeIntervalSince1970))
        print(timeStamp)
        let values = ["imageurl" : imageURL , "toid" : toID , "timestamp" : "\(timeStamp)" , "fromid" : fromId]
        
        childRef.updateChildValues(values, withCompletionBlock: { (error, ref) in
            if error != nil{
                print(error!)
                return
            }
            
            let userMessagesref = Database.database().reference().child("user-messages").child(fromId)
            
            let messageID = childRef.key
            userMessagesref.updateChildValues([messageID: 1])
            
            let recipientUserMessageRef = Database.database().reference().child("user-messages").child(toID)
            recipientUserMessageRef.updateChildValues([messageID: 1])
        })
        

}



func textFieldDidBeginEditing(_ textField: UITextField) {
   
    UIView.animate(withDuration: 0.2, animations:{
        self.bottomViewHeightConstraint.constant = 308;
        self.view.layoutIfNeeded()
    })
    
}

func textFieldDidEndEditing(_ textField: UITextField) {
    UIView.animate(withDuration: 0.2, animations:{
        self.bottomViewHeightConstraint.constant = 50;
        self.view.layoutIfNeeded()

    })
}

func configureTableView()
{
    tabelView.delegate = self;
    tabelView.dataSource = self;
    tabelView.register(UINib(nibName: "MessageCell" , bundle : nil), forCellReuseIdentifier: "customMessageCell");
    tabelView.allowsSelection = true;
  
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    NotificationCenter.default.removeObserver(self);
    
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let message = messages[indexPath.item]
    
    if message.imageUrl != nil{
        return 200
    }
   
    
    
    return (CGFloat((message.text?.count)! + 70) )
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    
    return 80
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    handleSendMessageToDataBase()
    return true
}

}

2
remove tap gesture from views..! add line below your tap gesture: TapAwayFromTextEditTapGesture.cancelsTouchesInView = true Hope this helps!!Sachin Dobariya
In your case i don't think tap gesture is required.You can dismiss keyboard using tableviewdidselect delegate method.Vikky

2 Answers

6
votes

The solution is to set the property cancelsTouchesInView of your TapAwayFromTextEditTapGesture to false. This will cause it to pass on touches to the table view.

From the Apple documentation on this property (with my emphasis):

When this property is true (the default) and the receiver recognizes its gesture, the touches of that gesture that are pending are not delivered to the view and previously delivered touches are cancelled through a touchesCancelled(_:with:) message sent to the view. If a gesture recognizer doesn’t recognize its gesture or if the value of this property is false, the view receives all touches in the multi-touch sequence.

0
votes

Two other methods:

  1. In yourUITextViewDelegate, enable the gesture recogniser when starting editing and disable it when ending editing. You could also enable it in the keyboard event handling method and disable it in the method the gesture recogniser calls.

  2. Assign the gesture recogniser a delegate, and check if the keyboard is up when asked in the delegate if the gesture recogniser should receive the touch event.