I have a UICollectionView and each cell has a UITextView. I'm trying to make it possible to delete the entire cell when long pressing the text in a TextView and selecting Delete from the context menu.
I know I can override Delete by overriding
func delete(_ sender: Any?) {//I want to delete cell from here}
and
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(delete(_:))
{
return true
}
return super.canPerformAction(action, withSender: sender)
}
I just don't know how to find the current cell I am in.
In your func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell set indexPath.row as tag of your UITextView. Then in your func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) or UITextView long press you can access the tag of UITextView which helps to find the current cell.
You can access current cell using this code:
let indexPath = IndexPath(row: textview.tag, section: 0)
let yourCell = self.collectionView.cellForItem(at: indexPath)
I hope this will help you, Or you are experiencing any other problems, let me know :)
Give tag to textfield which should be equal to current indexPath while adding cell to collection view. When long press listener is triggered, check the tag value and based on that, do you operation.
Swift 3 Solution:
So some people were close, but everyone led me down the right path almost. The solution doesn't exactly answer my question, but it could be combined with a gesturerecognizer to answer the question specifically.
So my solution is based on how my code is formatted and may need to be tweaked for anyone else.
First:
From the UICollectionViewCell subclass, where my textview is implemented of course, I overrode the built in delete function. From there is created a variable that is instantiated with with the cells superview, which is a UICollectionView. This allows me to get the indexPath for the cell. From there I also create a variable that is instantiated with the UIView for where my UICollectionView is located. From there I created a function inside my UIView that takes in a IndexPath and deletes the cell.
Inside UICollectionViewCell:
override func delete(_ sender: Any?) {
let cv = self.superview as! UICollectionView
let indexPath = cv.indexPath(for: self)
let view = self.superview?.superview as! UIView
view.delCell(indexPath!)
}
Inside UIView:
func delCell(indexPath: IndexPath) {/*code for cell removal here*/}
func deleteSections(IndexSet)
Deletes the sections at the specified indexes.
func deleteItems(at: [IndexPath])
Deletes the items at the specified index paths
Related
I have a UICollectionView that appropriately recognizes taps on its cells in its collectionView(didSelectItemAt: ) delegate method.
However, I then embedded a collection view within the cell itself (so that each cell has its own collection view inside of it), but now the parent cell is not recognizing any taps anymore, I'm assuming because the embedded collection view is eating them up.
Is there some property that needs to be set so that the original (parent) cells register taps again even with their embedded collection views?
This functionality can be confusing, as users are accustomed to "something else" happening when interacting with a control in a table view cell, rather than it selecting (or also selecting) the row itself.
However, if you really want to do that...
One approach is to use a closure in your cell. When you handle didSelectItemAt use the closure to tell the table view controller to select the row.
Note that Apple's docs point out:
Note
Selecting a row programmatically doesn't call the delegate methods tableView(_:willSelectRowAt:) or tableView(_:didSelectRowAt:), nor does it send selectionDidChangeNotification notifications to observers.
So, if you need to execute code when a table view row is selected, you'll need to call that yourself - something like this:
func myTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("My didSelectRowAt:", indexPath)
}
Using the code in my answer to your previous question...
In SomeTableCell add this closure setup:
public var didSelectClosure: ((UITableViewCell) ->())?
and, still in SomeTableCell:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("collectionView didSelecteItemAt:", indexPath)
print("Calling closure...")
didSelectClosure?(self)
}
Next, in cellForRowAt in the table view controller, set the closure:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "someTableCell", for: indexPath) as! SomeTableCell
cell.rowTitleLabel.text = "Row \(indexPath.row)"
cell.thisData = myData[indexPath.row]
cell.didSelectClosure = { [weak self] c in
guard let self = self,
let idx = tableView.indexPath(for: c)
else { return }
// select the row
tableView.selectRow(at: idx, animated: true, scrollPosition: .none)
// that does not call didSelectRowAt, so call our own func
// if we want something to happen on row selection
self.myTableView(tableView, didSelectRowAt: idx)
}
return cell
}
you can implement UICollectionViewDataSource & UICollectionViewDelegate methods of inner collectionViews inside the cells itself & pass the events with closure to main class with main UICollectionView.
Hello on a specific viewController of my project i have a UICollectionView with a custom class cell. But i have a big problem this that func :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("tapped on a cell")
}
when is click on a cell and instant realease (normal click) it does nothing, just nothing.
if i press and hold about 1s without release the finger it become grey, highlighted
And if i press and hold at least 3 seconds release the finger didSelectItemAt is executed correctly.
I tried to do the same on another project and that's work great but not on this VC and i really don't find the problem. The VC Bugged is of addTest clas in Main.storyboard
The insight of Mojtaba Hosseini is very clever, but the answer given might not be quite correct.
It turns out that there is a UITapGestureRecognizer on the main view; if it recognizes before the tap on the cell, it prevents cell selection. But if you merely set cancelsTouchesInView to false on that gesture recognizer, then they both operate, and that seems unlikely to be what is wanted. We surely want the cell tap and not the tap gesture recognizer tap.
The correct solution is thus to give the tap gesture recognizer a delegate and implement gestureRecognizerShouldBegin. Here, we look to see where the tap is. If it is within the bounds of a cell, we return false; otherwise we return true. We thus mediate between the cell tap and gesture recognizer tap.
Here is a possible implementation, demonstrated in a highly simplified form:
extension UIView {
func isDescendant(of whattype:UIView.Type) -> Bool {
var sup : UIView? = self.superview
while sup != nil {
if (whattype == type(of:sup!)) {
return true
}
sup = sup!.superview
}
return false
}
}
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let t = UITapGestureRecognizer(target: self, action: #selector(tap))
self.view.addGestureRecognizer(t)
t.delegate = self
}
#objc func tap(_:UIGestureRecognizer) {
print("tap")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("select")
}
func gestureRecognizerShouldBegin(_ gr: UIGestureRecognizer) -> Bool {
if let v = gr.view {
let loc = gr.location(in: v)
if let v2 = v.hitTest(loc, with: nil) {
return !v2.isDescendant(of: UICollectionViewCell.self)
}
}
return true
}
}
As you can see, we look to see whether the tap is inside a collection view cell; if it is, our gesture recognizer is prevented from recognizing, and the selection succeeds immediately.
Probably there is a UIGesture or another interactable thing underneath the collection view. You should DISABLE its ability to cancel touches in view in interface builder:
or in code:
myTapGestureRecognizer.cancelsTouchesInView = false
I am trying to segue from a specific cell in my CollectionView in order to access different View Controllers for each cell.
I have successfully found how to segue from a cell item.
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let selectedIndexPath = interests[indexPath.item]
performSegue(withIdentifier: "FeatureVC", sender: selectedIndexPath)
}
Here is an image of my current code
However, figuring out how to segue form a specfic cell is proving more difficult. I have looked at a few answers but nothing seems tp be very straight forward.
I am new to development so I can understand if this question has a simple solution however this will be key for the main functionality of my application so i have to get this right.
Any guidance I can receive would be greatly appreciated.
It seems like you are using the wrong delegate as per your conditions also if you want to segue only one cell you can check the IndexPath.item
change didDeselectItemAt to didSelectItemAtIndexpath
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexpath indexPath: IndexPath) {
if indexPath.item == yourSpecificCellIndex {
//perform segue
}
}
If you want to select from 1 cell use didSelectItemAtIndexpathmethod.
In that identify your cell using the position at indexpath.row and then call the segue accordingly
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
var cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath)!
// Use any cell values to identify which one should open segue
}
First use didSelectItem method. Then write appropriate condition in that:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
if condition // your specific condition
{
performSegue(withIdentifier: "FeatureVC", sender: selectedIndexPath)
}
}
As far as I understand, you want to have your specific item in the featureVC.
So in your FeatureVC class add a variable like
var interest : Interest! // this should be the type of what you have in your interests Array
In your controller where you perform the segue add a variable on the top like
var selectedInterest: Interest!
Your function to determine which item you clicked is not correct so use:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedInterest = interests[indexPath.item]
performSegue(withIdentifier: "FeatureVC", sender: nil)
}
Now you just need a little bit preparation bevor you segue, so just add:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "FeatureVC" {
let featureVC = segue.destination as! FeatureVC // your controller name
featureVC.interest = selectedInterest // here you can set the variable in your feature vc
}
}
I'm new to Swift and have no prior experience with Objective C. I usually implement an UI Collection View like this:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let reuseIdentifier = "cell"
var items = ["1", "2", "3" ... "100"]
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCollectionViewCell
cell.myLabel.text = self.items[indexPath.item]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
}
But I do not have the slightest idea of how to have a text field in those cells rather than the numbers from declared array (items). And how would I be able to store the text entered into an array?
P.S Im not posting this expecting for a code (although it would help me a lot) but rather for some guidance or weblinks on how to do it.
There are many ways to do this. A simple way might be to:
drag a UITextView into your UICollectionView prototype cell in your storyboard
give the textView a tag number so you can reference it in code
in the function `didSelectItemAtIndexPath, you need to get a reference to the cell, like this:
let cell = collectionView.cellForItemAtIndexPath(indexPath)
then you can get a reference to the UITextView by tag like this
let textField = cell?.viewWtihTag(100) as? UITextField
Now you can do whatever you want with the value. The issue here is when a user clicks in the UITextField, it will not trigger this method (although they can edit the textField). If they make a change and then click outside the textField, it will trigger this method, and you will see the changed text. You can work around this by adding a checkbox or something for the user to accept their changes. or something like that.
hello I have implemented a UICollectionView in my controller. There are lots of info I am displaying on each cell. When user clicks the particular cell I want to pass all the info that was in the cell to another controller.
As I am displaying all the data from db in the cells and cells are generating dynamically so I think one way could be to pass just a record id from one cell to this function didSelectItemAtIndexPath and then in the second controller I query again from the database. But I think that would not be the good idea. and also I don't know how can I pass the id or any info here in this function didSelectItemAtIndexPath.
So in short I want to display all the info in another controller that is being displayed in that cell which is being clicked
here is my code
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath
indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",
forIndexPath: indexPath) as! CardsCollectionCell
cell.usernameLabel.text = (((dict["\(indexPath.item)"] as?NSDictionary)!["Trip"] as?NSDictionary)!["userName"] as?NSString)! as String
cell.feeLabel.text = (((dict["\(indexPath.item)"] as?NSDictionary)!["Trip"] as?NSDictionary)!["fee"] as?NSString)! as String
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
}
So let's going to explain by parts, the didSelectItemAtIndexPath delegate method is used to know what UICollectionViewCell was tapped in the UICollectionViewController, you can get the UICollectionViewCell very easily using the method cellForItemAtIndexPath(it's not the same as collectionView(collectionView: UICollectionView, cellForItemAtIndexPath) like in the following code:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = self.collectionView?.cellForItemAtIndexPath(indexPath) as! CardsCollectionCell
// access to any property inside the cell you have.
// cell.usernameLabel.text
}
So regarding in short I want to display all the info in another controller that is being displayed in that cell which is being clicked
You can use the didSelectItemAtIndexPath method without any problem to launch a manual segue using the method performSegueWithIdentifier to another UIViewController and just in the prepareForSegue pass any data you need to pass to the another UIViewController. See the following code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// the identifier of the segue is the same you set in the Attributes Inspector
self.performSegueWithIdentifier("segueName", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "segueName") {
// this is the way of get the indexPath for the selected cell
let indexPath = self.collectionView.indexPathForSelectedRow()
let row = indexPath.row
// get a reference to the next UIViewController
let nextVC = segue.destinationViewController as! NameOfYourViewController
}
}
I hope this help you.