Cell button tableView Swift - swift

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

Related

Seguing from uicollectionview that is inside of a tableview

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.

How to select multiple images from UICollectionView and transfer them to another View Controller?

The code is written in Swift. I'm building a social app where the user can make posts. I'm using Firebase as a backend (database, storage). So, I have a UICollectionView that gets all the photos from the photo library of the device and populate the collection view using a custom cell. In the same View controller, I have another custom cell that the user can use to take a photo and use it to make a post. To make it clearer:
If the user decides to take a photo, when they click on "Use photo" they need to be presented to a new view controller that should display the photo they just took along with other options (such as title, description and tags using UITextFields & UITextView).
If the user decides to select multiple photos from their own library, I have to somehow mark those photos/cells (i.e. using a button for a checkmark), add the selected photos to an array (with some limit, maybe 10 photos top). When they click "Next" button, the array needs to be sent to the New Post View Controller where all the images should be dynamically displayed maybe using a horizontal UICollectionView (?!) (with an option to remove an image if it was selected by accident) and again, as above, have the opportunity to add title, description, etc. Now, I cannot figure out how to do any of that.
I looked for a solution, but I'm kind of stuck on this for couple days now, so help is very much welcome!
Here's what I have in the Collection View controller (PS: I didn't include the part with the function that gets the images from the Photos)
import UIKit
import Photos
class PrePhotoPostVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var nextButton: UIBarButtonItem!
var photosLibraryArray = [UIImage]()
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
checkPhotoLibraryPermission()
setupCollectionViewDelegates()
}
#IBAction func cancelButtonPressed (_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
}
#IBAction func nextButtonPressed (_ sender: UIBarButtonItem) {
nextButton.isEnabled = false
}
#IBAction func takeAphotoButtonPressed (_ sender: UIButton) {
// Camera Autorization
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { response in
if response {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerControllerSourceType.camera;
imagePicker.allowsEditing = false
self.present(imagePicker, animated: true, completion: nil)
}
else {
print("Camera isn't available in similator")
}
}
else {
print("unautorized")
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return photosLibraryArray.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let cellCamera = collectionView.dequeueReusableCell(withReuseIdentifier: cellPrePostCameraCell, for: indexPath)
return cellCamera
}
else {
let cellPhotoLibrary = collectionView.dequeueReusableCell(withReuseIdentifier: cellPrePostPhotoLibrary, for: indexPath) as! PrePhotoPostPhotoLIbraryCell
cellPhotoLibrary.awakeFromNib()
cellPhotoLibrary.photoLibraryImage.image = photosLibraryArray[indexPath.row]
return cellPhotoLibrary
}
}
}
A screenshot of what this UICollectionView looks like:
Here's my code from the Photo Library Cell:
import UIKit
class PrePhotoPostPhotoLIbraryCell: UICollectionViewCell {
// MARK: Outlets
#IBOutlet weak var photoLibraryImage: UIImageView!
// var selectedPhotos = [UIImageView]()
#IBAction func selectedButtonPressed(_ sender: UIButton) {
self.layer.borderWidth = 3.0
self.layer.borderColor = isSelected ? UIColor.blue.cgColor : UIColor.clear.cgColor
}
override func awakeFromNib() {
photoLibraryImage.clipsToBounds = true
photoLibraryImage.contentMode = .scaleAspectFill
photoLibraryImage.layer.borderColor = UIColor.clear.cgColor
photoLibraryImage.layer.borderWidth = 1
photoLibraryImage.layer.cornerRadius = 5
}
}
First of all declare an array of mutable type that will store the selected cells item in it.
var _selectedCells : NSMutableArray = []
then in your viewDidLoad function add below code.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//this will allow multiple selection on uicollectionviewcell
CollectionView.allowsMultipleSelection=true //CollectionView is your CollectionView outlet
}
Then, Implement delegate functions of collectionview for selecting and deselecting cells
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){
//add the selected cell contents to _selectedCells arr when cell is selected
_selectedCells.add(indexPath)
collectionView.reloadItems(at: [indexPath])
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
//remove the selected cell contents from _selectedCells arr when cell is De-Selected
_selectedCells.remove(indexPath)
collectionView.reloadItems(at: [indexPath])
}
I'd suggest saving the NSIndexPath of the selected item in an array, and then using that for the basis of comparison in the delegate function cellForItemAt indexPath.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YOUR_CELL_Identifier", for: indexPath as IndexPath)
//add your tick mark image to the cell in your storyboard or xib file.
let tickImage = cell.viewWithTag(YOUR_IMAGE_TAG_HERE) as? UIImageView
//Show tickImage if the cell is selected and hide tickImage if cell is NotSelected/deSelected.or whatever action you want to perform in case of selection and deselection of cell.
if _selectedCells.contains(indexPath) {
cell.isSelected=true
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: UICollectionViewScrollPosition.top)
tickImage?.isHidden=false
}
else{
cell.isSelected=false
tickImage?.isHidden=true
}
return cell
}
In Order to send items to next controller, get all the items from selected indexpaths.

Pass my indexPath from collectionView witht two different cells for the the indexPath

I'm trying to pass my indexPath from collectionView in to my cell so that I could have two different cells for the the indexPath. I've tried passing instances of the controller and setting it to self in cell. I also tried protocol delegate and that didn't seem to work either. I use delegates quite often so I know I'm doing it right however, in this case my delegate function isn't even being called even tho I set the delegate to self in the Cell. I'm not sure what is happening but nothing seems to be working.
CollectionView VC
protocol ActivityAboutVCDelegtae: class {
func passIndexPath(indexPath:Int)
}
class ActivityAboutVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var delegate:ActivityAboutVCDelegtae?
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.registerClass(ActivityAboutCell.self, forCellWithReuseIdentifier: CELL_ID)
self.collectionView?.pagingEnabled = true
self.collectionView?.alwaysBounceVertical = false
}
// MARK: UICollectionViewDataSource
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(CELL_ID, forIndexPath: indexPath) as! ActivityAboutCell
if delegate != nil {
delegate?.passIndexPath(indexPath.row)
}
return cell
}
CollectionViewCell
import UIKit
class ActivityAboutCell: BaseCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, ActivityAboutVCDelegtae {
lazy var actVc:ActivityAboutVC = {
let vc = ActivityAboutVC()
vc.delegate = self
return vc
}()
func passIndexPath(indexPath: Int) {
print(indexPath)
}
override func setupView() {
super.setupView()
ActivityAboutSetup()
backgroundColor = .whiteColor()
}
1) It is not clear what you want to achieve, but at lest the following solution will give you a way to call you passIndexPath method.
Replace
if delegate != nil {
delegate?.passIndexPath(indexPath.row)
}
with
cell.passIndexPath(indexPath.row)
2) UICollectionView uses items, not rows. This is why the final result should be
cell.passIndexPath(indexPath.item)
3) actVc property looks unnecessary or even wrong.

Tap on cell and get index

When i tap on a cell i want to receive the index or other identifier specific to that cell. The code works and goes in the tapped function. But how can i receive a index or something like that?
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ShowCell", forIndexPath: indexPath) as UICollectionViewCell
if cell.gestureRecognizers?.count == nil {
let tap = UITapGestureRecognizer(target: self, action: "tapped:")
tap.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
cell.addGestureRecognizer(tap)
}
return cell
}
func tapped(sender: UITapGestureRecognizer) {
print("tap")
}
Swift 4 - 4.2 - Returns index of tapped cell.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellIndex = indexPath[1] // try without [1] returns a list.
print(indexPath[1])
chapterIndexDelegate.didTapChapterSelection(chapterIndex: test)
}
Build on Matts answer
first off we add the tap recognizer
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapped(tapGestureRecognizer:)))
cell.textView.addGestureRecognizer(tap)
Then we use the following function to get the indexPath
#objc func tapped(tapGestureRecognizer: UITapGestureRecognizer){
//textField or what ever view you decide to have the tap recogniser on
if let textField = tapGestureRecognizer.view as? UITextField {
// get the cell from the textfields superview.superview
// textField.superView would return the content view within the cell
if let cell = textField.superview?.superview as? UITableViewCell{
// tableview we defined either in storyboard or globally at top of the class
guard let indexPath = self.tableView.indexPath(for: cell) else {return}
print("index path =\(indexPath)")
// then finally if you wanted to pass the indexPath to the main tableView Delegate
self.tableView(self.tableView, didSelectRowAt: indexPath)
}
}
}
Think about it. The sender is the tap gesture recognizer. The g.r.'s view is the cell. Now you can ask the collection view what the index path of this cell is (indexPathForCell:).

How to access the content of a custom cell in swift using button tag?

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.