问题
I have the follow code when I call to push the ViewController to detailed chat controller ( 1-to-1 chat). However, if I click too quickly, the view controller will be pushed twice. I see the animation twice. Could anyone point me where the mistake is? The code is from a Youtube lesson (Firebase Chat) from LBTA.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = messages[indexPath.row]
guard let chatPartnerId = message.chatPartnerId() else {return}
let ref = Database.database().reference().child("users").child(chatPartnerId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
let user = ChatUser(dictionary: dictionary)
user.id = chatPartnerId
self.showChatControllerForUser(user)
}, withCancel: nil)
}
func showChatControllerForUser(_ user: ChatUser) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.chatUser = user
navigationController?.pushViewController(chatLogController, animated: true)
}
回答1:
What you could do to avoid this issue is to disable the table view user interaction and reenable it after pushing to second view controller.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// add this:
tableView.isUserInteractionEnabled = false
let message = messages[indexPath.row]
guard let chatPartnerId = message.chatPartnerId() else {return}
let ref = Database.database().reference().child("users").child(chatPartnerId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
let user = ChatUser(dictionary: dictionary)
user.id = chatPartnerId
self.showChatControllerForUser(user)
}, withCancel: nil)
}
func showChatControllerForUser(_ user: ChatUser) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.chatUser = user
// edit this:
navigationController?.pushViewController(chatLogController, animated: true)
navigationController?.pushViewController(chatLogController, animated: true, completion: {
self.tableView.isUserInteractionEnabled = true
})
}
By default, pushViewController(_:animated:) does not has a completion handler, so as a workaround we could add the follwoing extention to achieve it:
extension UINavigationController {
public func pushViewController(
_ viewController: UIViewController,
animated: Bool,
completion: @escaping () -> Void)
{
pushViewController(viewController, animated: animated)
guard animated, let coordinator = transitionCoordinator else {
DispatchQueue.main.async { completion() }
return
}
coordinator.animate(alongsideTransition: nil) { _ in completion() }
}
}
Cited from: https://stackoverflow.com/a/33767837/5501940
回答2:
The problem is that you allow user to tap multiple times and this causes that view controller is pushed multiple times. You have to prevent it.
So, one option is creating one global variable isObserving
which doesn't allow observing multiple times.
var isObserving: Bool = false
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if !isObserving {
isObserving = true
...
ref.observeSingleEvent(of: .value, with: { snapshot in
...
self.isObserving = false
self.showChatControllerForUser(user)
})
}
}
Suggestions to better UX. If observing takes some time, you should let user know that there is something which needs time. So you can for example start and stop loading UIActivityIndicatorView
. Also you can forbid user to select cell multiple times by working with table view's isUserInteractionEnabled
.
回答3:
There's no mistake in your code, double tap on a cell its just something that Swift don't check.
You can try something like this to avoid this behavior:
override func viewWillAppear(){
super.viewWillAppear()
self.view.isUserInteractionEnabled = true // you need to enable user interaction if user comes back
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.view.isUserInteractionEnabled = false // this will prevent further taps
let message = messages[indexPath.row]
guard let chatPartnerId = message.chatPartnerId() else {return}
let ref = Database.database().reference().child("users").child(chatPartnerId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
let user = ChatUser(dictionary: dictionary)
user.id = chatPartnerId
self.showChatControllerForUser(user)
}, withCancel: nil)
}
func showChatControllerForUser(_ user: ChatUser) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.chatUser = user
navigationController?.pushViewController(chatLogController, animated: true)
}
Hope it helps you!
回答4:
The problem is that you're only pushing the ViewController after you get a server response, and the button can be tapped again before the response.
So you can either push the view controller immediately and then request data on the pushed view controller, or, prevent the "request" with a variable like @Robert Dresler did.
来源:https://stackoverflow.com/questions/54201067/pushviewcontroller-twice-when-i-double-click-too-quickly