I've put a uicollectionview inside of a uitableview. I'm having trouble seguing to another viewcontroller after selecting a collectionview cell that is inside of the table view cell.
// if the user selects a cell, navigate to the viewcontroller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// we check did cell exists or did we pressed a cell
if let cell = sender as? UICollectionViewCell {
let cell2 = tableView.dequeueReusableCell(withIdentifier: "cell") as! TestingTableView
// define index to later on pass exact guest user related info
let index = cell2.collectionView?.indexPath(for: cell)!.row
print(index as Any)
// if segue is guest...
if segue.identifier == "guest" {
// call guestvc to access guest var
let guestvc = segue.destination as! GuestCommunityViewVC
// assign guest user inf to guest var
guestvc.guest = communities[index!] as! NSDictionary
}
}
}
}
I'm getting an error at the line:
let index = cell2.collectionView?.indexPath(for: cell)!.row
because it is saying the value is nil. Does anyone know a better method to do this?
Here is an example of how to use a delegate:
1) Create a protocol outside of a class declaration:
protocol customProtocolName:class {
func pushToNewView(withData:[DataType])
}
note: use class in order to prevent a reference cycle
2) Create a delegate inside of the UITableViewCell that holds the reference to the UICollectionView:
class customUITableViewCell {
weak var delegate:customProtocolName? = nil
}
3) Inside the UIViewController that holds the reference to the UITableView, make sure you add the protocol besides the class declaration and add the function we created to ensure that the protocol specifications are satisfied:
class customViewController: customProtocolName {
func pushToNewView(withData:[DataType]) {
//inside here is where you will write the code to trigger the segue to the desired new UIViewController
//You can take this new data and store it in this ViewController and then during the segue pass it along
}
}
4) In the UITableViewDelegate function, "cellForRowAt", set the delegate inside the customUITableViewCell to self:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! customUITableViewCell
cell.delegate = self
return cell
}
5) Inside the customUITableViewCell, where the UICollectionView delegate function handles "didSelectItemAt" delegate function, you trigger the protocol function there like so:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.pushToNewView(withData:[DataType])
}
This is a very simplified example, if you wanted to pass an IndexPath, then you can modify the function to do so. you can also pass back anything you want as well, it isn't limited.
Related
I have a tableView with Sections and every section got todoitems that u add.
I'm trying to animate in my picture on the todoItem I click with my cell button delegate function. But I really don't know how to connect to just that one item and make it animate.
What I'm trying to get access to is the same access to a cell like I do in didSelectRow but in this cellButtonFunction, since it's the button in the cell I'm gonna press on and not the whole cell.
The thing I wanna accomplish with pressing the button is like this example:
cellPressed.image.ishidden = false
cellPressed.image.animate()
This cell image have an animated class that works set to it, it's just that I need to hit the specific cell for this
Watched a few YouTube videos and researched but I don't get it to work, so I hope you guys can help me
extension ViewController: FirstTableViewCellDelegate {
func myImageButtonTapped(with index: IndexPath, view: AnimatedCheckmarkView) {
HapticsManager.shared.selectionVibrate()
let todo = self.tableViewCoreData[index.section].sortedItems[index.row - 1]
todo.isMarked = !todo.isMarked
if todo.isMarked == true {
self.tableViewCoreData[index.section].totalMarked += 1
view.animate(withDuration: 1.0, delay: 0.7)
view.isHidden = false
do {
try self.context.save()
}
catch{
//error
}
tableView.reloadData()
} else {
view.isHidden = true
self.tableViewCoreData[index.section].totalMarked -= 1
do {
try self.context.save()
}
catch{
//error
}
tableView.reloadData()
}
Here is the function. Litterly everything in this works except the view.animate() that animates one random view instead
First create delegate to receive action from cell
protocol CellDelegate:AnyObject{
/// pass arguments as per requirement
func cell(buttonDidPressed indexPath: IndexPath)
}
Now in your Cell class create weak reference of delegate
class Cell: UICollectionViewCell{
weak var delegate: CellDelegate? = nil
var indexPath: IndexPath!
/// code
#IBAction func buttonDidPressed(_ sender: UIButton) {
delegate?.cell(buttonDidPressed: indexPath)
}
}
now in ur controller confirm this protocol
extension ViewController: UICollectionViewDelegate,
UICollectionViewDataSource,
CellDelegate{
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let cell : Cell dequeueReusableCell your cell
cell.delegate = self
cell.indexPath = indexPath
return cell
}
func cell(buttonDidPressed indexPath: IndexPath){
// your task on cell click
}
}
You can also use closures in cell class alternative to protocol
I have a delegate method where if I press a button in the tableview, it should segue to another view controller and pass along data but it doesn't seem to work.
func goToVC(uid: String) { //delegate method
performSegue(withIdentifier: "showVC", sender: self) //Do I need this
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showVC", sender: self)
self.tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVC" {
if let indexPath = tableView.indexPathForSelectedRow {
let guestVC = segue.destination as! GuestViewController
guestVC.ref = userArray[indexPath.row].ref
}
}
class MainViewController: UIViewController {
// set the cell's delegate in the data source
// pass the object to the cell from the data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.mainViewControllerDelegate = self
cell.object = someArray[indexPath.row]
}
// this is the method that gets called by the cell through the delegate
func pushToViewController(object: YourDataObject) {
let destination = SomeViewController()
destination.object = object
navigationController?.pushViewController(destination, animated: true)
}
}
class TheTableViewCell: UITableViewCell {
// create a delegate and a data object
var mainViewControllerDelegate: MainViewController?
var object: YourDataObject?
// this is the method that gets called when the button in the cell is tapped
#objc func buttonAction() {
mainViewControllerDelegate?.pushToViewController(object: object)
}
}
I highly recommend that beginners do not use Interface Builder. The less you use it early, the quicker you will understand more. Interface Builder is fool's gold for beginners.
You dont need delegate method here. Delegate method can be used if you need to pass the value from the child view controller.
What you are doing is exactly right. Make sure you set the segue identifier in the story board correctly.
And one more thing dont set your table IBOutlet as default tableView try setting a name apt for that table like toDoTable, so it will easy to debug.
The problem I'm facing is probably some lack of understanding on the concept of reusable cells. I have, let's say, 30 rows to be created and each of them has a UISwitch.
When I toggle one of the switches, it's behavior should affect the other 29. The point is: as far as I know, iOS doesn't create all of them at once, but rather wait to reuse the cells when the TableView is scrolled up and down.
How can I keep a copy of those reused objects and tell iOS to set the proper value to the switches?
I've thought on having the cells appended to a [UISwitch] but I can't manage to have all the 30 cells in there, look:
...
var switches = [UISwitch]()
...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Field10Cell", for: indexPath) as! Field10TableViewCell
...
//cell.value is a UISwitch
if !switches.contains(cell.value) {
switches.append(cell.value)
}
return cell
}
You could create a set which stores the indexes of the cells whose switches have been pressed.
var activeSwitches = Set<IndexPath>()
Whenever a user presses the switch on a cell, you store it on the set like this:
activeSwitches.insert(indexPath)
If you need to check if a switch was activated, just check if its container cell's indexPath is in active switches like so:
if activeSwitches.contains(indexPath) {
// do something
}
In order to know when a user pressed a specific switch I recommend the folliwing:
In cellForRowAtIndexPath save the current indexPath into your Field10TableViewCell.
Create a protocol on Field10TableViewCell and add a delegate.
protocol Field10Delegate {
func didChangeSwitch(value: Bool, indexPath: IndexPath)
}
class Field10TableViewCell {
var delegate: Field10Delegate?
var indexPath: IndexPath?
#IBOutlet weak var fieldSwitch: UISwitch! // Can't use 'switch' as a variable name
#IBAction func switchValueChanged(_ sender: UISwitch) {
if let indexPath = indexPath {
delegate?.didChangeSwitch(value: sender.isOn, indexPath: indexPath)
}
}
When you create a cell, set the view controller as a delegate
let cell = tableView.dequeueReusableCell(withIdentifier: "Field10Cell", for: indexPath) as! Field10TableViewCell
cell.delegate = self
cell.indexPath = indexPath
Make your view controller comply with the protocol:
extension ViewController: Field10Delegate {
/* Whenever a switch is pressed on any cell, this delegate will
be called. This is a good place also to trigger a update to
your UI if it has to respond to switch changes.
*/
func didChangeSwitch(value: Bool, indexPath: IndexPath) {
if value {
activeSwitches.insert(indexPath)
} else {
activeSwitches.remove(indexPath)
}
updateUI()
}
}
With the above, at any point you will know which switches are active or not and you can process the dequeued cells with this information.
I have used this tutorial to successfully embed a UICollectionView inside a UITableView I have in my ViewController.
The following will probably make more sense if you have a quick look at the linked tutorial's code (plus its a nifty thing to learn too!):
The next step for me is to perform segues from the cells of the UICollectionView inside the UITableViewCells of the tableView, but as the collectionView outlet is established in a separate View Controller, I am not sure how to reference it in the main ViewController.
In the TableViewCell.swift there is:
class TableViewCell: UITableViewCell {
#IBOutlet private weak var collectionView: UICollectionView!
}
extension TableViewCell {
func setCollectionViewDataSourceDelegate<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row
collectionView.reloadData()
}
}
And in the ViewController.swift I need to be able to, for instance, call the collectionView that is in the TableViewCell in the prepareForSegue function that would go in the ViewController.swift file. I just need to fill the gaps with the collectionView outlet:
let destination = segue.destinationViewController as! SecondViewController
let indexPaths = self.___________.indexPathsForSelectedItems()
let indexPath = indexPaths![0] as NSIndexPath
let arrayObject = self.arrayObjects[indexPath.row]
destination.object = arrayObject
'object' is implemented in SecondViewController like so, var object: PFObject!.
I now need to fill the gap, ________, in the above code with the collectionView in order to display the correct 'object' in SecondViewController (the destinationViewController)
Add Push Segue from UICollectionViewCell to YourViewController from IB.
Give an identifier to your segue ("YourSegueIdentifier").
In your custom UITableViewController or UIViewController, override prepareForSegue() method.
This is:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourSegueIdentifier" {
if let collectionCell: YourCollectionViewCell = sender as? YourCollectionViewCell {
if let collectionView: UICollectionView = collectionCell.superview as? UICollectionView {
if let destination = segue.destination as? YourViewController {
// Pass some data to YourViewController
// collectionView.tag will give your selected tableView index
destination.someObject = tableObjects[collectionView.tag].someObject
destination.productId = collectionCell.product?.id
}
}
}
}
}
Hi I had the same problem
This is what I did :
1. set a tag for the cell in the extension in your first View Controller
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("advertisementCollectionCell",forIndexPath: indexPath) as! AdvertisementCollectionViewCell
// set your cell information
cell.tag = indexPath.row
return cell
}
2. Do a segue on the storyboard from the UIcollectionViewCell to the new View Controller
3. In prepareForSegue of the first TableViewController :
else if segue.identifier == "identifier"
{
let destinationViewController = segue.destinationViewController as! DestinationViewController
if let cell = sender as? MyCollectionViewCell
{
let indexPath = cell.tag
// use indexPath :D
}
}
i just played with the code you attached. i added segue in story board manually. its easy to call .
1) just add viewcontroller to the storyboard.
2) Controll+ drag and drop segue from collectionview cell
In your code, the reference to CollectionView is connected in TableViewCell.
So, you can refer it through TableViewCell and set datasource and delegate.
To add connect into your code, you have to select the CollectionView with RightButton and drag and drop into #IBOutlet private weak var collectionView: UICollectionView!
When clicks the CollectionViewCell, collectionView(collectionView:UICollectionView, didselecteditemAtIndexPath indexPath: NSIndexPath) function of UICollectionViewDelegate will be called by delegate.
At that time, you can segue by calling performSegue in this function.
As you write in your code, the index of tableview is set by tag, so you can get the index by tag using collectionView.tag()
The code will be as following.
var tableViewIndex = collectionView.tag()
So you can refer the correct cell using tableViewIndex and indexPath.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var tableViewIndex = collectionView.tag;
self.performSegueWithIdentifier("segueIdentifier", sender: self)
}
I have an app that has a custom button in a custom cell. If you select the cell it segues to the a detail view, which is perfect. If I select a button in a cell, the code below prints the cell index into the console.
I need to access the contents of the selected cell (Using the button) and add them to an array or dictionary. I am new to this so struggling to find out how to access the contents of the cell. I tried using didselectrowatindexpath, but I don't know how to force the index to be that of the tag...
So basically, if there are 3 cells with 'Dog', 'Cat', 'Bird' as the cell.repeatLabel.text in each cell and I select the buttons in the rows 1 and 3 (Index 0 and 2), it should add 'Dog' and 'Bird' to the array/dictionary.
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postsCollection.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell
// Configure the cell...
var currentRepeat = postsCollection[indexPath.row]
cell.repeatLabel?.text = currentRepeat.product
cell.repeatCount?.text = "Repeat: " + String(currentRepeat.currentrepeat) + " of " + String(currentRepeat.totalrepeat)
cell.accessoryType = UITableViewCellAccessoryType.DetailDisclosureButton
cell.checkButton.tag = indexPath.row;
cell.checkButton.addTarget(self, action: Selector("selectItem:"), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func selectItem(sender:UIButton){
println("Selected item in row \(sender.tag)")
}
OPTION 1. Handling it with delegation
The right way of handling events fired from your cell's subviews is to use delegation.
So you can follow the steps:
1. Above your class definition write a protocol with a single instance method inside your custom cell:
protocol CustomCellDelegate {
func cellButtonTapped(cell: CustomCell)
}
2. Inside your class definition declare a delegate variable and call the protocol method on the delegate:
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
3. Conform to the CustomCellDelegate in the class where your table view is:
class ViewController: CustomCellDelegate
4. Set your cell's delegate
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell
cell.delegate = self
return cell
}
5. Implement the required method in your view controller class.
EDIT: First define an empty array and then modify it like this:
private var selectedItems = [String]()
func cellButtonTapped(cell: CustomCell) {
let indexPath = self.tableView.indexPathForRowAtPoint(cell.center)!
let selectedItem = items[indexPath.row]
if let selectedItemIndex = find(selectedItems, selectedItem) {
selectedItems.removeAtIndex(selectedItemIndex)
} else {
selectedItems.append(selectedItem)
}
}
where items is an array defined in my view controller:
private let items = ["Dog", "Cat", "Elephant", "Fox", "Ant", "Dolphin", "Donkey", "Horse", "Frog", "Cow", "Goose", "Turtle", "Sheep"]
OPTION 2. Handling it using closures
I've decided to come back and show you another way of handling these type of situations. Using a closure in this case will result in less code and you'll achieve your goal.
1. Declare a closure variable inside your cell class:
var tapped: ((CustomCell) -> Void)?
2. Invoke the closure inside your button handler.
#IBAction func buttonTapped(sender: AnyObject) {
tapped?(self)
}
3. In tableView(_:cellForRowAtIndexPath:) in the containing view controller class :
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
cell.tapped = { [unowned self] (selectedCell) -> Void in
let path = tableView.indexPathForRowAtPoint(selectedCell.center)!
let selectedItem = self.items[path.row]
println("the selected item is \(selectedItem)")
}
Since you have 1 section in the table view you can get the cell object as below.
let indexPath = NSIndexPath(forRow: tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomCell!
where tag you will get from button tag.
Swift 3
I just get solution for access cell in #IBAction function using superview of button tag.
let cell = sender.superview?.superview as! ProductCell
var intQty = Int(cell.txtQty.text!);
intQty = intQty! + 1
let strQty = String(describing: intQty!);
cell.txtQty.text = strQty
#IBAction func buttonTap(sender: UIButton) {
let button = sender as UIButton
let indexPath = self.tableView.indexPathForRowAtPoint(sender.center)!
}
I updated option 1 of the answer from Vasil Garov for Swift 3
1. Create a protocol for your CustomCell:
protocol CustomCellDelegate {
func cellButtonTapped(cell: CustomCell)
}
2. For your TableViewCell declare a delegate variable and call the protocol method on it:
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
3. Conform to the CustomCellDelegate in the class where your tableView is:
class ViewController: CustomCellDelegate
4. Set your cell's delegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as CustomCell
cell.delegate = self
return cell
}
5. Implement the required method in your ViewController.
Based on Cao's answer, here is a solution to handle buttons in a collection view cell.
#IBAction func actionButton(sender: UIButton) {
let point = collectionView.convertPoint(sender.center, fromView: sender.superview)
let indexPath = collectionView.indexPathForItemAtPoint(point)
}
Be aware that the convertPoint() function call will translate the button point coordinates in the collection view space. Without, indexPath will always refer to the same cell number 0
XCODE 8: Important Note
Do not forget to set the tags to a different value than 0.
If you attempt to cast an object with tag = 0 it might work but in some weird cases it doesn't.
The fix is to set the tags to different values.
Hope it helps someone.