How to reference outlet inside custom cell from a different class (Swift) - swift

I have a tableView with a custom cell that has a UITextView inside it and I want to make the UITextView of the last cell in the tableView the firstresponder when a button is pressed. However, I'm not sure how to reference that UITextView inside the cell.
It has an outlet in the custom cell class, but I don't know how to use this button (an action in another class) to reference it.
class ActionCell: UITableViewCell {
#IBOutlet weak var label: UITextView!
}
What I tried is this, but it doesn't work:
#IBAction func addAction(_ sender: Any) {
let cell = tableview.cellForRow(at: IndexPath.init(row: -1, section: 1)) as? ActionCell
let textview = cell?.contentView.subviews.filter{$0 is UITextView}
textview![0].becomeFirstResponder()
}

Related

How can I get the button title for each selected cell in a UICollectionView?

I have a collectionView of buttons as pictured below. I want to be able to select multiple of these cells, and in doing so pass the title of each selected button into an array of Strings.
UICollectionView - each cell has a button with a title
The UICollectionView is in WordViewController class
class WordViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
And the UICollectionViewCell is in it's own file.
import UIKit
class WordCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var wordView: UIButton!
#IBAction func buttonTapped(_ sender: UIButton) {
if wordView.isSelected == true {
wordView.isSelected = false
wordView.backgroundColor = UIColor.blue
}else {
wordView.isSelected = true
wordView.backgroundColor = UIColor.green
}
}
}
I'm very new to Swift and I have been trying to find an answer to this for days, but I can't figure it out. I suspect I may have to use indexPathsForSelectedItems but I have tried this and can't get it working.
func indexSelected () {
let collectionView = self.collectionView
let indexPath = collectionView?.indexPathsForSelectedItems?.first
print(indexPath!)
let cell = collectionView?.cellForItem(at: indexPath!) as? WordCollectionViewCell
let data = cell?.wordView.currentTitle
print(data!)
}
I'm not sure if I have something fundamental wrong in the way I have set up my CollectionView or if it is something to do with me using buttons within the CollectionViewCells.
Any help would be very appreciated.
This is one way you could do it. First get the indexPaths for the selected cells. Then loop through the indexPaths and get your cell for each IndexPath (cast them as your custom CollectionViewCell to access your button). Now you can append each title to an array to save them.
var titleArray = [String]()
guard let selectedIndexPaths = collectionView.indexPathsForSelectedItems else { return }
for indexPath in selectedIndexPaths {
if let cell = collectionView.cellForItem(at: indexPath) as? WordCollectionViewCell {
self.titleArray.append(cell.yourButton.titleLabel?.text)
}
}
Welcome to SO. This sounds a bit like an X/Y problem: A case where you are asking about how to implement a specific (often sub-optimal) solution rather than asking about how to solve the problem in the first place.
You should not treat the views in your collection view as saving data. (buttons are views.)
You should use the button to figure out the indexPath of the cell the user tapped and then look up the information in your data model.
You should set up an array of structs (or an array of arrays, if your collection view is in sections of rows.) Each of those structs should contain the current settings for a cell.
Your collectionView(_:cellForItemAt:) method should use the array of structs to configure your sell for display.
As the user taps buttons and selects cells, you should update the struct(s) at the appropriate IndexPath(s) and then tell the collection view to update those cell.
If you need to do something with the selected cells, you should ask the collection view for an array of the selected cells, you should use those IndexPaths to index into your model array and fetch the struct for each IndexPath, and then look up teh data you need.
EDIT:
You can use a really simple extension to UICollectionView to find the indexPath of any view inside your collection view (and a button is a view, as mentioned...)
extension UICollectionView {
func indexPathForCellContaining( view: UIView) -> IndexPath? {
let viewCenter = self.convert(view.center, from: view.superview)
return self.indexPathForItem(at: viewCenter)
}
}

How to connect UITableViewCell button to perform UITableView functions?

I have an app which utilizes a tableView which contains a prototype cell. This prototype cell contains a few labels and a button (connected to that cell's UITableViewCell file). If that's not clear, this is what I mean:
class ContactsCell: UITableViewCell { //this custom cell is used as the prototype cell for the tableView
#IBOutlet weak var firstNameLabel: UILabel!
#IBOutlet weak var lastNameLabel: UILabel!
#IBOutlet weak var numberLabel: UILabel!
#IBOutlet weak var indexPathRowLabel: UILabel!
#IBOutlet weak var replaceContactButtonUI: UIButton!
#IBAction func replaceContactButtonTapped(_ sender: Any) {
//THIS is the button in question
}
The reason for this is because I'd like each cell in the tableView to have all of those labels (and the button), but with each row having different values in the labels. These values are handled by the cellForRowAt tableView function, via a system as follows:
Pressing a button (not the button in the cells I was talking about... a different button that is at the top of the VC, outside of the tableView) brings up the contact picker:
func selectContact(){//allows user to bring up contactPicker
let contactPicker = CNContactPickerViewController()
contactPicker.displayedPropertyKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
contactPicker.delegate = self
contactPicker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.#count >= 0")
contactPicker.predicateForSelectionOfContact = NSPredicate(format: "phoneNumbers.#count >= 1")
self.present(contactPicker, animated: true, completion: nil)
}
#IBAction func selectContactsButton(_ sender: Any) {
selectContact()
//button brings up the picker to allow selection of contact
}
Selecting one of the contacts updates an array which is used by the tableView to update the value of the labels:
func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
contactArray.append(contactProperty.contact)
print(contactArray)// contactArray = [CNContact]()
contactsTableView.reloadData()// reloads the value of the labels
dismiss(animated: true)
}
These all work as intended. However, I run into a bit of an issue with the button that is in each cell. This button is intended to allow the replacement of a the CNContact in the tableView at the row of which the button was pressed... (ie if there are 4 rows, there are 4 "replace" buttons. Clicking the "replace" button of the third row replaces the CNContact at the 2nd index of the contactArray). This is where my issue is, though. Since the button, and its action, lives in the UITableViewCell's swift file, and not in the tableViewControllers, it doesn't have access to the contactArray to remove and append, nor the picker functions to allow the selection of the new contact, nor the tableView itself to run .reloadData(). How do I 'give access' to the UITableViewCell's swift file to utilize things from the tableView controller that it is a part of/connected to?
Note Its possible that the entire premise of this question is wrong and that I should've actually placed the button's action elsewhere (not in the UITableViewCell's swift file). If this is the case, where should I put it?
Since you have all the information in your ViewController, I would suggest moving your #IBAction into your ViewController as well. If you are worried about referencing the correct cell, you can set the tag value of your UIButton in cellForRow method. Then in #IBAction, you can use that value to access the correct index in the array as well as the cell itself
you can give selector to button in cellforrowatindexpath:
cell.replaceContactButtonUI.tag = index.row
cell.replaceContactButtonUI.addTarget(self, action: #selector(self.
selectContact(sender:)), for: .touchUpInside)
func selectContact(sender:UIButton){}
From button tag you can find the cell index by sender.tag.

How do I use UIStepper to change a property in a Realm Model Object?

I currently have a ViewController with a TableView inside it called SelectedListItemsViewController. This ViewController's TableView is populated by a custom cell class called SelectedListItemsTableViewCell.
I have an array of Realm Model Objects called selectedListItems, each of which has several properties. The SelectedListItemsTableViewCell populates the TableView with the listItem property of that indexPath.row's object, and each row has a UIStepper with a UILabel next to it that (as of now) shows UIStepper.value for each row. Ideally, the label will reflect the listItemWeight property of each row, and change it when incrementing or decrementing that row.
This is my custom cell:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
}
}
And in my ViewController's cellForRowAtIndexPath, I've configured the cell like so:
// Configure the cell...
cell.selectedListItemLabel.text = selectedListItems[indexPath.row].listItem
cell.listItemWeightLabel.text = "\(selectedListItems[indexPath.row].listItemWeight)"
Which perfectly loads the listItem property, and the listItemWeight property shows up correctly, but as soon as I increment or decrement on the UIStepper it gets messed up.
How do I properly link my UILabel and UIStepper to the [indexPath.row].listItemWeight?
In the same method that gets called when your stepper updates, update your listItem. However, since this item is stored in your Realm database, you will have to get an instance of your Realm database and write the change to the database.
You can do this by having your TableViewCell hold on to an instance of the listItem. Your new TableViewCell class will look something like this:
class SelectedListItemsTableViewCell: UITableViewCell {
#IBOutlet var selectedListItemLabel: UILabel!
#IBOutlet weak var listItemWeightLabel: UILabel!
#IBOutlet weak var stepperControl: UIStepper!
var listItem: Item?
#IBAction func stepperValueChanged(sender: UIStepper) {
listItemWeightLabel.text = Int(sender.value).description
if let listItem = listItem {
let realm = try! Realm
try! realm.write {
listItem.listItemWeight = Int(sender.value) ?? 0
}
}
}
}
The above answer was helpful in leading me to the actual solution of my issue, but if anyone in the future is curious - I ended up using a closure.
In my custom cell class, I did
#IBAction func stepperValueChanged(sender: UIStepper) {
selectedListItemLabel.text = Int(sender.value).description
tapped?(self)
}
And in my view controller, I did
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! SelectedListItemsTableViewCell
cell.stepperControl.value = selectedListItems[indexPath.row].listItemWeight
// Configure the cell...
cell.tapped = { (selectedCell) -> Void in
selectedListItems[indexPath.row].listItemWeight = cell.stepperControl.value
}
Which allowed me to access each cell's UIStepper in the view controller file.
It helped to read flashadvanced's Option 2 answer in this thread.

How would I unhide a button if user performs segue in swift?

I have a button inside a cell (PFQueryTableViewController) that is hidden and I want to unhide it when the user performs a certain segue that I call programatically.
When the user taps the cell it segues to a view controller which displays the contents of the cell full screen... I want the button to unhide in this cell when the segue is called so when the user goes back to the table of cells they can see it on the cell they just tapped.
How can I do this?
Edit after questions:
inside cellRowForIndexPath I have the following for the button
cell.myButton.tag = indexPath.row
cell.myButton.addTarget(self, action: "pressed:", forControlEvents: UIControlEvents.TouchUpInside)
cell.myButton.hidden = true
And the segue itself carries information from the cell (stored in Parse backend) across to FullPostViewController from AllPostsTableViewController. The code for that is this (would I call the unhide in here somewhere?):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showFullPost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showFullPost" {
let indexPath = self.tableView.indexPathForSelectedRow
let fullPostVC = segue.destinationViewController as! FullPostViewController
let object = self.objectAtIndexPath(indexPath)
fullPostVC.post = object?.objectForKey("postContent") as? String
let likeCount = object!.objectForKey("likedBy")!.count
fullPostVC.likesCounted = String(likeCount)
self.tableView.deselectRowAtIndexPath(indexPath!, animated: true)
}
}
(Answer thoroughly edited after thorough edit of question)
One possible solution follows below.
Since you mention table cells (each containing a button; I'll assume UIButton), I assume you populate your table view cells with UITableViewCell objects; some fancy subclass to the latter. In this class:
If you haven't already, create an #IBOutlet from your button as a property in this class.
Overload the method setSelected(...) to un-hide your button in case the UITableViewCell is selected (which will precede the segue)
Hence, in your UITableViewCell subclass, you should be able to do something along the lines:
// ...TableViewCell.swift
Import UIKit
// ...
class ...TableViewCell: UITableViewCell {
// Properties
#IBOutlet weak var button: UIButton!
// button contained in UITableViewCell
// ...
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// If table cell is selected (after which segue will follow),
// un-hide button.
if (selected) {
button.hidden = false
}
}
}
Hopefully this will achieve your goal.

Referencing different buttons in cell on IBAction

When a button is clicked (thats located inside the cell of a tableview), I pass a reference to it to another class. However, along with it I also want to pass a reference to another button in the same cell. I can easily reference the clicked button as "sender", but how would I reference the other button in the same cell?
This is in the cell subclass, not the main view controller.
class ImageTableViewCell: UITableViewCell {
#IBOutlet weak var upVote: UIButton!
#IBOutlet weak var downVote: UIButton!
#IBAction func upVote(sender: AnyObject) {
self.delegate?.controller(self, button: sender as! UIButton, other: *REFERENCE DOWNVOTE BUTTON HERE*, selectedButtonIndexPath: indexPath!)
}
}
The cell has a property for each button.
Since it appears that you have separate action methods for each button, the other button is known to you and you can pass it directly:
#IBAction func upVote(sender: AnyObject) {
self.delegate?.controller(self, button: sender as! UIButton, other: downVote, selectedButtonIndexPath: indexPath!)
}
If you don't know which button is which, you can determine the opposite button by
let other = sender === self.upVote ? self.downVote : self.upVote