From within cellForRowAtIndexPath:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
I am creating a UITableViewCell subclass, PeopleTableViewCell:
let cell:PeopleTableViewCell = self.tv_main.dequeueReusableCellWithIdentifier("row_cell") as PeopleTableViewCell
and then I pass some parameters
cell.loadItem( param1 as NSString, p2: param2 )
Now, in each row I have a button, and when I click it
#IBAction func button_clicked( param1: NSString){
I need to call a function in the parent table that takes as an argument one of the parameters I had passed (ex param1).
How can I accomplish this?
Edit, after the answer that #rob gave:
What finally worked is to
A. Pass a reference to the the parent UIViewController to the cell.loadItem
func cell.loadItem( param1 as NSString, controller: self )
and assign the controller variable to a local variable , say pvcontroller
func loadItem(param1: String, controller: PeopleViewController) {
self.pvcontroller = controller
}
B. From within the PeopleTableViewCell class, from within the button click function, I call the function of the parent UIViewController via the pvcontroller variable
#IBAction func person_image_click(sender: UIButton) {
self.pvcontroller?.person_clicked(self.param1)
}
You could:
Have a property in PeopleTableViewCell that is updated by loadItem:
class PeopleTableViewCell : UITableViewCell {
var param1: NSString?
func loadItem(param1: NSString, param2: NSString) {
self.param1 = param1
// do your other stuff here
}
// the rest of your implementation here
}
Have your cellForRowAtIndexPath call loadItem:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("row_cell", forIndexPath: indexPath) as PeopleTableViewCell
let param1 = ...
let param2 = ...
cell.loadItem(param1, param2: param2)
return cell
}
Then your #IBAction could determine the PeopleTableViewCell instance, and access its property. Note, the #IBAction parameter, sender, references the button, like always. Thus, if this #IBAction was implemented in the table view controller, then you'd have to navigate up the view hierarchy to get to the cell, and then access the property from there:
#IBAction func buttonClicked(sender: UIButton) {
let cell = sender.superview?.superview as PeopleTableViewCell
let param1 = cell.param1
// do something with param1 now
}
In this example, I have the button on the cell's content view, so I'm going up two levels of superview (one to get the content view, one to get the cell). Just ensure that whatever you do here mirrors the hierarchy you've configured in IB.
Alternatively, you can implement your #IBAction from within the PeopleTableViewCell class, in which case you don't have use this sender.superview?.superview syntax, but rather can just reference self to get to that param1 property. It's up to you.
Related
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
The piece of code below prints the content of whichever cell is clicked on in my TableView.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(self.cell[indexPath.row])
}
I want to use the result that is printed in a label on another ViewController.
How do I get the string value from the function and then use it on on the other view? My thought is to use a global variable but I need to get the string value out first.
For example, You can use simple organization of a singleton of another ViewController (SecondScreen) with var main (in case, as usual, when SecondScreen inited via a Storyboard):
class SecondScreen : UIViewController {
// 1. add this var
static var main : SecondScreen? = nil
// 2. Your some UI element
#IBOutlet weak var textButton: UIButton!
// 3. add this method
func updateUI(string : String) {
textButton.setTitle(string, for: .normal)
}
// 4. setting a var
override func viewDidLoad() {
if SecondScreen.main == nil {
SecondScreen.main = self
}
}
// ... another your and standard methods
}
And you can update your SecondScreen like this:
let v = SecondScreen.main
v?.updateUI(string: "yourString")
Also I recommend you to call method async:
DispatchQueue.main.async {
SecondScreen.main?.updateUI(withString : string)
}
I suggest you to learn more about singletons...
At first, when you create a tableView, you have to collect data (string here) of cells in an array or another data collection. And you can get a needed data (strings) with indexPath variable in the method didSelectRowAt. And you can pass the string to another ViewController (let use SecondViewController) with several ways.
Here is an example:
// declaration an array of your strings
var array : [String] = ["First", "Second", "Third", ...]
...
// getting a string from method:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let string = array[indexPath.row]
print(string)
// next, for example, you need to pass the string to a singleton SecondViewController with static var **main**:
SecondViewController.main?.neededString = string
}
Don't forget update in async DispatchQueue:
DispatchQueue.main.async {
SecondViewController.main?.updateUI(withString : string)
}
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 am am googling around the whole day for a probably simple question but I do not get it right. Hopefully someone can help me.
I have a tableview controller with one prototype cell containing three custom labels.
When I run the app the table view controller will generate about 150 tableview cells with content parsed form a csv-file.
When I click on one of these cells the user will be forwarded two a second view controller showing some additional infotext for his cell selection.
During the same time the user is clicking the tabelview cell a variable will be updated to the corresponding tableview-row-number (e.g. 150 for the last tableview cell.
Now I want to use this variable as reference text within the text shown in the second view controller.
The variable in the tableview controller is "rowSelectedFromList" and will be set by the following code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var rowSelectedFromList: String
rowSelectedFromList = rowOfItems[indexPath.row].customlabel3!
println(rowSelectedFromList)
}
The "println" is just for checking if it works correctly and it does.
The question is how can I use the variable "rowSelectedFromList" in the second view controller?
Appreciate your help, thanks!
You can add your custom logic in prepareForSegue like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let controller = segue.destinationViewController as? YourSecondController,
indexPath = tableView.indexPathForSelectedRow() {
controller.someVariable = rowOfItems[indexPath.row].customlabel3!
}
}
Replace YourSecondController with class name for second view controller.
Don't forget to create IBOutlet for your UITableView and name it tableView.
You'll want to put something in prepareForSegue as well as a variable in your second view controller. So in your table view controller:
var variableToPass: String!
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
variableToPass = rowOfItems[indexPath.row].customlabel3!.text
performSegueWithIdentifier("SecondControllerSegue", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SecondControllerSegue" {
let destinationController = segue.destinationViewController as! SecondViewController
destinationController.passedVariable = variableToPass
}
}
And in your second view controller you'll want to add the variable that the value will be passed to:
var passedVariable: String!
You can, of course, choose to replace the variable with whatever type you wish to send :)
Good question if you want sort this problem plz follow below code:
class ViewController {
var cvDataArray = cells = NSMutableArray.new()
func viewDidLoad() {
super.viewDidLoad()
cvDataArray.enumerateObjectsUsingBlock({(obj: AnyObject, idx: Int, stop: Bool) in var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("")
cell.textLabel.text = obj["title"]
cells.addObject(cell)
})
tableView.reloadData()
}
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return cells.objectAtIndex(indexPath.row)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell: UITableViewCell = cells.objectAtIndex(indexPath.row)
}
}
The code which is working for me is a mixture Phoen1xUK and glyuck answers.
I put both together and ended up with this working version:
For the FirstViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SecondControllerSegue" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let rowSelectedFromList = rowOfItems[indexPath.row].customlabel3
(segue.destinationViewController as! SecondViewController).rowTransferedFromList = rowSelectedFromList
}
}
}
In the SecondViewController I set up the variable as follows:
var rowTransferedFromList: String!
I am learning Swift and I have pattern that I used to do in Objective C, but don't understand how to do it here.
I have UIViewController with TableView. I works fine when I put my array inside it. But according to MVC I want to move my array with data to another class. And I have no idea how to do it. Everything I tried doesn't work.
Thank you!
My code, how to move tableDS outside:
import UIKit
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
//temp table data
let tableDS = ["fdf", "dfd"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableDS.count
}
let textCellIdentifier = "TableViewCell"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyCell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as MyCell
let row = indexPath.row
cell.dayLabel.text = tableDS[row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
println(tableDS[row])
}
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = tableDS[indexPath.row]
return cell
}
This should work.
If you want to use the MVC pattern, create a new singleton class, create the array there, then create a method returning the array.
First you need to initialize your table view with an empty array. When you load your MyViewController from another view controller in the code example below you can pass your data, and change your let tableDS = [“fdf”, “dfd”] to var tableDS = [“fdf”, "dfd"]. let is used for a constant variables.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YourMyViewControllerSequeId" {
let myViewController = segue.destinationViewController as MyViewController
var myArrayToPass = ["learn swift", "or get a life"];
myViewController.tableDS = myArrayToPass
myViewController.tableView.reloadData()
}
}
In the MVC design pattern for a table view the table view is the view object. The controller is the view controller.
The model is whatever you use to store your data.
The controller object serves as an intermediary between the model and the view.
For a simple table view the model object can be a as simple as an array. The array is the model. Thus there is no reason to store the data in a separate object.
If you really want to make your model a completely different object, create a new class. Call it MyTableViewModel. Make your MyTableViewModel class contain an array of your data. Also make MyTableViewModel conform to the UITableViewDatasource protocol. To do that, you'll have to implement several methods - in particular, cellForRowAtIndexPath.
Now in your view controller, create a MyTableViewModel object as a strong property of your view controller, install the array in it, and make it the data source of the table view.
Done.
Again, though, it's quite common to just treat a simple array as your model, and let the view controller serve up cells by implementing cellForRowAtIndexPath in the view controller.