scrollToRowAtIndexPath issue with userInteraction - ios

I'm currently using automatic scroll to bottom in UITableView, but there is a bug to solve:
If I press in any location of my UITableView before the scroll is executed that stop to work. (I cannot scroll to bottom before the view is displayed because I retrieve async data)
So the first thing I thought is tho disable the userInteraction like this:
override func viewDidLoad() {
super.viewDidLoad()
self.view.userInteractionEnabled = false
...
}
And to unlock it here:
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.tableView.numberOfSections
let numberOfRows = self.tableView.numberOfRowsInSection(numberOfSections-1)
if numberOfRows > 0 {
let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.None, animated: animated)
}
self.view.userInteractionEnabled = true
})
}
But (it's really strange) is not working, yes the view is disabled (I've tried even to lock the UITableView) and I can not scroll the UITableView but it seems that it can retrieve anyway the touch.

Related

Long press to slide show images

I have a CollectionView, the cell in CollectionView has size equal to the screen (CollectionView has paging enable mode).
I want to press long on the screen, then the CollectionView will scroll to the next cell.
For example:
I need 1 second to make the CollectionView scroll to the next cell,
and I press for 2,5 seconds.
The begining time: I am starting long press on the screen and the collection view is now on the first cell.
After the first second: It will scroll to the second cell.
After the second second: It will scroll to the third cell.
The last half second: It still stand on the third cell (because half second is not enough time to make the collection view scroll to the next cell).
I have added the UILongPressGestureRecognizer to the cell and I have tried like this:
func handleLongPress(longGesture: UILongPressGestureRecognizer) {
if longGesture.state == .Ended {
let p = longGesture.locationInView(self.collectionView)
let indexPath = self.collectionView.indexPathForItemAtPoint(p)
if let indexPath = indexPath {
let row = indexPath.row + 1
let section = indexPath.section
if row < self.photoData.count {
self.collectionView.selectItemAtIndexPath(NSIndexPath(forRow: row, inSection: section), animated: true, scrollPosition: .Right)
}
print(indexPath.row)
} else {
print("Could not find index path")
}
}
}
But I always have to END the long gesture to make the collection view scroll.
What you seem to want is something that kicks off a timer that fires every 1 second while the finger is down. I'd probably make a function:
func scrollCell() {
if (longPressActive) {
//scroll code
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
scrollCell() // function calls itself after a second
})
}
}
That you could control in your handleLongPress code:
func handleLongPress(longGesture: UILongPressGestureRecognizer) {
if longGesture.state == .Began {
longPressActive = true
scrollCell()
} else if longGesture.state == .Ended || longGesture.state == .Canceled {
longPressActive = false
}
}
So, when the long press gesture first fires, it sets a bool (longPressActive), and then calls the scroll function. When the scroll function completes, it calls itself again. If the gesture ever finalizes, it will clear the longPressActive bool, so if the timer fires, the bool will be false and it won't scroll.
More ideally I'd probably not use a long press gesture recognizer and just track the touches myself, as I could reference the touch and check its state instead of use a boolean. Also, there is probably a fun bug involved with the dispatch when it goes into the background.
Here is the way I have tried:
First, I add these properties to my Controller:
var counter = 0
var timer = NSTimer()
var currentIndexPath: NSIndexPath?
Then I count up the counter whenever longGesture.state == .Began
func handleLongPress(longGesture: UILongPressGestureRecognizer) {
if longGesture.state == .Began {
let point = longGesture.locationInView(self.collectionView)
currentIndexPath = self.collectionView.indexPathForItemAtPoint(point)
self.counter = 0
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ODIProfileAlbumMode4TableViewCell.incrementCounter), userInfo: nil, repeats: true)
} else if longGesture.state == .Ended {
self.timer.invalidate()
}
}
func incrementCounter() {
self.counter += 1
print(self.counter)
if let indexPath = currentIndexPath {
let section = indexPath.section
if self.counter < self.photoData.count {
self.collectionView.scrollToItemAtIndexPath(NSIndexPath(forRow: self.counter, inSection: section), atScrollPosition: .Right, animated: true)
} else {
self.counter = 0
self.collectionView.scrollToItemAtIndexPath(NSIndexPath(forRow: 0, inSection: section), atScrollPosition: .Right, animated: true)
}
} else {
print("Could not find index path")
}
}
It works perfectly now. :)

Scroll down tableView in swift?

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.

How to drag a static cell into tableView swift?

I have one tableView in my storyBoard where I added 4 static cell into it and my storyBoard look like:
I don't have any dataSource for this tableView because my cells are static.
And I use below code to drag a cell and it is working fine till I scroll a table.
import UIKit
class TableViewController: UITableViewController {
var sourceIndexPath: NSIndexPath = NSIndexPath()
var snapshot: UIView = UIView()
let longPress: UILongPressGestureRecognizer = {
let recognizer = UILongPressGestureRecognizer()
return recognizer
}()
override func viewDidLoad() {
super.viewDidLoad()
longPress.addTarget(self, action: "longPressGestureRecognized:")
self.tableView.addGestureRecognizer(longPress)
self.tableView.allowsSelection = false
}
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
// MARK: UIGestureRecognizer
func longPressGestureRecognized(gesture: UILongPressGestureRecognizer){
let state: UIGestureRecognizerState = gesture.state
let location:CGPoint = gesture.locationInView(self.tableView)
if let indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(location){
switch(state){
case UIGestureRecognizerState.Began:
sourceIndexPath = indexPath
let cell: UITableViewCell = self.tableView .cellForRowAtIndexPath(indexPath)!
//take a snapshot of the selected row using helper method
snapshot = customSnapshotFromView(cell)
//add snapshot as subview, centered at cell's center
var center: CGPoint = cell.center
snapshot.center = center
snapshot.alpha = 0.0
self.tableView.addSubview(snapshot)
UIView.animateWithDuration(0.25, animations: { () -> Void in
center.y = location.y
self.snapshot.center = center
self.snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05)
self.snapshot.alpha = 0.98
cell.alpha = 0.0
}, completion: { (finished) in
cell.hidden = true
})
case UIGestureRecognizerState.Changed:
let cell: UITableViewCell = self.tableView.cellForRowAtIndexPath(indexPath)!
var center: CGPoint = snapshot.center
center.y = location.y
snapshot.center = center
print("location \(location.y)")
//is destination valid and is it different form source?
if indexPath != sourceIndexPath{
//update data source
//I have commented this part because I am not using any dataSource.
// self.customArray.exchangeObjectAtIndex(indexPath.row, withObjectAtIndex: sourceIndexPath.row)
//move the row
self.tableView.moveRowAtIndexPath(sourceIndexPath, toIndexPath: indexPath)
//and update source so it is in sync with UI changes
sourceIndexPath = indexPath
}
if (location.y < 68) || (location.y > 450) {
print("cancelled")
self.snapshot.alpha = 0.0
cell.hidden = false
UIView.animateWithDuration(0.10, animations: { () -> Void in
self.snapshot.center = cell.center
self.snapshot.transform = CGAffineTransformIdentity
self.snapshot.alpha = 0.0
//undo fade out
cell.alpha = 1.0
}, completion: { (finished) in
self.snapshot.removeFromSuperview()
})
}
case UIGestureRecognizerState.Ended:
//clean up
print("ended")
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cell.hidden = false
UIView.animateWithDuration(0.25, animations: { () -> Void in
self.snapshot.center = cell.center
self.snapshot.transform = CGAffineTransformIdentity
self.snapshot.alpha = 0.0
//undo fade out
cell.alpha = 1.0
}, completion: { (finished) in
self.snapshot.removeFromSuperview()
})
break
default:
break
}
}else{
gesture.cancelsTouchesInView = true
}
}
func customSnapshotFromView(inputView: UIView) -> UIView {
// Make an image from the input view.
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0)
inputView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
// Create an image view.
let snapshot = UIImageView(image: image)
snapshot.layer.masksToBounds = false
snapshot.layer.cornerRadius = 0.0
snapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
snapshot.layer.shadowRadius = 5.0
snapshot.layer.shadowOpacity = 0.4
return snapshot
}
}
When I scroll after dragging it looks like:
As you can see cell is not appearing again. I want to drag and drop static cell and I want to save it's position so I will not rearrange again when I scroll.
Sample project for more Info.
This is just a demo project But I have added many elements into my cell and every cell have different UI.
There is a library that does exactly what you are looking to do with a very similar approach. It's called FMMoveTableView but it's for cells with a datasource.
I think that what is causing your problem is that when you move the cells around and then you scroll the datasource from the storyboard is no longer in sync with the table and therefore your cell object can't be redrawn.
I think you should implement your table this way:
Make your 4 cells custom cells.
Subclass each one.
Create an Array with numbers 1 to 4
Reorder the array on long drag
Override cellForRowAtIndexPath to show the right cell for the right number
You can drag uitableview cell from uitableview delegates .......
1) set the table view editing style to none in its delegate.
2) implement table view delegate to enable dragging of cell i.e canMoveRowAtIndexPath methods...
You can create multiple dynamic cells.
You'll just have to dequeue cells with correct identifier.
Are you doing this for layout purposes only, maybe a UICollectionView or a custom made UIScrollView could do the job?
Never the less, I have a solution:
Create a IBOutlet collection holding all your static UITableViewCells
Create a index list to simulate a "data source"
Override the cellForRowAtIndexPath to draw using your own index list
When updating the list order, update the indexList so that the view "remembers" this change
This Table view controller explains it all:
class TableViewController: UITableViewController {
#IBOutlet var outletCells: [UITableViewCell]!
var indexList = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
// Prepare a index list.
// We will move positions in this list instead
// of trying to move the view's postions.
for (index, _) in outletCells.enumerate() {
indexList.append(index)
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Use dynamic count, not needed I guess but
// feels better this way.
return outletCells.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Use the index path to get the true index and return
// the view on that index in our IBOutlet collection
let realIndexForPos = indexList[indexPath.row]
return outletCells[realIndexForPos]
}
#IBAction func onTap(sender: AnyObject) {
// Simulating your drag n drop stuff here... :)
let swapThis = 1
let swapThat = 2
tableView.moveRowAtIndexPath(NSIndexPath(forItem: swapThis, inSection: 0), toIndexPath: NSIndexPath(forItem: swapThat, inSection: 0))
// Update the indexList as this is the "data source"
// Do no use moveRowAtIndexPath as this is never triggred
// This one line works: swap(&indexList[swapThis], &indexList[swapThat])
// But bellow is easier to understand
let tmpVal = indexList[swapThis]
indexList[swapThis] = indexList[swapThat]
indexList[swapThat] = tmpVal
}
}
To create the IBOutlet use the Interface Builder.
Use the Referencing Outlet Collection on each Table View Cell and drag, for each, to the same #IBOutlet in your controller code.

Dynamically scroll through tableView with scrollToRowAtIndexPath

I'd like to visually scroll through my whole tableView. I tried the following, but it doesn't seem to perform the scrolling. Instead it just runs through the loops. I inserted a dispatch_async(dispatch_get_main_queue()) statement, thinking that that would ensure the view is refreshed before proceeding, but no luck.
What am I doing wrong?
func scrollThroughTable() {
for sectionNum in 0..<tableView.numberOfSections() {
for rowNum in 0..<tableView.numberOfRowsInSection(sectionNum) {
let indexPath = NSIndexPath(forRow: rowNum, inSection: sectionNum)
var cellTemp = self.tableView.cellForRowAtIndexPath(indexPath)
if cellTemp == nil {
dispatch_async(dispatch_get_main_queue()) {
self.tableView.scrollToRowAtIndexPath(indexPath!, atScrollPosition: .Top, animated: true)
self.tableView.reloadData()
}
}
}
}
}
I found a solution. Simply use scrollToRowAtIndexPath() with animation. To do so I had to create a getIndexPath() function to figure out where I want to scroll. Has more or less the same effect as scrolling through the whole table if I pass it the last element of my tableView.
If you want it to happen slower with more scrolling effect, wrap it inside UIView.animateWithDuration() and play with 'duration'. You can even do more animation if you want in its completion block. (No need to set an unreliable sleep timer, etc.)
func animateReminderInserted(toDoItem: ReminderWrapper) {
if let definiteIndexPath = indexPathDelegate.getIndexPath(toDoItem) {
self.tableView.scrollToRowAtIndexPath(definiteIndexPath, atScrollPosition: .Middle, animated: true)
}
}

How to check UITableViewCell fully visible / loaded with Swift - iOS (setSelected)

I have UITableView with CustomCell. I want to rotate record image when a cell fully visible or visible more than half at least.
This code block in CustomTableViewCell.swift
override func setSelected(selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
RotateImage()
}
The problem is that image is starting rotate immediately custom cell appeared like image below.
Sample Image:
There are 2 cells. First one is fully visible and rotating. And second one is partially visible / loaded but its also rotating.
Is it possible to check visibility in setSelected code block or need to check with UITableView functions?
The result should be like that:
override func setSelected(selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
if fullyVisible == true
{
RotateImage()
}
}
Thanks.
If you want more control for the behavior of cells based on their visibility withing the bounds of your screen, you could use UIScrollViewDelegate functions
such as:
optional func scrollViewDidScroll(_ scrollView: UIScrollView)
Get an array of the tableView's visible cells.
you can check the bounds of each cell to see if it is on screen
Check if the cell's record is spinning and if not start the rotation.
I did something similar in one of my projects:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleArea = scrollView.contentOffset.y + scrollView.height
for cell in tableView.visibleCells {
guard let propertyGraphCell = cell as? PropertyGraphCell else { continue }
if cell.frame.midY <= visibleArea, let indexPath = tableView.indexPath(for: cell) {
switch visibleRows[indexPath.row] {
case .daysOnMarket:
if daysOnMarketAnimated {
propertyGraphCell.drawLine(animated: true)
daysOnMarketAnimated = false
}
case .priceTrend:
if priceTrendAnimated {
propertyGraphCell.drawLine(animated: true)
priceTrendAnimated = false
}
case .vendorDiscount:
if vendorDiscountAnimated {
propertyGraphCell.drawLine(animated: true)
vendorDiscountAnimated = false
}
case .marketInsights:
assert(false, "cell animation not supported")
}
}
}
}

Resources