Updating CoreData through UISwipeGestureRecognizer on custom UITableViewCell? - ios

I am adding a custom swipe gesture on a UITableViewCell that updates CoreData when a specific cell is swiped. However, I am having trouble passing the indexPath of the swiped cell to the function that will modify the CoreData. I am not using a Table View Swipe Action - I am animating the cell when it is swiped to cross the item out, which means I will need to call an animation on the custom UITableViewCell. So far, I have tried passing the indexPath of the swiped cell to the #selector of the UISwipeGestureRecognizer like so:
The function that handles the gesture:
#objc func handleSwipeGesture(sender: UISwipeGestureRecognizer ,at indexPath: IndexPath) {
itemArray[indexPath.row].complete = !itemArray[indexPath.row].complete
print("\(itemArray[indexPath.row].complete)")
saveItems()
// call animation??
}
In cellForRowAt in the UITableViewDelegate I declare the swipe action and pass the indexPath of the swiped cell as a parameter to the function above.
let swipeToComplete = SwipeCompletion.init(target: self, action: #selector(handleSwipeGesture))
swipeToComplete.indexPath = indexPath
cell.addGestureRecognizer(swipeToComplete)
This is the class that enables me to pass the indexPath through:
class SwipeCompletion: UISwipeGestureRecognizer {
var indexPath: IndexPath!
}
However, this I get the error when I swipe on a cell: EXC_BAD_ACCESS (code=1, address=0xf)
Any ideas on how I could achieve this and how I could call the cell animation on a cell?

On these "#selectors" you can't customize the values passed to the call (As far as I know).
It looks like you have indexPath as a property of your custom recognizer.
Use that indexPath, not the one being passed in. Thats likely not the correct instance.
Something like this might be what your trying to do.
#objc func handleSwipeGesture(sender: SwipeCompletion) {
itemArray[sender.indexPath.row].complete = !itemArray[sender.indexPath.row].complete
print("\(itemArray[sender.indexPath.row].complete)")
saveItems()
//call animation??
}

Related

How do I pass button action from a nested collectionView cell?

I have a MainCollectionView used for scrolling between items, inside of one of these cells I have another collectionView with cells. In that collection view, I have a button for each cell. My question is how do I pass action from my button to my MainCollectionView when it is tapped? I did create protocol for that button in the cell but I don't know how to let MainCollectionView know when my button is tapped. I can call action from my cell class but I think it is better to run it in Model which is my MainCollectionView. Below is my button protocol.
protocol ThanhCaHotTracksCellDelegate: class {
func handleSeeAllPressed()}
weak var delegate: ThanhCaHotTracksCellDelegate?
#objc func handleSeeAllButton(){
delegate?.handleSeeAllPressed()
}
LIke NSAdi said, you're on the right track, but the delegate pattern is a bit much overhead for just a single task like notifying about a button press.
I prefer using closures, because they're lightweight and helps to keep related code together.
Using Closures
This is what I'm always doing in UITableView. So this will work in UICollectionView too.
class MyTableViewCell: UITableViewCell {
var myButtonTapAction: ((MyTableViewCell) -> Void)?
#IBAction func myButtonTapped(_ sender: Any) {
myButtonTapAction?(self)
}
}
So when I dequeue my cell and cast it to MyTableViewCell I can set a custom action like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCellReuseIdentifier", for: indexPath) as! MyTableViewCell
cell.myButtonTapAction = { cell in
// Put your button action here
// With cell you have a strong reference to your cell, in case you need it
}
}
Using direct reference
When you're dequeueing your UICollectionView cell you can obtain a reference to your button by casting the cell to your cell's custom subclass.
Then just do the following
cell.button.addTarget(self, action: #selector(didTapButton(_:)), forControlEvents: .TouchUpInside)
And outside have a function:
#objc func didTapButton(_ sender: UIButton) {
// Handle button tap
}
Downside of this is that you have no direct access to your cell. You could use button.superview? but it's not a good idea since your view hierarchy could change...
You're on the right track.
Make sure MainCollectionView (or the class that contains) it implements ThanhCaHotTracksCellDelegate protocol.
Then assign the delegate as self.
Something like...
class ViewController: ThanhCaHotTracksCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
subCollectionView.delegate = self
}
}

ONLY double tap on UITableView Swift 3.0

I would like my tableView to only react to double taps and not at all to single taps. I am currently using the following code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTap))
tapGesture.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGesture)
let doubleTapGesture = UITapGestureRecognizer()
doubleTapGesture.numberOfTapsRequired = 2
view.addGestureRecognizer(doubleTapGesture)
tapGesture.require(toFail: doubleTapGesture)
// implement what to do
if userInfo[indexPath.row].identifier == "username" {
editUsername()
}
}
func singleTap() {
// DO NOTHING
}
So basically I have been trying to "redirect" the single tap to a function that does nothing. However, I find that (in the simulator), the tableView sometimes reacts to the single tap, sometimes not. Any help to solve this issue is highly appreciated!
To achieve your goal:
Add tap gesture recognizer on your table view, do not forget to set numberOfTapsRequired = 2
Do not implement didSelectRowAtIndexPath method
To prevent table view cells from changing their background color after single tap, set in interface builder, Attributes Inspector tab, table view "selection" attribute to "No selection" or table view cell "selection" attribute to "None".
If you want to get indexpath of cell being doubletapped, in your gesture recognizer handler method get tap location in tap.view and use indexPathForRowAtPoint method of tableView:
let tapLocationPoint = tap.location(in: tap.view)
let tappedCellIndexPath = tableView.indexPathForRow(at: tapLocationPoint)

How to add tap gesture to UICollectionView , while maintaining cell selection?

Task
Add a single tap gesture to UICollectionView, do not get in the way of cell selection.
I want some other taps on the no-cell part of the collectionView.
Code
Using XCode8, Swift 3.
override func viewDidLoad() {
...
collectionView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
}
func tap(sender: UITapGestureRecognizer){
print("tapped")
}
Result
Yeah, it gets in the way now. When you tap on cell, it logs "tapped".
Analysis
I check the hitTest return value of the collectionView and the cell. Both returned the tapped cell, which means they form a responder chain of Cell -> CollectionView
no gestures on the cell
3 gestures on collectionView, no one seems to work with the cell select
UIScrollViewDelayedTouchesBeganGestureRecognizer
UIScrollViewPanGestureRecognizer
UITapGestureRecognizer
callStack, seems cell selection has a different stack trace with gesture's target-action pattern.
double tap gesture works along with cell selection.
Question
Couldn't find more trace. Any ideas on how cell selection is implemented or to achieve this task?
Whenever you want to add a gesture recognizer, but not steal the touches from the target view, you should set UIGestureRecognizer.cancelsTouchesInView for your gestureRecognizer instance to false.
Instead of trying to force didSelectItem you can just get the indexPath and/or cell this way:
func tap(sender: UITapGestureRecognizer){
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell = self.collectionView?.cellForItem(at: indexPath)
print("you can do something with the cell or index path here")
} else {
print("collection view was tapped")
}
}
I get another way: when adding gestures, set delegate, implement below method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
you can decide whether the gesture recognizer to receive the touch by your own logic, with position or the page's hidden property, based on your demand.

Swift: get arbitrary info from UITapGestureRecognizer

I have a list of images within a cell, within a UITableView. For reasons I won't go (too much) into, I can't use didSelectRowAtIndexPath to know which one was selected due to the fact that I am using a third party module that adds its own parent Gestures, and I cannot set cancelsTouchesInView = false (which could technically fix my problem).
In either case, is there a way to add arbitrary info to a view, so that when I receive it in as the sender, I could introspect it.
Eg: if this were HTML & JavaScript, you could do this.
$(myImage).data('foo', 'bar')
$(anotherImage.data('foo', 'thunk')
$('img').on('click', function () {
console.log($(this).data('foo')) // could be "foo" or "thunk"
})
In Swift
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = MyCustomTableViewCell()
cell.userInteractionEnabled = true
let tapped = UITapGestureRecognizer(target: self, action: Selector("myCallback:"))
cell.addGestureRecognizer(tapped)
// my imaginary world...
cell.foo = self.extraData[indexPath.row]
return cell
}
func myCallback(sender: AnyObject?) {
println(sender.foo)
}
Obviously, the above doesn't work, but is there a way to achieve what I'm trying to do?
Although I personally don't recommend using that much but you can make use of objc_setAssociatedObject if you want to attach extra data to objects at runtime.
Here is one good resource about how to do it in Swift:
http://nshipster.com/swift-objc-runtime/
Alternatively, UIView classes have a property named tag to where you can assign indexPath.row for getting the cell that was tapped on later:
cell.tag = indexPath.row
BTW, you better not be working on cells. Instead, always operate on its contentView property when you want to add gesture or another sub view etc.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
cell.contentView.userInteractionEnabled = true
// Always remove previously added tap gestures because cells are reused
// as you scroll up and down so you'll end up having multiple
// recognizers on the same cell otherwise.
for recognizer in cell.contentView.gestureRecognizers {
cell.contentView.removeGestureRecognizer(recognizer)
}
cell.contentView.addGestureRecognizer(
UITapGestureRecognizer(target: self, action: "myCallback:"))
cell.contentView.tag = indexPath.row
...
return cell
}
It is fairly straightforward to get the cell in call back function:
(Presuming you have only one section so that indexPath.section = 0)
func myCallback(sender: UIGestureRecognizer) {
let indexPath = NSIndexPath(forRow: sender.view.tag , inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
print("Cell \(cell) has been tapped.")
}
}

Swift - How to get row data from custom UITableViewCell on UIImageView click from within the cell

I’m trying to get data associated with the UITableViewCell where a UIImageView is clicked within the cell. The code I currently have to capture the click event is working fine. However, once I get into the click function that is called, I’m unable to retrieve the associated data from the same UITableViewCell where the UIImageView was clicked. Here is the code I'm using to set up the click event. This code is contained in cellForRowAtIndexPath:
var tap = UITapGestureRecognizer(target: self, action: Selector("staticMap_click:"))
cell.imgStaticMap.addGestureRecognizer(tap)
cell.imgStaticMap.userInteractionEnabled = true
Here is the function staticMap_click that gets called when the UIImageView is clicked:
func staticMap_click(sender:UITapGestureRecognizer)
{
let rowData: NSDictionary = self.arrayPosts[sender.valueForKey("row") as Int] as NSDictionary
mdblStaticLat = rowData["dblLat"] as String
mdblStaticLong = rowData["dblLong"] as String
self.performSegueWithIdentifier("sgShowMapFromStaticClick", sender: self)
}
As you can see, I'm unsure of how to reference the data for the row that was clicked. I attempted setting a tag on the UIImageView, but that didn’t work. I also attempted to set a tag on the UITapGestureRecognizer, but I haven’t been able to get that to work either.
Does anyone know how I can reference the data from the selected row where the UIImageView is tapped?
You might be interested in
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
}
This function will be called whenever a cell is pressed.
You can access the row which is pressed by using indexPath.row
Furthermore you can use
func staticMap_click(sender:UITapGestureRecognizer){
let tappedView = sender.view as? UIImageView
let indexPath = (tappedView.superview as UITableView).indexPathForCell(self)
}
To get the cell which was tapped if you want only the Image to be tapable
This code will also return an indexPath, you will be able to use indexPath.row again to get the row.
Getting the row will enable you to get data out of an array if deemed necessary

Resources