I'm trying to insert number more cells in the table view when the user reaches the bottom of the table. the insert is working fine, but a weird animation is happening when scrolling up.
I tried to add beginUpdates() and endUpdates() before and after the insert() method. but this didn't help. I tried to add tableView.contentInsetAdjustmentBehavior = .never in viewDidLoad and it didn't help.
the code I tried is:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if tableView.contentOffset.y >= (tableView.contentSize.height - tableView.frame.size.height) {
loadMoreCells()
}
func loadMoreCells() {
ServerRequests.getDataServerRequest(id: provider.ID, page: page) { (foundEpisodes) in
if foundEpisodes.count > 0 {
let previous = self.episodes.count
self.episodes.insert(contentsOf: foundEpisodes, at: self.episodes.count)
self.isViewOpen.insert(contentsOf: [Bool](repeatElement(false, count: foundEpisodes.count)), at: self.isViewOpen.count)
var indexPathsToBeInserted = [IndexPath]()
for i in previous..<self.episodes.count{
indexPathsToBeInserted.append(IndexPath(row: i, section: 0))
}
self.tableView.insertRows(at: indexPathsToBeInserted, with: UITableViewRowAnimation.none)
}
}
}
Am I calling the insert in the wrong place? or what is the wrong thing I am doing ?
UPDATE
in viewDidLoad:
tableView.register(UINib(nibName: NIB_NAME, bundle: nil), forCellReuseIdentifier: NIB_IDENTIFIRE)
in cellForRow:
let cell = tableView.dequeueReusableCell(withIdentifier: NIB_IDENTIFIRE, for: indexPath) as! EpisodePerProviderTableViewCell
cell.setUpCell(tableView: tableView, episode: episodes[indexPath.row], indexPath: indexPath, isViewOpen: isViewOpen)
return cell
the setUpCell function is:
unc setUpCell(tableView: UITableView, episode: Episode, indexPath: IndexPath, isViewOpen: [Bool]) {
isMenuVisible = false
menuView.isHidden = true
if (isViewOpen[indexPath.row]){
descriptionLabel.numberOfLines = 100
moreLessLabel.text = "Less"
}else{
descriptionLabel.numberOfLines = 2
moreLessLabel.text = "More"
}
tableView.beginUpdates()
tableView.endUpdates()
self.isViewOpen = isViewOpen
self.indexPath = indexPath
self.tableView = tableView
self.episode = episode
arrowButton.transform = .identity
//set up cell content
if let myDate = dateFormatter.date(from: episode.episodePostDate) {
postedDate.text = dateFormatter.timeSince(from: myDate as NSDate)
}
episodeImage.windless.start()
episodeImage.sd_setImage(with: URL(string: episode.episodeImageUrl ?? ""), placeholderImage: UIImage(named: "FalloundPlayerLogo")) { (providerImage, error, SDImageCacheType, url) in
self.episodeImage.windless.end()
}
title.text = episode.episodeName
durationLabel.text = "".formatted(time: Float(episode.episodeDuration), withoutSec: true)
descriptionLabel.text = episode.episodeDescription
}
I think the problem is because loadMoreCells is called too many times.
try using this check instead of willDisplay:
if indexPath.row == yourArrayOfCells.count - 1{
loadMoreCells()
}
UPDATE
So I have some how of the same issue, after some digging I found this solution for myself :
//conform to UIScrollViewDelegate
let threshold : CGFloat = 10.0 // threshold from bottom of tableView
var isLoadingMore = false //checks if API session ended
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if !isLoadingMore && (maximumOffset - contentOffset) <= threshold {
self.isLoadingMore = true
DispatchQueue.main.async {
loadMoreCells()
self.isLoadingMore = false
}
}
}
If the API request happening too fast and the load looks funny you can add a delay :
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
loadMoreCells()
self.isLoadingMore = false
}
If it won't work I will assume that the issue is not in the code you provided.
UPDATE-2
Those 2 funcs in setUpCell() aren't neccessery :
tableView.beginUpdates()
tableView.endUpdates()
Hope this helps.
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.insertRows(at: indexPathsToBeInserted, with: UITableViewRowAnimation.none)
self.tableView.endUpdates()
UIView.setAnimationsEnabled(true)
Related
I have a UITableView which loads images from a firebase database. Each cell in the table contains three pictures. The firestore query loads three documents at a time, while the table view paginates when the users scroll to the bottom. The issue I am having is that as I scroll the table view stutters every time it reaches a new cell. Each cell takes up a little more than the full screen. Here is an example of what I am trying to describe: https://imgur.com/a/xRB6gZg
Here is the code that is producing these issues:
func paginate(){
postQuery = postQuery.start(afterDocument: documents.last!)
self.loadPosts()
}
//queries Firestore and loads into postArray
func loadPosts() {
if let blockedArray = userDefaults.array(forKey: blockKey) as? [String]{
blockedUsers = blockedArray
}
postQuery.getDocuments{ [weak self](querySnapshot, error) in
self!.q.async{
if let err = error {
print(err)
}else{
var postsTemp = self?.postArray
for doc in querySnapshot!.documents{
self?.documents += [doc]
let post = self!.createPost(doc)
if(!self!.postArray.contains(post) && !self!.blockedUsers.contains(post.uid)){
postsTemp?.append(post)
}
DispatchQueue.main.async {
self!.postArray = postsTemp!
self!.tableView.reloadData()
self!.isNewDataLoading = false
}
}
self!.loadedFirst = true
}
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if postArray.count == 0{
return 1
}else{
return postArray.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var post: PostStruct
var peopleUserIsFollowing: [String] = []
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
cell.delegate = self
if postArray.count == 0 {
let instructions = cell.textLabel
instructions?.text = "Press the camera to start Piking!"
instructions?.textAlignment = .center
clearPosts(cell)
}else {
post = postArray[indexPath.row]
if let leftPostArray = userDefaults.array(forKey: fbLeftKey) as? [String]{
votedLeftPosts = leftPostArray
}
if let rightPostArray = userDefaults.array(forKey: fbRightKey) as? [String]{
votedRightPosts = rightPostArray
}
let firstReference = storageRef.child(post.firstImageUrl)
let secondReference = storageRef.child(post.secondImageUrl)
//For FriendsTableView query
let db = Firestore.firestore()
let followingReference = db.collection("following")
.document(currentUser!)
.collection("UserIsFollowing")
followingReference.getDocuments(){(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
peopleUserIsFollowing.append(document.documentID)
}
}
}
//Fill in labels and imageViews
cell.timer = createTimer(post, cell)
cell.firstImageView.sd_setImage(with: firstReference)
cell.secondImageView.sd_setImage(with: secondReference)
cell.leftTitle.text = post.firstTitle
cell.rightTitle.text = post.secondTitle
cell.postDescription.text = post.postDescription + "\(indexPath)"
if post.userPic == "" {
userPic =
"https://firebasestorage.googleapis.com/v0/b/pikit-7e40e4.appspot.com/o/Default%20Profile%20Pic.png?alt=media&token=2bc88382-2ad3-4eb8-8163-dcddf391c666"
} else{
userPic = post.userPic
}
let url = URL(string: userPic)
let data = try? Data(contentsOf: url!)
cell.profilePic.image = UIImage(data: data!)
let votesCollection = db.collection("votes").document(post.postID)
getCount(ref: votesCollection, cell: cell)
if(post.uid != currentUser){
cell.userName.text = post.poster
}else{
cell.userName.text = "Me"
cell.tapLeft.isEnabled = false
cell.tapRight.isEnabled = false
}
cell.textLabel?.text = ""
if(post.poster == Auth.auth().currentUser!.uid || post.endDate - Int(NSDate().timeIntervalSince1970) <= 0){
cell.tapRight.isEnabled = false
cell.tapLeft.isEnabled = false
cell.firstImageView.layer.borderWidth = 0
cell.secondImageView.layer.borderWidth = 0
}
else if(votedRightPosts.contains(post.firstImageUrl)){
cell.secondImageView.layer.borderColor = UIColor.green.cgColor
cell.secondImageView.layer.borderWidth = 4
cell.firstImageView.layer.borderWidth = 0
cell.tapRight.isEnabled = false
cell.tapLeft.isEnabled = true
}
else if (votedLeftPosts.contains(post.firstImageUrl)){
cell.firstImageView.layer.borderColor = UIColor.green.cgColor
cell.firstImageView.layer.borderWidth = 4
cell.secondImageView.layer.borderWidth = 0
cell.tapLeft.isEnabled = false
cell.tapRight.isEnabled = true
}
}
return cell
}
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let postCell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
clearPosts(postCell)
postCell.timer?.invalidate()
postCell.timer = nil
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//Bottom Refresh
if scrollView == tableView{
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height)
{
if !isNewDataLoading{
isNewDataLoading = true
paginate()
}
}
}
}
I have tried adjusting what didEndDisplaying does, such as clearing cells/ not clearing cells, but that had no effect. I have also tried changing around where paginate is called but this seems to be the best way. I am not sure where I went wrong. I have also noticed in the Xcode debugger that the memory usage of the app steadily rises as the table view is scrolled up and down, but never seems to go down.
In general, you have two options to fix this problem. That's a lot of code to parse through, so I can't give you a code sample, but the answers are either:
Pre-fetching
When you scroll to item 2, kick off the fetch for items 4,5,6 (since you fetch 3 at a time) before you scroll down that far.
Also... you might consider fetching more than 3 at a time. Like... 50, or 100. Modern iOS devices have lots of memory. No real reason I can think of to limit it to so few.
Placeholders
Build your layout so it gives placeholder data and then kick off the fetch asynchronously to update the on-screen layout with the real data.
Either way is going to require you to restructure your code a bit. My intuition says that pre-fetching is going to be easier for you.
I have a tableView with chat bubbles.
Those bubbles are shortened if the character count is more than 250
If a user clicks on a bubble
The previous selection gets deselected (shortened)
The new selection expands and reveals the whole content
The new selections top constraint changes (from 0 to 4)
What I would like to achieve?
If a long bubble is selected already, but the user selects another bubble, I want the tableView to scroll to the position of the new selected bubble.
I'll share a video about it
Without this scrolling, the contentOffset remains the same and it looks bad.
(In the video: on the right)
Video:
Right: without the mentioned scrolling
Left: with scrolling
https://youtu.be/_-peZycZEAE
Here comes the problem:
On the left, you can notice that it is glitchy.
Random ghost cells are appearing for no reason.
Sometimes it even messes up the height of some bubbles (not in the video)
Why is it so?
Code:
func bubbleTappedHandler(sender: UITapGestureRecognizer) {
let touchPoint = sender.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
if indexPath == currentSelectedIndexPath {
// Selected bubble is tapped, deselect it
self.selectDeselectBubbles(deselect: indexPath)
} else {
if (currentSelectedIndexPath != nil){
// Deselect old bubble, select new one
self.selectDeselectBubbles(select: indexPath, deselect: currentSelectedIndexPath)
} else {
// Select bubble
self.selectDeselectBubbles(select: indexPath)
}
}
}
}
func selectDeselectBubbles(select: IndexPath? = nil, deselect: IndexPath? = nil){
var deselectCell : WorldMessageCell?
var selectCell : WorldMessageCell?
if let deselect = deselect {
deselectCell = tableView.cellForRow(at: deselect) as? WorldMessageCell
}
if let select = select {
selectCell = tableView.cellForRow(at: select) as? WorldMessageCell
}
// Deselect Main
if let deselect = deselect, let deselectCell = deselectCell {
tableView.deselectRow(at: deselect, animated: false)
currentSelectedIndexPath = nil
// Update text
deselectCell.messageLabel.text = self.dataSource[deselect.row].message.shortened()
}
// Select Main
if let select = select, let selectCell = selectCell {
tableView.selectRow(at: select, animated: true, scrollPosition: .none)
currentSelectedIndexPath = select
// Update text
deselectCell.messageLabel.text = self.dataSource[select.row].message.full()
}
UIView.animate(withDuration: appSettings.defaultAnimationSpeed) {
// Deselect Constraint changes
if let deselect = deselect, let deselectCell = deselectCell {
// Constarint change
deselectCell.nickNameButtonTopConstraint.constant = 0
deselectCell.timeLabel.alpha = 0.0
deselectCell.layoutIfNeeded()
}
// Select Constraint changes
if let select = select, let selectCell = selectCell {
// Constarint change
selectCell.nickNameButtonTopConstraint.constant = 4
selectCell.timeLabel.alpha = 1.0
selectCell.layoutIfNeeded()
}
}
self.tableView.beginUpdates()
self.tableView.endUpdates()
UIView.animate(withDuration: appSettings.defaultAnimationSpeed) {
if let select = select, deselect != nil, self.tableView.cellForRow(at: deselect!) == nil && deselectCell != nil {
// If deselected row is not anymore on screen
// but was before the collapsing,
// then scroll to new selected row
self.tableView.scrollToRow(at: select, at: .top, animated: false)
}
}
}
Update 1: Added Github project
Link: https://github.com/krptia/test2
I made a small version of my app, so that you can see and test what my problem is. I would be so thankful if someone could help to solve this issue! :c
First let's define what we mean by "without scrolling" - we mean that the cells more of less stay the same. So we want to find a cell that we want to be the anchor cell. From before the changes to after the changes the distances from the cell's top to the top of the screen is the same.
var indexPathAnchorPoint: IndexPath?
var offsetAnchorPoint: CGFloat?
func findHighestCellThatStartsInFrame() -> UITableViewCell? {
return self.tableView.visibleCells.filter {
$0.frame.origin.y >= self.tableView.contentOffset.y
}.sorted {
$0.frame.origin.y > $1.frame.origin.y
}.first
}
func setAnchorPoint() {
self.indexPathAnchorPoint = nil;
self.offsetAnchorPoint = nil;
if let cell = self.findHighestCellThatStartsInFrame() {
self.offsetAnchorPoint = cell.frame.origin.y - self.tableView.contentOffset.y
self.indexPathAnchorPoint = self.tableView.indexPath(for: cell)
}
}
we call this before we start doing stuff.
func bubbleTappedHandler(sender: UITapGestureRecognizer) {
self.setAnchorPoint()
....
Next we need to set the content offset after we are doing doing our changes so that the cell moves back to where it is suppose to.
func scrollToAnchorPoint() {
if let indexPath = indexPathAnchorPoint, let offset = offsetAnchorPoint {
let rect = self.tableView.rectForRow(at: indexPath)
let contentOffset = rect.origin.y - offset
self.tableView.setContentOffset(CGPoint.init(x: 0, y: contentOffset), animated: false)
}
}
Next we call it after we are done doing our changes.
self.tableView.beginUpdates()
self.tableView.endUpdates()
self.tableView.layoutSubviews()
self.scrollToAnchorPoint()
The animation can be a little strange because there is a lot going on at the same time. We are changing the content offset and the sizes of the cell at the same time, but if you place your finger next to the first cell that top is visible you will see it ends up in the correct place.
Try using this willDisplay api on UITableViewDelegate
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if (indexPath == currentSelectedIndexPath) {
// logic to expand your cell
// you don't need to animate it since still not visible
}
}
Replace your code for bubbleTappedHandler with this and run and check:
func bubbleTappedHandler(sender: UITapGestureRecognizer) {
let touchPoint = sender.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
if indexPath == currentSelectedIndexPath {
currentSelectedIndexPath = nil
tableView.reloadRows(at: [indexPath], with: .automatic)
} else {
if (currentSelectedIndexPath != nil){
if let prevSelectedIndexPath = currentSelectedIndexPath {
currentSelectedIndexPath = indexPath
tableView.reloadRows(at: [prevSelectedIndexPath, indexPath], with: .automatic)
}
} else {
currentSelectedIndexPath = indexPath
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { [weak self] in
let currentIndexPath = self?.currentSelectedIndexPath ?? indexPath
self?.tableView.scrollToRow(at: currentIndexPath, at: .top, animated: false)
})
}
}
You can use tableView?.scrollToRow. For example
let newIndexPath = IndexPath(row: 0, section: 0) // put your selected indexpath and section here
yourTableViewName?.scrollToRow(at: newIndexPath, at: UITableViewScrollPosition.top, animated: true) // give the desired scroll position to tableView
Available scroll positions are .none, .top, .middle, .bottom
This glitch is because of a small mistake. Just do these 2 things and the issue will be resolved.
Set tableView's rowHeight and estimatedRowHeight in `viewDidLoad
tableView.estimatedRowHeight = 316 // Value taken from your project. This may be something else
tableView.rowHeight = UITableView.automaticDimension
Remove the estimatedHeightForRowAt and heightForRowAt delegate methods from your code.
I am trying to implement pagination in table view. so when fetching data from server, I want to get 10 data per request.
so the first time user opens the view, it will fetch 10 data, after the user scrolls to the 10th row, it will send request to get the data 11st to 20th.
but after fetching that 11-20th data and reloading the table view, it seems the table view drag down and it shows the last row (i.e the 20th row).
I want after the 10th row --> fetching data --> show the 11st row of the table view
so it will give smooth scrolling and good user experience
here is the simplified code i use. Thanks in advance
let numberOfDocumentsPerQuery = 5
var fetchingMore = false
var recommendedEventList = [EventKM]()
var lastDocumentFromQuery : QueryDocumentSnapshot?
extension HomeVC : UITableViewDelegate, UITableViewDataSource {
//MARK: - Table View Methods
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recommendedEventList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCell", for: indexPath) as! HomeCell
cell.eventData = recommendedEventList[indexPath.row]
return cell
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// to fetch more events if it comes to the bottom of table view
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
if maximumOffset - currentOffset <= 10.0 {
if !fetchingMore {
getRecommendedEvents()
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "toEventDetailVC", sender: nil)
}
}
func getRecommendedEventsFromBeginning() {
EventKM.observeRecommendedEvents (
selectedCity: selectedCity,
limit: numberOfDocumentsPerQuery) { (recommendedList, listener, lastDocument) in
if let events = recommendedList {
self.recommendedEventList = events
self.tableView.reloadData()
self.lastDocumentFromQuery = lastDocument
self.recommendedListener = listener
self.fetchingMore = false
} else {
self.fetchingMore = true // to stop fetching data
}
}
}
You can have a property to record the row that scroll to.
var rowForScroll = 1
every time you loaded data, add the rowForScroll
rowForScroll = rowForScroll + 10
let ip = NSIndexPath(forRow: rowForScroll, inSection: 0)
self.tableView.scrollToRowAtIndexPath(ip, atScrollPosition: .bottom, animated: true)
// Add your custom object.
recommendedEventList.add(customObject)
let numberOfRowsBeforeAPIRequest = tableView.numberOfRows(inSection: section)
let indexPath:IndexPath = IndexPath(row:(recommendedEventList.count - 1), section:0)
tableView.insertRows(at:[indexPath], with: .none)
// Do Validate if the actual indexpath exists or not, else will crash.
let ip = NSIndexPath(forRow: numberOfRowsBeforeAPIRequest + 1, inSection: 0)
tableView.scrollToRowAtIndexPath(ip, atScrollPosition: .bottom, animated: true)
Just putting what I am trying to do above.
Saving the number of Rows before reloading.
Adding new rows after the last row of the section.
Then scrolling 1 row down in the tableview.
I have a FollowVC and FollowCell Setup with collection View. I can display all the datas correctly into my uIcollection view cell using the following code with no problem.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("FollowCell", forIndexPath: indexPath) as? FollowCell {
let post = posts[indexPath.row]
cell.configureCell(post, img: img)
if cell.selected == true {
cell.checkImg.hidden = false
} else {
cell.checkImg.hidden = true
}
return cell
}
}
Note that I could also select and deselect multiple images using the following code
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if deletePressed == true {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FollowCell
cell.checkImg.hidden = false
} else {
let post = posts[indexPath.row]
performSegueWithIdentifier(SEGUE_FOLLOW_TO_COMMENTVC, sender: post)
}
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FollowCell
cell.checkImg.hidden = true
}
When In "Select" mode, I can perform the selction of each cell and a check mark will be displayed on the cell. However, what I want to do is to have a cancel buttom to disable all the selected cell and removing the checkImg.
I have tried
func clearSelection() {
print("ClearSelection posts.count = \(posts.count)")
for item in 0...posts.count - 1 {
let indexP = NSIndexPath(forItem: item, inSection: 0)
followCollectionView.deselectItemAtIndexPath(indexP, animated: true)
let cell = followCollectionView.cellForItemAtIndexPath(indexP) as! FollowCell
cell.checkImg.hidden = true
}
}
The program crashes here giving me a fatal error: Unexpectedly found nil while unwrapping an optional error at
let cell = followCollectionView.cellForItemAtIndexPath(indexP) as! FollowCell
I dont know why it is having trouble unwrapping the cell to be my FollowCell which contains an instance of the checkImg. I already used it before in a similar situation in didSelectItemAtIndexPath and it seems to work?
Thanks,
Not all of the selected cells may be on screen at the point when you are clearing the selection status, so collectionView.cellForItemAtIndexPath(indexPath) may return nil. Since you have a force downcast you will get an exception in this case.
You need to modify your code to handle the potential nil condition but you can also make your code more efficient by using the indexPathsForSelectedItems property of UICollectionView
let selectedItems = followCollectionView.indexPathsForSelectedItems
for (indexPath in selectedItems) {
followCollectionView.deselectItemAtIndexPath(indexPath, animated:true)
if let cell = followCollectionView.cellForItemAtIndexPath(indexPath) as? FollowCell {
cell.checkImg.hidden = true
}
}
Using Extension in Swift 4
extension UICollectionView {
func deselectAllItems(animated: Bool) {
guard let selectedItems = indexPathsForSelectedItems else { return }
for indexPath in selectedItems { deselectItem(at: indexPath, animated: animated) }
}
}
To simplify further, you could just do
followCollectionView.allowsSelection = false
followCollectionView.allowsSelection = true
This will in fact correctly clear your followCollectionView.indexPathsForSelectedItems even though it feels very wrong.
collectionView.indexPathsForSelectedItems?
.forEach { collectionView.deselectItem(at: $0, animated: false) }
This answer may be useful in swift 4.2
let selectedItems = followCollectionView.indexPathsForSelectedItems
for (value in selectedItems) {
followCollectionView.deselectItemAtIndexPath(value, animated:true)
if let cell = followCollectionView.cellForItemAtIndexPath(value) as? FollowCell {
cell.checkImg.hidden = true
}
}
I got it solved easier by doing this:
tableView.selectRow(at: nil, animated: true, scrollPosition: UITableView.ScrollPosition.top)
I have a PFQueryTableViewController in which I am trying to learn some tableView interactions.
What I currently have: The tableView cell increases its height on a tap didSelectRowAtIndexPath or I can basically segue cell related data to next viewController using performSegueWithIdentifier together with prepareForSegue using a single tap.
In the below code, if I comment the "Tap code to increases height of tableView cell" code, I can detect double tap. Other wise the single tap increases the height as the code is inside didSelect block.
What I need:
On Single tap, cell height increases or decrease. While the cell height is increased, if I double tap, it should segue cell data to next UIViewController.
If not, the single tap should increase or decrease the height. And double tap should segue cell data to next UIViewController
Code is as below:
var selectedCellIndexPath: NSIndexPath?
var SelectedCellHeight = CGFloat() // currently set to 480.0 tableView height
var UnselectedCellHeight = CGFloat() // currently set to 300.0 tableView unselected hight
....
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
return SelectedCellHeight
}
}
return UnselectedCellHeight
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! DataViewTableViewCell!
if cell == nil {
cell = DataViewTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
}
// Below is the double Tap gesture recognizer code in which
// The single tap is working, double tap is quite difficult to get
// as the cell height increases on single tap of the cell
let doubleTap = UITapGestureRecognizer()
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
tableView.addGestureRecognizer(doubleTap)
let singleTap = UITapGestureRecognizer()
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
singleTap.requireGestureRecognizerToFail(doubleTap)
tableView.addGestureRecognizer(singleTap)
// Tap code to increases height of tableView cell
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
self.selectedCellIndexPath = nil
cell.postComment.fadeIn()
cell.postCreatorPicture.alpha = 1
cell.postDate.fadeIn()
// cell.postTime.fadeIn()
cell.postCreator.fadeIn()
} else {
self.selectedCellIndexPath = indexPath
}
} else {
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
func doubleTap() {
print("Double Tap")
}
func singleTap() {
print("Single Tap")
}
The other way I can make it work is, if I can get NSIndexPath of selected cell outside of didSelectRowAtIndexPath code, I can basically use the double tap to perform if else to get the required action. Can you get NSIndexPath outside the tableView default blocks?
Got it working!
First: Define var customNSIndexPath = NSIndexPath() and add the below code inside the didSelectRowAtIndexPath code block.
customNSIndexPath = indexPath
print(customNSIndexPath)
Second: Remove "Tap code to increases height of tableView cell" code from didSelectRowAtIndexPath and add to the singleTap() function. And perform segue using the doubleTap().
func doubleTap() {
print("Double Tap")
performSegueWithIdentifier("MainToPost", sender: self)
}
func singleTap() {
print("Single Tap")
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! DataViewTableViewCell!
if cell == nil {
cell = DataViewTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
}
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == customNSIndexPath {
self.selectedCellIndexPath = nil
} else {
self.selectedCellIndexPath = customNSIndexPath
}
} else {
selectedCellIndexPath = customNSIndexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
This is how you screw up working late without sleeping. It was the easiest thing to do. Thanks
Note: If someone has better solution or this one is wrong in some xyz case, please do comment. If yours is better it would really help others.
var lastClick: TimeInterval = 0.0
var lastIndexPath: IndexPath? = nil
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let now: TimeInterval = Date().timeIntervalSince1970
if (now - lastClick < 0.3) && (lastIndexPath?.row == indexPath.row ) {
print("Double Tap!")
} else {
print("Single Tap!")
}
lastClick = now
lastIndexPath = indexPath
}