How do I get the row number of the cell where the UITextView was edited? - swift

I have a UITextView inside each cell of a UITableView
I am using Core Data to save data which is typed in the UITextView
I would like to save the text typed in UITextView once the user is done editing it
I have added UITextViewDelegate to my TableViewCell class
I am using Notifications to post the new text to the TableViewController
I am able to get the new text to the TableViewController but I don't know how to get the row number of the cell that contained the textview wherein the text was typed. I need to know the row number (or the object in that row) to update the correct NSManagedObject.
What I have Tried:
I was thinking about using tags but since I need to constantly add and delete rows it wouldn't be the best approach
I have tried using DidSelectedRowAtIndexPath but it doesn't get triggered while the
user taps the UITextView (UITextView covers up to 80% of the one cell)
In the TableViewCell class, I have:
func textViewDidEndEditing(_ textView: UITextView) {
// Post notification
let userInfo = [ "text" : textView.text]
NotificationCenter.default.post(
name: UITextView.textDidEndEditingNotification,
object: nil,
userInfo: userInfo as [AnyHashable : Any])
}
In the TableViewController, I have:
//Subscribe
NotificationCenter.default.addObserver(self,
selector: #selector(textEditingEnded(notification:)),
name: UITextView.textDidEndEditingNotification,
object: nil)
#objc func textEditingEnded(notification:Notification) {
guard let text = notification.userInfo?["text"] as? String else {return}
print ("text: \(text)")
}
Don't hesitate to ask for more details.
I'll appreciate every bit of help I can get!

Create a property of the NSManagedObject type in the table view cell.
In Interface Builder connect the delegate of the text view to the cell.
In the controller pass the appropriate data source item in cellForRowAt to the cell.
Delete the observer and instead of posting a notification change the attribute in the NSManagedObject instance directly and save the context.
As NSManagedObject instances are reference types the changes will persist.

I hope you can have variable inside your UITableViewCell subclass for certain item
var item: Item?
then in cellForRowAt set certain item for certain cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
...
cell.item = array[indexPath.row]
...
}
now you can implement UITextViewDelegate to your cell subclass and you can use method textViewDidEndEditing for handling when user is done with typing
class YourCell: UITableViewCell {
...
var item: Item?
...
override func awakeFromNib() {
yourTextView.delegate = self
}
}
extension YourCell: UITextViewDelegate {
func textViewDidEndEditing(_ textView: UITextView) {
... // here save text and here you can use variable `item`
}
}

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)

Xcode 8.3.3 Swift 3 - Variable not updating between files

I'm having a issue with xcode, since I updated to 8.3.3. I usually work with the MCV (Model - View - Controller) method, and now, my variables aren't updating between them.
Situation: I Have a Model (store all major variables and calculations functions); a TableView Controller (Control Tableview) and TableViewCell (set Outlet and actions)
Goal: When a button is pressed in a cell, it should add a row in TableView.
Problem: Why isn't table view getting the new value of Model() variable.
To make it better to understand, here is a timeline of what is going on :
Run > run viewDidLoad in TableViewController > update variable test in Model() > cellForRowAt is called and prints ["1"] > show tableView with 1 row > press button > print ["1"] > add ["2"] to Model() > print ["1","2"] > post notification > viewDidLoad gets Notification and prints "reloading table" > cellForRowAt is called and prints ["1"] > tableView keeps 1 row.
Here is one example of my code:
I have my Model.Swift:
class Model {
var test : [String] = []
}
My TableViewController:
class BudgetTableViewController: UITableViewController {
let model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.test.append("1")
center.addObserver(forName: NSNotification.Name(rawValue: "reloadTableVIew"), object: appDelegate, queue: queue) {[unowned self] (_) in
print("reloading table")
self.tableView.reloadData()
}
}
To simplify.. in Sections I keep returning "1" and for rows I count the variable test in Model().
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(model.test) // **ALWAYS PRINT ["1"]**
let cell = tableView.dequeueReusableCell(withIdentifier: "buttonCell", for: indexPath) as! TableViewCell
return Cell
}
And my TableViewCell:
class TableViewCell: UITableViewCell{
let model = BudgetModel()
let notification = Notification(name: Notification.Name(rawValue: "reloadTableVIew"), object: appDelegate)
#IBAction func okButton(_ sender: UIButton) {
print(model.test)
model.test.append("2")
print(model.test)
NotificationCenter.default.post(notification)
}
}
I hope it is clear enough.
The point is, if I press the button 5 times, it will add the string 5 times to the array (that is confirmed in the print) but when cellForRowAt is called, it will print ["1"] always.
THank you for your help
The two model vars are in different classes. You have var model in your viewController, & var model in your cell. Changing one isn't going to affect the other. You'd be best to implement a delegate for your cells, where the viewController is the delegate, and the cell calls it when pressed -
protocol MyCellDelegate: class {
func cellWasPressed()
}
In the cell -
weak var delegate: MyCellDelegate?
Set this to the viewController when you create the cell.
Then in the button pressed method, add -
self.delegate?.cellWasPressed()
In the viewController, implement this -
func cellWasPressed() {
self.model.test.append("2")
self.tableView.reloadData()
}
I don't think you need to be using a notification, this is far simpler.

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)
}

Using the contentView property on a custom tableView cell (being passed as a header) how to prevent it from nullifying the custom attributes?

For example here is my custom cell:
protocol SectionHeaderTableViewCellDelegate {
func didSelectUserHeaderTableViewCell(sectionHeader: SectionHeaderTableViewCell, selected: Bool, type: Type)
}
class SectionHeaderTableViewCell: UITableViewCell {
#IBOutlet weak var labelContainerView: LabelContainerView!
#IBOutlet weak var sectionTitleLabel: UILabel!
#IBOutlet weak var plusButton: UIButton!
var type: Type?
var delegate: SectionHeaderTableViewCellDelegate?
var dog: Dog?
let sections = [Type.Meals, Type.Exercise, Type.Health, Type.Training, Type.Misc]
}
extension SectionHeaderTableViewCell {
#IBAction func plusButtonPressed(sender: AnyObject) {
if let type = type {
delegate?.didSelectUserHeaderTableViewCell(self, selected: plusButton.selected, type: type )
}
}
In my controller if I add a return of header.contenView I get the desired results of the header staying in place but unfortunately it nullifies the button included in the custom header preventing it from being called. Otherwise if I simply just return header the button on the custom header cell works as expected but the header moves with the row being deleted which is obviously unsightly and not what I want.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let header = tableView.dequeueReusableCellWithIdentifier("sectionHeader") as? SectionHeaderTableViewCell else { return UITableViewCell() }
header.delegate = self
header.updateDogWithGender(dog)
header.type = header.sections[section]
header.sectionTitleLabel.text = header.sections[section].rawValue
return header.contentView
}
moving headers
In case anyone runs into a similar situation the solution was to create a Nib file and customize it as you see fit. Create a nib file by going to File -> New File -> iOS -> User Interface -> and selecting View. Create Nib file. I added my views and buttons to get the look I wanted. customize Nib. From there I changed the custom cell class to be UITableViewHeaderFooterView instead and reconnected my outlets and actions to the new Nib file.
class SectionHeaderView: UITableViewHeaderFooterView {... previous code from above }
Back in the controller update the viewForHeaderInSection function to load a nib instead :
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = NSBundle.mainBundle().loadNibNamed("SectionHeader", owner: self, options: nil).first as? SectionHeaderView
header?.delegate = self
header?.updateDogWithGender(dog)
header?.type = header?.sections[section]
header?.sectionTitleLabel.text = header?.sections[section].rawValue
return header
}
By the way we declared the property first at the end of the loadNibNamed property because it returns an array of AnyObjects and since my Nib file only contains one UIView that houses a label and a button I only needed the first and only item in the array. Thanks to my mentor James for figuring this out!

UISwitch in TableView in Switch

Hello fellow programmers! I have a challenge I need help with. I have built a table using a Custom Style Cell.
This cell simply has a Label and UISwitch. The label displays a name and the switch displays whether they are an Admin or not. This works perfectly. My challenge is how and where do I put code to react when the switch is changed.
So if I click the switch to change it from off to on where can I get it to print the persons name? If I can get the name to print I can do the php/sql code myself. Thanks and here is a snippet from my code.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as UITableViewCell
let admin = self.admin[indexPath.row]
let text1a = admin.FirstName
let text1aa = " "
let text1b = admin.LastName
let text1 = text1a + text1aa + text1b
(cell.contentView.viewWithTag(1) as UILabel).text = text1
if admin.admin == "yes" {
(cell.contentView.viewWithTag(2) as UISwitch).setOn(true, animated:true)
} else if admin.admin == "no" {
(cell.contentView.viewWithTag(2) as UISwitch).setOn(false, animated:true)
}
return cell
}
You have to set an action in your Custom Table View Cell to handle the change in your UISwitch and react to changes in it, see the following code :
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
#IBAction func statusChanged(sender: UISwitch) {
self.label.text = sender.on ? "On" : "Off"
}
}
The above example is just used to change the text of the UILabel regarding the state of the UISwitch, you have to change it in base your requirements of course. I hope this help you.
You need to listen .ValueChanged of UISwitch, in YOUR_CUSTOM_CELL to make some decisions. There you can catch to "println" your data.
Eric,
At some point in the tableview's lifecycle, you'll need to configure each UISwitch in the table cell with a target/action.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIControl_Class/index.html#//apple_ref/occ/instm/UIControl/addTarget:action:forControlEvents:
The action tells the UISwitch instance what method it should invoke when the switch is flipped by the user. The target tells the UISwitch instance what object is hosting that method.
Typically, you'll use the UITableViewController (or UIViewController) subclass as the target.