Right now I have a list of scrolling usernames using a Collection View of buttons. But I’d like to add overlapping delete buttons to each row. They'd need to be attached to the name buttons and scroll with them.
How can I add these buttons to my CollectionView?
(Also I'd like to skip the delete button on the first row for obvious reasons)
Current Code:
//Add the cells to collection
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UsernameCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! UsernameCollectionViewCell
cell.usernameLabel.text = userNames [indexPath.row]
return cell
}
//Upon Selecting an item
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if (indexPath.row == 0){
self.performSegueWithIdentifier("newUserSegue", sender: self)
}
else {
sendData(userNames[indexPath.row])
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Got it working! Here's how:
I added a button to the cell in the Storyboard.
Connected an outlet to the UICollectionViewCell class.
Edited view controller code to:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UsernameCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! UsernameCollectionViewCell
cell.usernameLabel.text = userNames [indexPath.row]
cell.deleteButton?.layer.setValue(indexPath.row, forKey: "index")
cell.deleteButton?.addTarget(self, action: "deleteUser:", forControlEvents: UIControlEvents.TouchUpInside)
// Remove the button from the first cell
if (indexPath.row == 0){
var close : UIButton = cell.viewWithTag(11) as! UIButton
close.hidden = true
}
return cell
}
func deleteUser(sender:UIButton) {
let i : Int = (sender.layer.valueForKey("index")) as! Int
userNames.removeAtIndex(i)
UserSelectCollection.reloadData()
}
Many thanks to JigarM for his examples on GitHub:
https://github.com/JigarM/UICollectionView-Swift
Why not create custom UICollectionViewCell in IB and just add button to it ?
Register it to your collectionView with :
- registerNib:forCellReuseIdentifier:
You can use delegate or notification to process button tap.
Related
This question already has an answer here:
Scrolling in UICollectionView selects wrongs cells - Swift
(1 answer)
Closed 5 years ago.
I have a UICollectionViewController which uses a custom cell. There is a function get called when the user taps on a cell, to distinguish the selected cell I change the background color of the cell to the green.
The problem is, when user taps on another cell, the previous one should be unselected, another function will be called. as long as the collectionView is not scrolled it works fine, but when the user scrolls the collectionView and selected one goes out of the visible rect of the screen, my deselect function does not work and there will be two cells with a green background.
It's the demo:
You can see there is a cell with green background at the top, and another one at the end.
Here are methods for selecting and deselecting cells:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CategoryCollectionViewCell {
cell.selectItem()
}
}
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
for _cell in collectionView.visibleCells {
if let __cell = _cell as? CategoryCollectionViewCell {
__cell.deselectItem()
}
}
if let indexPath = collectionView.indexPathsForSelectedItems {
if indexPath.count > 0 {
if let _cell = collectionView.cellForItem(at: indexPath.first!) as? CategoryCollectionViewCell {
_cell.deselectItem()
}
}
}
return true
}
Try this
let selectedIndexPath : IndexPath
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CategoryCollectionViewCell {
cell.selectItem()
}
if let preViousSelectedcell = collectionView.cellForItem(at: selectedIndexPath) as? CategoryCollectionViewCell {
preViousSelectedcell.deselectItem()
}
selectedIndexPath = indexPath
}
I guess, I got your issue, simply this happens after scrolling the scroll view, that means the selected become selected and due to reusable cell another cell become selected automatically, well this is a common problem and it happens in UITableView and UIScrollView mostly.
In this view's wherever you have used if condition in datasource and its delegate methods put an else part too, and this will solve your problem.
For example:
if let indexPath = collectionView.indexPathsForSelectedItems {
if indexPath.count > 0 {
if let _cell = collectionView.cellForItem(at: indexPath.first!) as? CategoryCollectionViewCell {
_cell.deselectItem()
}
}
else{
// do something here
}
}
else{
// do something here
}
Hope this may help you.
I have 2 swift files the one is my HomeViewController and the second is my EventCollectionViewCell. In the second one I have an IBOutlet = informationView :UIView and I want to access this from the HomeViewController.
Is there any way ?
Thank you in advance,
KS
Well I suppose that in HomeViewController you have a UICollectionView and that acts as a datasource for it, then in your collectionView dataSource you can make:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellIdentifier", forIndexPath: indexPath) as! EventCollectionViewCell
//Here you can access the IBOulets define in your cell
cell.informationView.backgroundColor = UIColor.greenColor()
return cell
}
Edit:
Problem: When you tap on a cell you want to show an Overlay inside the cell.
Solution:
You need you data model to maintain the state, if it's active or not (the informationView), suppose that ItemCell is my model (in your case Event can be):
class ItemCell{
var active:Bool = false
}
In the collectionView:cellForRowAtIndexPath:, you're going to check the current status of your model, and base on that, show or hide that overlay:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellIdentifier", forIndexPath: indexPath) as! EventCollectionViewCell
let event = self.data[indexPath.row]
//Here you can access the IBOulets define in your cell
cell.informationView.hidden = !event.active
return cell
}
Then as a final step, every time you select a cell (func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) method), you're going to update the status of your model:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
let event = self.data[indexPath.row]
event.active = !event.active
collectionView.reloadData()
}
Sample Project:
https://github.com/Abreu0101/SampleTap
Follow what Jose has answered and if you are creating the cell in the interface builder. Set the class of the cell in Identity Inspector to EventCollectionViewCell and set the cell identifier to "cellIdentifier" that Jose has specified.
Declare a property for that cell and access IbOutlets through that object.
#property (nonatomic, strong) MSSearchHeaderView *searchHeaderView;
if (self.searchHeaderView.searchTextField.text.length <= 0) {
self.searchHeaderView.clearButton.hidden = YES;
}else{
self.searchHeaderView.clearButton.hidden = NO;
}
So I'm setting a UITableViewCell's layout programmatically when it is selected:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.selectedCellIndexPath = indexPath
var selectedCell = tableView.cellForRowAtIndexPath(indexPath)!
tableView.beginUpdates()
tableView.endUpdates()
var cell:SelectedPatientCell = tableView.dequeueReusableCellWithIdentifier("patient selected", forIndexPath: indexPath) as! SelectedPatientCell
cell.patientName.text = patients[indexPath.row].fullName
cell.dob.text = patients[indexPath.row].dob
...
selectedCell = cell
}
And when I scroll the tableView, the layout of the cell resets to its original layout set in cellForRowAtIndexPath. However, the height stays as it should when I set it in the function above. Does anyone know how to fix this?
Here is an album of what's happening:
http://imgur.com/a/OUIMJ
Image 1:original state
Image 2: selected state (how it should stay on scrolling)
Image 3: what actually happens
you should hold this state in
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath == self.selectedCellIndexPath {
var cell:SelectedPatientCell = tableView.dequeueReusableCellWithIdentifier("patient selected", forIndexPath: indexPath) as! SelectedPatientCell
cell.patientName.text = patients[indexPath.row].fullName
cell.dob.text = patients[indexPath.row].dob
return cell
}
let cell = tableView.dequeueReusableCellWithIdentifier("patient selected") as! OriCell
...
return cell
}
in this way if you scroll tableView,it won't resume to original Cell.
Hopefully it is clear.
So I found my own solution:
Instead of doing
tableView.beginUpdates()
tableView.endUpdates()
I needed to do this at the end of the function:
tableView.reloadData()
and that solves the issue
I have a UITableViewController. It has one prototype cell, and spawns anywhere from 0 to 100 cells based on a user query. Once they load, I want an IBAction to trigger if a user taps anywhere within the cell EXCEPT for a specific button. I have multiple labels, and I still want the IBAction to be triggered if user taps on them. How do I accomplish this?
Here's my code for loading the tables:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TypeOfActivityTableViewCell
cell.activityLabel?.text = activityNames[indexPath.row]
cell.bgImage?.image = UIImage(named: activityPic[indexPath.row])
cell.sloganLabel?.text = slogan[indexPath.row]
return cell
}
I do not want to code all this within tableView for MVC principles.
For tapping anywhere in the cell you need not configure an IBAction. Just use the default UITableViewDelegate i.e:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
let geoSearchWord = "geoSearchWord" + searchQuery[indexPath.row]
let geoSearchLat = "&geoSearchWordLat=" + (lat == "" ? "33.9700" : lat)
let geoSearchLon = "&geoSearchWordLon=" + (lat == "" ? "-118.4180" : lon)
let geoSearchRadius = "&geoSearchWordRad=5mi"
let twitterURLRequest: String = "https://quiet-cove-5048.herokuapp.com/tw?\(geoSearchWord)\(geoSearchLat)\ (geoSearchLon)\(geoSearchRadius)"
alamoRequest(twitterURLRequest)
}
Else if you have Buttons inside your cell for specific actions. You will have to add action to that button and assign a tag for identification(which button is tapped).
You can add action in cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TypeOfActivityTableViewCell
cell.activityLabel?.text = activityNames[indexPath.row]
cell.bgImage?.image = UIImage(named: activityPic[indexPath.row])
cell.sloganLabel?.text = slogan[indexPath.row]
cell.myButton.addTarget(self, action: "myAction:", forControlEvents: .TouchUpInside)
cell.myButton.tag = indexPath.row
return cell
}
Tapping on a button inside the cell will execute the below function:
func myAction(sender:UIButton){
let selectedActivityName = activityNames[sender.tag]
}
Here is one crafty solution.You can add an UIControl(e.g. UIButton) to receive touch in that special area.And then receive events in UITableViewDelegate: tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
If you want click any where in cell then you can use its delegate methods
i.e
tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
And if you want to add UIControl and on touch event of that control you need to do as follows:
First You need to add target for your control in your cellForRowAtIndexPath as below.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TypeOfActivityTableViewCell
cell.activityLabel?.text = activityNames[indexPath.row]
cell.bgImage?.image = UIImage(named: activityPic[indexPath.row])
cell.sloganLabel?.text = slogan[indexPath.row]
cell.btn.addTarget(self, action: "btnClickAction", forControlEvents: UIControlEvents.TouchDragInside)
return cell
}
Button Click fucntion:
func btnClickAction(sender: UIButton)
{
var superView = sender.superview
while superView?.isKindOfClass(UITableViewCell) == false
{
superView = superView?.superview
}
var cell = superView as! UITableViewCell
var indexPath = self.tableView.indexPathForCell(cell)! as NSIndexPath
}
In above code you will find indexPath.
I have a custom class for a table cell that is connected to a switch within the table cell (with an action) and I want to be able to to communicate to the TableViewController that the action happened as well as the path of the cell. The way that initially came to mind was if I could use some function in UITableViewCell to get the TableViewController of the table the cell is part of, as my custom class is (rather obviously) a subclass of UITableViewCell. Please tell me if I'm missing something.
To get a reference to the containing view controller, I add a weak property on the cell subclass.
#property (nonatomic, weak) UITableViewController *viewController;
You can assign this value in cellForRowAtIndexPath:
For accessing each cell in TableView
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as YourTableViewCell
cell.mainTextLabel.text = self.venueService.mainCategoriesArray()[indexPath.row]
return cell
}
For getting selected cell in TableView
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow();
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell;
print(currentCell)
}
Reaching from TableViewCell to TableView
self.superview //// self is TableViewCell and its superview represents TableViewController
hope that helps
You should make the ViewController the target of the switch action.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SwitchCell", forIndexPath: indexPath) as! SwitchTableViewCell
cell.onSwitch.addTarget(self, action: Selector("cellSwitchDidChange:"), forControlEvents: .ValueChanged)
cell.label.text = "This is a Switch"
return cell
}
func cellSwitchDidChange(sender: UISwitch) {
let origin = sender.convertPoint(CGPointZero, toView: tableView)
let indexPath = tableView.indexPathForRowAtPoint(origin)!
// do something
}