Referencing a textfield from a cell in UITableViewController - swift

I've created a tableview with five cells in Xcode 8. Each cell has its own label and textfield. I want to be able to access the user input from these textfields in a separate tableViewController file/class and then do stuff with the user input.
I've created a swift file/class for each cell.
I've made each of the classes public.
And, when creating the IBOutlet for each of the textfields, I've made those conform to the following format:
public class NameCell: UITableviewCell {
#IBOutlet public weak var NameTextField!
override public func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
What I think I need to do is declare a variable in the viewcontroller class for the textfields in each class. I was wondering if this can be done? And if so, how do I declare the variables/textfields from another class? Or is there a better way?
I was thinking something along the lines of:
var NameTextField.NameCell = ""
Or
var NameCell.NameTextField = ""
But these are obviously wrong. I've also put the textfield delegate in the TableViewController class rather than the NameCell class. I don't know if this is contributing to the issue or not.
Any help would be much appreciated!

Make sure your IBOutlet variable is declared correctly. It should be:
#IBOutlet weak var textField: NameTextField!
Now, to access the text from the cell's textField, you first need to access the cell itself. You can call var cell = tableView.cellForRow(at: indexPath) as! NameCell where indexPath is that of the cell you are attempting to access. Then, you can call cell.textField.text. This will give you the text that the user has entered into the textField of the given cell.
Also, make sure to read up on swift basics to understand the proper usage of variables and type annotations, as well as proper naming conventions.

Related

Protocol Doesn't Send Value to Other VC

That is my footerView called FooterTableViewCell. I have this protocol called SurveyAnswerTableViewCellDelegate. It's parent is AddQuestionViewController.
When I tap on the footerView I trigger #IBActtion.
#objc protocol SurveyAnswerTableViewCellDelegate: AnyObject {
func textSaved(_ text: String)
}
class FooterTableViewCell: UITableViewHeaderFooterView {
var parentVC: AddQuestionViewController!
#IBAction func addNewTapped(_ sender: Any) {
print("tapped")
let newTag = model.tag + 1
parentVC.addNewAnswer()
}
This button action triggers AddQuestionViewController
class AddQuestionViewController: SurveyAnswerViewDelegate, UITextFieldDelegate, UITableViewDelegate, SurveyAnswerTableViewCellDelegate {
var answers: [SurveyAnswerModel] = []
var savedText : String = ""
static var delegate: SurveyAnswerTableViewCellDelegate?
I try creating an empty string and append a new answer to my array. But this text here is always "".
func addNewAnswer() {
let newAnswer = SurveyAnswerModel(answer: savedText, tag: 0)
self.answers.append(newAnswer)
self.tableView.reloadData()
}
func textSaved(_ text: String) {
savedText = text
}
The textfield I try to read is inside SurveyAnswerTableViewCell while setting up the cell inside the tableview I call setup function.
class SurveyAnswerTableViewCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
weak var delegate: SurveyAnswerTableViewCellDelegate?
var parentVC: AddQuestionViewController!
func setup() {
if let text = self.textField.text {
self.delegate?.textSaved(textField.text!)
}
}
extension AddQuestionViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SurveyAnswerTableViewCell
cell.parentVC = self
cell.setup()
return cell
}
How can I successfully send that text to AddQuestionViewController so it appends a new answer with correct string
There are a few things keeping this from working.
You are calling SurveyAnswerTableViewCell's setup() function directly after dequeuing the cell for reuse. It has not yet (re)appeared on the screen at that point, so the user has not had a chance to enter anything into the text field.
You don't currently set the delegate property of SurveyAnswerTableViewCell to anything, so even if the textfield had valid input, the delegate would be nil and delegate?.textSaved(textField.text!) wouldn't do anything.
Both of the previous points mean that the value of AddQuestionViewController .savedText never gets updated from the empty string. So when addNewAnswer() tries to read it, it will always see that empty string.
Rather than reading the text field when the cell is dequeued, it would make more sense to save the text field value when the user is done typing.
To do that, conform the cell to UITextFieldDelegate and implement the textFieldDidEndEditing(_:) method. From within that method you can then call the delegate method you already have to save the text. Make sure the delegate property on the cell has been set by the VC, or else this won't do anything!
The VC itself should not have a delegate property of type SurveyAnswerTableViewCellDelegate. It serves as the delegate, rather than having one. If this doesn't quite make sense, I would recommend reviewing some online resources on the delegate pattern.
So make sure the ViewController conforms to SurveyAnswerTableViewCellDelegate and then set the cell's delegate value to the VC. The cellForRowAt function should then look something like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SurveyAnswerTableViewCell
cell.delegate = self
return cell
}
As a side note, neither the footer nor the cell should have a reference to the parent view controller. as a general rule it is good to avoid subviews being aware of their parent views. Things get unnecessarily complicated when there is two-way knowledge sharing between components, and it makes the subview much less reusable. I would recommend making a delegate for the footer as well, and removing the parentVC property from both the footer and the cell.
Here's what it looks like is happening:
Button tapped
addNewTapped(_:) invoked
addNewAnswer() invoked
newAnswer is appended to answers
tableView.reloadData() invoked
Cells are regenerated with new/empty textfields (so delegate.textSaved is never invoked)
so I'm not sure what you're trying to do, but here's what I figure are a couple possible routes:
store UITextFields separately and add them into table cells so they're not removed by a table reload
conform AddQuestionViewController to UITextFieldDelegate and set it as the textfields' delegate to observe textfield texts changing (and if you're only using 1 textfield, you could set savedText there)

Swift add button to UICollectionViewCell that opens new controller

I have a UICollectionViewCell header on a UICollectionViewController, and I've added a button to it. I would like for the button, when clicked, to push a new view controller atop the current one. The problem is that the button doesn't have access to the navigation controller of the UICollectionViewController, so I there's no way to directly push a controller from, say, a connector to the buttn (that I know of). Is there any way to achieve this? Maybe something can be overriden, such as a collectionView function. Thanks!
If you just want to process the cell selection there is a handy method in the UICollectionViewDelegate that you can implement to get the index path of the pressed cell.
If your goal is to have a custom button inside the cell (or maybe even several) you can use delegation pattern to retrieve user actions to your controller to than process in any way, including pushing/presenting new controllers. Assign the controller's instance (the one managing the collection view) to the delegate member of your cell.
Define a protocol that I would call something like MyCustomCellDelegate (replace MyCustomCell with a more appropriate name for your case). Something like MyCustomCellDelegate: class { func didPressButtonX() }
Declare an optional delegate property in your cell subclass. weak var delegate: MyCustomCellDelegate?
Implement your delegate protocol by the class you want to respond to button presses (or any other interactions defined by your protocol).
Every time you create/dequeue a cell for your UICollectionView to use you set the delegate property to the view controller managing the collection view. cell.delegate = self (if done inside the view controller itself).
After receiving the UI event inside your custom cell use your delegate property to retrieve the action to the controller (or with ever object you used when assigning the property). Something like: delegate?.didPressButtonX()
In your class that implements MyCustomCellDelegate use the method to push the new controller.
Below I will provide sample code that should give more details on the implementation of the proposed solution:
// In your UICollectionViewCell subclass file
protocol MyCustomCellDelegate: class {
func didPressButtonX()
func didPressButtonY()
}
MyCustomCell: UICollectionViewCell {
weak var delegate: MyCustomCellDelegate?
#IBOutlet var buttonX: UIButton!
#IBOutlet var buttonY: UIButton!
#IBAction func didPressButtonX(sender: Any) {
delegate?.didPressButtonX()
}
#IBAction func didPressButtonY(sender: Any) {
delegate?.didPressButtonY()
}
}
// Now in your UICollectionViewController subclass file
MyCustomCollectionViewController: UICollectionViewController {
// ...
override func collectionView(UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier identifier: "YourCellIdentifierGoesHere", for indexPath: indexPath) as! MyCustomCell
// In here we assign the delegate member of the cell to make sure once
// an UI event occurs the cell will call methods implemented by our controller
cell.delegate = self
// further cell setup if needed ...
return cell
}
}
// In order for the instance of our controller to be used as cell's delegate
// we implement the protocol that we defined earlier in the cell file
extension MyCustomCollectionViewController: MyCustomCellDelegate {
func didPressButtonX() {
print("X button was pressed")
// now lets finally push some new controller
let yourNextCoolViewController = UIViewController()
self.push(yourNextCoolViewController, animated: true)
// OR if you are using segues
self.performSegue(withIdentifier: "YourSegueIdentifierGoesHere", sender: self)
}
func didPressButtonY() {
print("Y button was pressed")
}
}

Swift3 Multiple buttons dynamic coding to trigger event

I have multiple buttons. Each button contains a language label.
I want to make it so that when users tap on the button, the selected language label changes its value according to the button tapped.
The selected language outlet is called SelectedLangText.
A simple solution would be to create multiple Action outlets for each button and set the value of SelectedLangText label. However, if there would be 100 buttons, that would be bad coding.
I'm not sure how to approach this situation in Swift 3 coming from web development.
I prefer using the delegate design pattern when it comes to solving an issue like that for it I find it to be a much cleaner approach than just a mass amount of #IBActions
1- Create a Language class
import Foundation
class Language {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
2- Create the custom cell in the storyboard or nib and then add the appropriate outlets and actions. And then you create a Delegate protocol for it
import UIKit
protocol CustomCellDelegate: class {
func customCell(newLanguageSelected language: Language)
}
class CustomCell: UITableViewCell {
var language: Language!
#IBOutlet weak var languageTextLabel: UILabel!
weak var delegate: CustomCellDelegate?
func setupCustomCell(withLanguage language: Language){
self.language = language
self.languageTextLabel.text = self.language.name
}
#IBAction func buttonPressed(sender: UIButton){
delegate?.customCell(newLanguageSelected: self.language)
}
}
3- Finally add the implementation in the cellForRow method of the UITableViewDataSource and add the implementation of the delegate in the UITableViewController class
import UIKit
class YourTableViewController: UITableViewController{
var languages: [Language] = []
//implement the other methods in the dataSource
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuse", for: indexPath) as! CustomCell
cell.delegate = self
cell.setupCustomCell(withLanguage: languages[indexPath.row])
return cell
}
}
extension YourTableViewController: CustomCellDelegate{
func customCell(newLanguageSelected language: Language) {
//Do what you will with the language
}
}
Good luck
You can add Tags to each button and Set same IBAction for all buttons.
First in your method get the language based on the button.
Now use loop(go with for loop as you will need proper index for each button) and get all buttons from its tag and set language tag.
A bit complex at glance, but will solve your problem and good solution in my eye.
for index in 101...103 {
let myBtn = self.view.viewWithTag(index) as! UIButton
myBtn.setTitle("localisedtitle string", for: .normal)
}

Getting Ordered TextField Data From Dynamically Created Cells in Swift (3.1) Table View

I am trying to figure out how to get textfield data from cells in a tableview in an ordered fashion. So far I am able to type into each textfield a retrieve the type data. To do so in the correct order though the user must edit the first cell...last cell, any other way and the data isn't in the correct order.
For Example:
I have the program create 5 cells with a textfield,
textfield1: I typed here second
textfield2: I typed here fourth
textfield3: I typed here first
textfield4: I typed here fifth
textfield5: I typed here third
the way I currently have it my dataArray would look identical to this one, because it is being stored based on when it is typed in and not the order of the cells.
I would like to type the above example, but my data come out like this:
textfield1: I typed here first
textfield2: I typed here second
textfield3: I typed here third
textfield4: I typed here fourth
textfield5: I typed here fifth
Here is my textfield editing code:
#IBAction func TitleEditBegin(_ sender: UITextField) {
}
#IBAction func TitleEditEnd(_ sender: UITextField) {
print(sender.tag) // Debug
titleArray.append(sender.text!)
}
I know for the time being that any other changes will be appended to the titleArray, but I want to solve the ordering issue first.
Thanks!
EDIT: I forgot to add in how I am creating the cells, the code is below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = TitleSessionTableView.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath) as! TitleSessionCell
cell.SessionTitleLabel.text = "Title"
// cell.SessionTitleField.text = "Default"
cell.SessionTitleField.tag = indexPath.row
cell.SessionTitleField.delegate = self
print(indexPath.row) // Debug
return cell
}
EDIT 2: Adding where I define the text fields.
import Foundation
import UIKit
class TitleSessionCell: UITableViewCell{
#IBOutlet weak var SessionTitleField: UITextField!
#IBOutlet weak var SessionTitleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
The easiest way would be to use a dictionary I believe. I would assign all of the textFields a different tag. So for textField1 you would say textField1.tag = 1. I don't see the creation of your text fields, so it is hard to show the best way of adding that in, but then after handling that, I would create the dictionary as a class variable.
var textFieldDictionary: [Int: String] = [:]
and then add in the text to it like so:
if sender.text != nil && sender.text != "" {
textFieldDictionary[sender.tag] = sender.text
}
then when you want to retrieve the information, do something like this:
for i in 0..<biggestTextFieldNumber {
if let text = textFieldDictionary[i] {
//do something with text
print(text)
}
}
or you could just grab the specific number values out whenever you needed them by using:
textFieldDictionary[numberYouWant]
I hope this helps!

How do I assign an IBOutlet a value using an Initializer?

I am having difficulty with something that I feel like should be working, I am wondering if there are some things that I'm not understanding about initializing values of IBOutlets.
So I've created a class that has three IBOutlets. I then created initializers for those outlet variables. When I create a new instance of the class and pass it hard-coded values I get nil in return.
So I've println()'ed the incoming settingLabel value to make sure incoming parameter indeed carries a value, it comes back as "hello" as to be expected. Then I attempt to assign the value to the settingsLabel.text, however when I call println(settingsLabel.text) comes back as nil.
(Thread 1: EXV_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0))
My Question is:
What am I missing here and/or How do I assign an IBOutlet a value with an initializer?
This is my call to the initializer:
let cell = SettingCell(settingLabel: "hello", settingSwitch: true, timeSetting: 3)
This is my SettingCell.swift class:
class SettingCell: UITableViewCell {
#IBOutlet weak var settingsLabel: UILabel!
#IBOutlet weak var settingsSwitch: UISwitch!
#IBOutlet weak var timeSetting: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
init(settingLabel: String, settingSwitch: Bool, timeSetting: NSNumber) {
println(settingLabel) // returns hello <--------------------
super.init(style: .Default, reuseIdentifier: "SwitchSettingCell")
settingsLabel.text = settingLabel
self.settingsSwitch.on = settingSwitch
self.timeSetting.text = timeSetting.description as String
println(settingsLabel.text) // returns nil <--------------------
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var cellDelegate: SettingCellDelegate?
#IBAction func handledSwitchChange(sender: UISwitch) {
self.cellDelegate?.didChangeSwitchState(sender: self, isOn:settingsSwitch!.on)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
myStoryboard:
The IBOutlet keyword is just a way to tell Xcode that the view should be visible inside your storyboard. You can connect these outlets in your storyboard, and in doing so cause the variables to be instantiated & added as subviews from the storyboard instead of in code.
In your code you are creating your cells programmatically, not from a storyboard (indeed if you tried to create them from a storyboard your app would crash, since you haven't implemented init(coder:)). Because of this, your variables have no reason to be "outlets" and are not ever initialised. You must initialise them yourself, add them as subviews & setup constraints, all in code.
In your initialiser, your code is ineffectual because your variables are still nil. In fact, in your edit, you removed the optional chaining ?s which will have the effect of crashing your app, since the variables are force unwrapped, but are nil. This is the same reason println(settingsLabel.text) is crashing.
Edit:
Ok, so your issue is that you want to initialise your cells from the storyboard, but you're instead doing it programmatically by calling your custom initialiser. In tableView(_:cellForRowAtIndexPath:), you get your cells by dequeuing one from the reuse queue:
let cell = tableView.dequeueReusableCellWithIdentifier("SettingsCell", forIndexPath: indexPath) as! SettingCell
(This assumes you have given your cell prototype the identifier "SettingsCell" in the storyboard)
Whenever a view is created from a storyboard, the view's init(coder:) initialiser is used, so you must provide an implementation of this in your SettingCell subclass. Once the cell has been dequeued, the cell's outlets will be initialised and added as subviews of the cell.
So after dequeuing a cell, you can setup your labels and switch:
cell.settingsLabel.text = //...
cell.settingsSwitch.on = //...
cell.timeSetting.text = //...