I am trying to make an iOS very similar to the game Lifeline
The game looks as though it's just a table view that has a row inserted after a delay.
I have managed to insert rows like this
for obj in array {
self.dialogue.append(obj)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}}
*Note the self.dialogue is my data source for my table view.
However I do not know how to add a delay in between adding a new row, for example
-> Insert row -> Wait 3 seconds-> Insert another row
How can I do this like it's done in the game?
you may implement separate method or block for inserting the rows and call it from inside your for loop with delay. e.g.
var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
for obj in array {
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.dialogue.append(obj)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
})
}}
Related
I'm building an app where rows containing messages are inserted at the end of a table
messages.append(message)
let indexPath:IndexPath = IndexPath(row:(messages.count - 1), section:0)
tableView.insertRows(at: [indexPath], with: .none)
I then scroll to the bottom of the table
DispatchQueue.main.async {
let indexPath = IndexPath(
row: self.numberOfRows(inSection: self.numberOfSections - 1) - 1,
section: self.numberOfSections - 1)
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
Like other messaging apps, I would like to modify this so that the autoscrolling only happens if you're already scrolled at the end of the table instead of every time a new message gets inserted.
I've tried several techniques like detecting if the last cell is full visible https://stackoverflow.com/a/9843146/784637, or detecting when scrolled to the bottom https://stackoverflow.com/a/39015301/784637.
However my issue is that because scrollToRow sets animated:true, if a new message comes in but the previous message which came a split second before is still being scrolled down to via scrollToRow, then the autoscrolling to newest message and subsequent messages doesn't occur - ex. the last cell won't be fully visible until the animation is complete, or detecting if you're scrolled to to the bottom will be false until the animation is complete.
Is there any way I can get around this without setting animated: false?
What I would do is insert the row in a batch operation to make use of its completion handler, which serializes the UI update. Then I would check to see which rows are visible to the user and if the last couple of rows are, I think the user is close enough to the bottom of the conversation to force a down scroll.
let lastRow = IndexPath(row: messages.count - 1, section: 0)
DispatchQueue.main.async {
self.tableView.performBatchUpdates({
self.tableView.insertRows(at: [lastRow], with: .bottom)
}, completion: { (finished) in
guard finished,
let visiblePaths = self.tableView.indexPathsForVisibleRows else {
return
}
if visiblePaths.contains([0, messages.count - 2]) || visiblePaths.contains([0, messages.count - 1]) { // last 2 rows are visible
DispatchQueue.main.async {
self.tableView.scrollToRow(at: lastRow, at: .bottom, animated: true)
}
}
})
}
I am trying to update my cell size when I click on particular cell.My table gets update perfectly in iOS 10.2 but my table flickers when I run my code on iOS 11 .I am also getting warning in iOS 11 that
-[UIApplication application state] must be used from main thread.
So, I have reload my table in dispatch queue but nothing happens. I have multi-level XIBs like I have cell in which I have a table which is registering another cell which expands on click.
Below is my code sample when height expands my main table flicker.
func expandMessageTable(height : CGFloat,expendedRow:Int){
self.expandMessageHeight = height
self.msgExpRow = expendedRow
let row = cellMenuArr.index(of:"MessageXIB")
let path = IndexPath(row: row!, section: 0)
print(path)
homeTblView.reloadRows(at: [path], with: .fade)
}
Below is the fine working code i am using to reload cells -
DispatchQueue.main.async(execute: {
UIView.performWithoutAnimation {
self.tableView?.beginUpdates()
let contentOffset = self.tableView?.contentOffset
self.tableView?.reloadRows(at: [IndexPath(row: j, section: 0)], with: .automatic)
self.tableView?.setContentOffset(contentOffset!, animated: false)
self.tableView?.endUpdates()
}
})
Hope it helps.
I have a table view which received data from a real-time database. These data are added from the bottom of the table view and so on this table view has to scroll down itself to show new data.
I've found a method to do it however I'm not satisfied because the scroll always start from the top of the list. Not very beautiful.
Here is the code of this method :
func tableViewScrollToBottom(animated: Bool) {
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
let numberOfSections = self.clientTable.numberOfSections
let numberOfRows = self.clientTable.numberOfRowsInSection(numberOfSections-1)
if numberOfRows > 0 {
let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
self.clientTable.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
}
})
}`
Is there a way to modify this method in order to scroll only from the previous position ?
The issue is probably how the rows were inserted into the table. For example, if you add rows to the end using something like this, you get a very smooth UI:
#IBAction func didTapAddButton(sender: AnyObject) {
let count = objects.count
var indexPaths = [NSIndexPath]()
// add two rows to my model that `UITableViewDataSource` methods reference;
// also build array of new `NSIndexPath` references
for row in count ..< count + 2 {
objects.append("New row \(row)")
indexPaths.append(NSIndexPath(forRow: row, inSection: 0))
}
// now insert and scroll
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .None)
tableView.scrollToRowAtIndexPath(indexPaths.last!, atScrollPosition: .Bottom, animated: true)
}
Note, I don't reload the table, but rather call insertRowsAtIndexPaths. And I turned off the animation because I know they're off screen, and I'll then scroll to that row.
I'm trying to insert a row after another row deletion animation is completed. I've been trying doing the following:
tableView.beginUpdates()
CATransaction.begin()
CATransaction.setCompletionBlock {
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
}
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
CATransaction.commit()
tableView.endUpdates
This gave me the usual assertion failure when the count of rows is not the same as it's been expecting.
Then I've tried using an UIView animation with a completion block:
tableView.beginUpdates()
func animations() {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
}
func completion() {
if count == self.payments.count && self.payments.isEmpty { insert() }
}
UIView.animateWithDuration(0.5, animations: { animations() }) { _ in completion() }
tableView.endUpdates()
Both attempts is giving me the same error. Is it possible or should I look into custom animation for inserting / deleting tableview rows?
Edit:
I managed to make it work by moving tableView.endUpdates() to the completion block. But the insertion animation still animates at the same time when the row is being deleted.
Is there another way of doing this?
if you know how much time does your animation takes to complete just add this function to wait for a given amount of time before executing some code:
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
Usage:
delay(seconds: 0.5) {
//code to be delayed "0.5 sec"
}
I think you're putting code in wrong position
It should be like this:
CATransaction.begin()
CATransaction.setCompletionBlock {
// animation has finished
}
tableView.beginUpdates()
// do some work
tableView endUpdates()
CATransaction.commit()
Reference: How to detect that animation has ended on UITableView beginUpdates/endUpdates?
I have an app that has a table view. Every minute or so I want the first row of the UITableView to disappear. It would be nice to have it animate off. I am assuming I need to use something like this:
let myNSIndexPath = [NSIndexPath(forRow: 0, inSection: 0)]
mainTableView.deleteRowsAtIndexPaths(myNSIndexPath, withRowAnimation:UITableViewRowAnimation.Fade)
This doesn't work. I'm not sure exactly how to use IndexPath correctly. My table only has one section and I always want the cell at Index 0 to be removed. What must to do to get this working correctly?
One way
Use removeAtIndex
yourarray.removeAtIndex(indexPath.row)
And don't forget to reload the table view
tableView.reloadData()
Second Way
yourIndexPath = [NSIndexPath(forRow: 0, inSection: 0)]
yourtable.beginUpdates() //if you are performing more than one operation use this
yourarray.removeObjectAtIndex(myNSIndexPath.row)
yourtable.deleteRowsAtIndexPaths(NSArray(object: yourIndexPath), withRowAnimation: UITableViewRowAnimation.Left)
yourtable.endUpdates() //if you are performing more than one operation use this
Took help from
#Rmaddy
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/
You can delete the first cell like this.
One way
let myNSIndexPath = [NSIndexPath(forRow: 0, inSection: 0)]
self.tableView.beginUpdates()
self.arrayData.removeObjectAtIndex(myNSIndexPath.row) // also remove an array object if exists.
self.tableView.deleteRowsAtIndexPaths(NSArray(object: myNSIndexPath), withRowAnimation: UITableViewRowAnimation.Left)
self.tableView.endUpdates()
Second way
yourArr.removeAtIndex(0)
self.tableView.reloadData()
in SWIFT 4
You can delete the row like this.
let indexPathRow=sender.tag
let indexPath = IndexPath(row: indexPathRow, section: 0)
arrayList.remove(at: indexPathRow)
mainTableView.deleteRows(at: [indexPath], with: .none)