I have a textView embedded in a cell in a tableView and am trying to determine whether the text has been changed when the user presses done.
When a user taps on a note, it loads the note from the database, they can make edits, and press done. When they press done I need to invoke didChange to make sure that they changed something to avoid writing the same information back to the database. I've set my cell as a UITextFieldDelegate and tried setting didChange.
import UIKit
class NoteEditTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var editorView: UITextView!
var origionalContent = ""
var didContentChange = false
func didChange<Value>(_ changeKind: NSKeyValueChange, valuesAt indexes: IndexSet, for keyPath: __owned KeyPath<NoteEditTableViewCell, Value>) {
debugPrint("Content Changed")
}
}
I can't get didChange to invoke though. No matter what I change in that field, it never prints the debug message.
You can use callback function here. You can also use callback function like this for button tap action in tableViewCell. That will respond in tableview.
1)Declare callback function in your tableViewCell file. Here this callback function pass String, you can use any type you want or you can leave it empty[()->()] if you just want to detect this function call in tableview.
var textViewTextChangeCallback:((String) -> Void)?
2) use UITextViewDelegate with textViewDidChange in your tableViewCell file. Put this extension into your tableViewCell file. call the callback function here & pass anything you want.
extension NoteEditTableViewCell: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
textViewTextChangeCallback?(textView.text)
}
}
3)Define your callback closure in cellForRawAt of UITableViewDataSource in your ViewController file.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifire") as! NoteEditTableViewCell
cell.textViewTextChangeCallback = { [unowned self] text in
print(text)
// if you want to save text on buttonClick when textViewDidChange call, create callback function for buttonClick & call its's closure here. i already show you how to define callback function follow as abov for any click you want from tableViewCell ;)
}
}
return cell
}
Related
I have a table view controller with a custom cell which contains a text field - it's a form basically.
i want to automatically go to the next text field when users press "return" on their keyboard but for some reason my solution doesn't work.
In TableViewController, I do:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CustomCell
cell?.box.tag = indexPath.row
In my custom table view cell, I have
override func awakeFromNib() {
super.awakeFromNib()
box.delegate = self
...
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.viewWithTag(textField.tag+1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
The issue is that textField.superview?.viewWithTag(textField.tag+1) is always nil. I don't know why because I clearly set the tag and also mark it as a delegate. thank you.
Adding some clarity and more suggestions to the valid answer by #jawadAli, as I feel you are still new to iOS development.
You are trying to get the tableView from the textField. But you will not get it by referring to the superview of textField. Because the view hierarchy would be like this:
UITableView > UITableViewCell > contentView > Your text field.
There can also be some more views in the view hierarchy, so you need to keep traversing through the superview chain till you get the UITableView. And #jawadAli has posted the code on how to get it.
But overall that is an incorrect approach. You should use delegation. I.e. your cell should call a method when it has resigned as first responder. And your table view controller will receive that call.
Then your view controller has to get the next cell and make it the first responder.
And if this doesn't make any sense to you, then I would very strongly suggest that you learn about Delegation. It's ubiquitous in iOS' libraries.
EDIT:
Approach to use delegation.
Create a protocol, let's say CellDelegate that has a function like func didFinishDataCapture(forCell: UITableViewCell).
The cell will have a delegate property of type CellDelegate.
The controller will conform to CellDelegate and will set itself as the cell's delegate in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
Now in your cell, when you are done with the text field (which you would know as cell would be the text field's delegate), you call your own delegate's function i.e. delegate.didFinishDataCapture(forCell: self).
In your implementation of didFinishDataCapture in the controller, you will know which cell has finished with the data capture and can put the logic on what to do next.
It should be nil as textField.superview is your cell class ... and your cell class does not have the view with required Tag .. so it will return nil..
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
guard let view = self.superview as? T else {
return self.superview?.lookForSuperviewOfType(type: type)
}
return view
}
}
Get tableView through this extension like this
let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
your function will become
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
if let cell = tableView?.cellForRow(at: IndexPath(row: textField.tag+1, section: 0)) as? CustomCell {
cell.box.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
I've got a UITableViewController with two custom cells - one contains a UITextField (for the user to input a title) and the other contains a UITextView (for the user to input a description). Whenever these change, I want to update my memory object (which is a struct with two variables - memoryTitle and memoryDescription).
The memoryTitle seems simple enough - on my ViewController I have the following:
#IBAction func memoryTitleChanged(_ sender: UITextField) {
memory.memoryTitle = sender.text ?? ""
}
The UITextView has confused me slightly though. There's two issues I'm having - I can't create an action in the same way I can for the UITextField, so my next thought was to make the ViewController the delegate and use textViewDidChange to update memory.memoryDescription but that brings me to my second problem.
In order to make the UITextView cell resize dynamically, I used the following tutorial which works perfectly (https://medium.com/#georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01) to make my custom cell this:
class DetailTextTableViewCell: UITableViewCell, UITextViewDelegate {
//Found below method for resizing UITextView and cell - https://medium.com/#georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01
#IBOutlet weak var memoryDescriptionTextView: UITextView!
var textChanged: ((String) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
memoryDescriptionTextView.delegate = self
memoryDescriptionTextView.backgroundColor = UIColor.red
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//Found below method for resizing UITextView and cell - https://medium.com/#georgetsifrikas/embedding-uitextview-inside-uitableviewcell-9a28794daf01
func textChanged(action: #escaping (String) -> Void) {
self.textChanged = action
}
func textViewDidChange(_ textView: UITextView) {
textChanged?(textView.text)
}
}
Now I'm stuck with DetailTextTableViewCell being the UITextView's delegate, so I'm not sure how to make it update my memory object in the ViewController when the text changes. If anyone has any ideas or guidance it'd be much appreciated!
Thank you in advance.
First, you don't need textChanged method
func textChanged(action: #escaping (String) -> Void) {
Then, what you need, is assigning your textChanged closure variable (which is good approach btw) in controller's cellForRowAt for each certain cell.
Inside closure declare, that when text view did change, certain item's (from table view data source array) property will be assigned with String parameter of closure and if you need, then reload certain cell for this IndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.textChanged = { text in
self.dataSourceArray[indexPath.row].stringProperty = text
// tableView.reloadRows(at: [indexPath], with: .none)
// if you want to reload row, move calling closure
// to `textViewDidEndEditing` instead
}
...
}
declare this protocol above your cell DetailTextTableViewCell
protocol CellDelegate {
func textViewChanged(textView : UITextView)
}
add a delegate var in your DetailTextTableViewCell
var delegate : CellDelegate?
In the cell for row of your tableView assign self to delegate property of cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
}
In your DetailTextTableViewCell add this line inside textViewDidChange
func textViewDidChange(_ textView: UITextView) {
textChanged?(textView.text)
delegate?.textViewChanged(textView)
}
Now implement the delegate function in your view controller
func textViewChanged(textView: UITextView) {
}
Inside cellForRowAt do
let cell = ///
cell.memoryDescriptionTextView.tag = indexPath.row
cell.memoryDescriptionTextView.delegate = self
and implement the delegate methods inside the vc
Try to enable user interaction property of text view inside cellForRowAt method.
cell.memoryDescriptionTextView.delegate = self
cell.memoryDescriptionTextView.isUserInteractionEnabled = true
DeviceTableViewCell
I create a protocol on my custom DeviceTableViewCell
protocol DeviceTableViewCellDelegate : NSObjectProtocol {
func printMe(_ text : String)
}
I also declared my delegate in
weak var delegate: DeviceTableViewCellDelegate?
DevicesViewController
I had this
extension DevicesViewController: DeviceTableViewCellDelegate {
func printMe(_ text : String) {
let text = "Protocol & Delegate"
print("........")
print(text)
print("........")
}
}
I don't know how to trigger my print() statement.
How would one trigger it ?
Do I need to call my printMe() somewhere ?
Did I missing something here ?
First, do this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! DeviceTableViewCell
cell.delegate = self
}
Then, you must call printMe from your cell to handle your action
You just declare how the delegate function working, you haven't called it yet.
Based on the context, you can decide when to call delegate.printMe(_ text : "Foo").
I suggest a simple example you could refer: passing data back to previous view controller from current view controller using delegate.
Let's take example to understand the concept. So as you already create delegate variable for your protocol.
weak var delegate: DeviceTableViewCellDelegate?
Now to call protocol method you need to assign your delegate to some viewController or class. Let's assign in same view controller in viewDidLoad method.
override func viewDidLoad(){
delegate = self
}
Now let's say need to call protocol method when some button pressed. So what you need to do is call this method like this in button press method.
delegate?.printMe("Button Pressed")
I have a Social Network Feed in form UItableView which has a cell. Now each cell has an image that animates when an even is triggered. Now, This event is in form of a string, will be triggered at every cell. the options for the event are defined in another class(of type NSObject).
My issue:
I constructed a protocol delegate method in table view, which will be called whenever the event is triggered for each cell. Then, I define this function in UITableViewCell Class, since my the image will be animating on that.
All is working well but I am unable to figure out how to assign the delegate of TableView class to cell class. What I mean is, how can I use UITableView.delegate = self in cellView class. I have tried using a static variable, but it doesn't work.
I have been playing around the protocols for a while now but really unable to figure out a solution to this.
I hope I am clear. If not, I will provide with an example in the comments. I am sorry, This is a confidential project and I cant reveal all details.
If I understand you correctly, you are trying to make each of your cells conform to a protocol that belongs to their UITableView? If this is the case then this cannot be done. The Delegation design pattern is a one to one relationship, i.e only one of your UITableViewCells would be able to conform to the UITableView's delegate.
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.
Quote from the Apple Docs
I would suggest that your UITableViewCell should call a block (Objective-C) or a closure (Swift) whenever your specified event is triggered to achieve what you are looking for. Set up this closure in your tableView:cellForRowAtIndexPath function.
EXAMPLE
TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCellID", for: indexPath) as! MyTableViewCell
cell.eventClosure = {
//Do something once the event has been triggered.
}
return cell
}
TableViewCell
func eventTriggered()
{
//Call the closure now we have a triggered event.
eventClosure()
}
If I correctly understood your question, maybe this could help:
class ViewController: UIViewController, YourCustomTableDelegate {
#IBOutlet weak var tableView: YourCustomTableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.customTableDelegate = self
}
// table delegate method
func shouldAnimateCell(at indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.animate(...)
}
}
}
Try something like this:
Define your delegate protocol:
protocol CustomCellDelegate: class {
func animationStarted()
func animationFinished()
}
Define your CustomCell. Extremely important to define a weak delegate reference, so your classes won't retain each other.
class CustomCell: UITableViewCell {
// Don't unwrap in case the cell is enqueued!
weak var delegate: CustomCellDelegate?
/* Some initialization of the cell */
func performAnimation() {
delegate?.animationStarted()
UIView.animate(withDuration: 0.5, animations: {
/* Do some cool animation */
}) { finished in
self.delegate?.animationFinished()
}
}
}
Define your view controller. assign delegate inside tableView:cellForRowAt.
class ViewController: UITableViewDelegate, UITableViewDataSource {
/* Some view controller customization */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CustomCell.self)) as? CustomCell
cell.delegate = self
cell.performAnimation()
return cell
}
}
I have one view controller named TableViewController and another customised cell called feed.swift
The cells are getting reused properly and I have put tags on various buttons as I wan't to know what button of what feed is pressed on.
In my cellForRowAtIndexPath I'm populating my username with json that I have parsed. It looks like this
cell.username.text = username[indexPath.row]
output-> ["andre gomes", "renato sanchez", "renato sanchez"]
Then I have tagged my username button like this
cell.usernamePress.tag = indexPath.row
This is going on in my TableViewController
In my feed.swift I'm checking if a button is pressed and printing out the tag assigned to that button
#IBAction func usernameBut(sender: AnyObject) {
print(usernamePress.tag)
}
output-> 2
Now I need to access the username array of TableViewController in feed.swift and do something like username[usernamePress.tag]
I tried making a global.swift file but I'm not able to configure it for an array of strings.
import Foundation
class Main {
var name:String
init(name:String) {
self.name = name
}
}
var mainInstance = Main(name: "hello")
Even after doing this I tried printing mainInstance.name and it returned hello even after changing it. I want a solution where the array of strings holds the values I set in TableViewController and I can be able to use them in feed.swift
Any suggestions would be welcome! I'm sorry if there are any similar question regarding this but I'm not able to figure out how to use it for a mutable array of strings
I suggest you don't use the array directly in your FeedCell but instead return the press-event back to your TableViewController where you handle the event. According to the MVC Scheme, which is the one Apple requests you to use (checkout Apples documentation), all your data-manipulation should happen in the Controller, which then prepares the Views using this data. It is not the View that is in charge to display the right values.
To solve your problem I would choose to pass back the press-event via the delegation-pattern, e.g. you create a FeedCellDelegate protocol that defines a function to be called when the button is pressed:
protocol FeedCellDelegate {
func feedCell(didPressButton button: UIButton, inCell cell: FeedCell)
}
Inside your FeedCell you then add a delegate property, which is informed about the event by the View:
class FeedCell {
var delegate: FeedCellDelegate?
...
#IBAction func pressedUsernameButton(sender: UIButton) {
delegate?.feedCell(didPressButton: sender, inCell: self)
}
}
If your TableViewController then conforms to the just defined protocol (implements the method defined in there) and you assign the ViewController as the View's delegate, you can handle the logic in the Controller:
class TableViewController: UITableViewController, FeedCellDelegate {
...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FeedCell", forIndexPath: indexPath) as! FeedCell
cell.delegate = self
// Further setup
return cell
}
func feedCell(didPressButton button: UIButton, inCell cell: FeedCell) {
guard let indexPath = tableView.indexPathForCell(cell) else { return }
// Do your event-handling
switch (button.tag) {
case 2: print("Is username button")
default: print("Press not handled")
}
}
}
As you might recognize I changed your class name. A Feed sounds more like a Model-class whereas FeedCell implies its role to display data. It makes a programmer's life way easier if you choose self-explaining names for your classes and variables, so feel free to adapt that. :)
you should add a weak array property to the tableViewCell:
weak var userNameArray:[String]?
Then in your tableViewController pass the username array into the cell:
fun tableView(tableView:UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// create the cell, then...
if let array = self.username {
cell.userNameArray = array
}
}
Then you can use the array in the cell itself to populate its fields handle button taps, etc.