nicklockwood / layout

A declarative UI framework for iOS
MIT License
2.23k stars 97 forks source link

Anyone have trouble using outlets within a UITableViewCell.xml file? #146

Closed sidhenn closed 5 years ago

sidhenn commented 5 years ago

This is the following code I'm having trouble with which produces the following error.

UITableViewCell does not have an outlet named journalTextView.....

The setState value for ""journal" works as well as other variables... so sure the issue. Scratching my head for 2 days off and on.

class TableScreen: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate
{
    @IBOutlet var journalTextView: UITextView?
    @IBOutlet var TableView: UITableView? {
        didSet {
            TableView?.registerLayout(
                named: "Cell.xml",
                forCellReuseIdentifier: "Cell"
            )
        }
    }
....
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
        let cellNode = tableView.dequeueReusableCellNode(withIdentifier: "Cell", for: indexPath)

        cellNode.setState([
            "journal": journalTextView?.text as Any,
            ])
        return cellNode.view as! UITableViewCell
    }

TableScreen.xml

<TableScreen>
    <UITableView
        outlet="TableView"
        ...
        >
    </UITableView>
</TableScreen>

Cell.xml

<UITableViewCell
    reuseIdentifier="Cell"
    ....
    >
    ....
    <UIView>
        <UITextView
            outlet="journalTextView"
            text="{journal}"
           ...
        />
    </UIView>
</UITableViewCell>
nicklockwood commented 5 years ago

@den2k Layout only supports automatic binding of outlets in table view cell subviews to the cell itself.

If you want to bind an outlet for a view inside the cell to your view controller, you’ll need to do it manually inside the tableView(_:cellForRowAt:) method.

sidhenn commented 5 years ago

That sounds great! Just not sure how.. ;) . I'm a novice to this so..

This is what I will experiment with. Thanks for the quick response.

class TableScreen: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate
{
    @objc @IBOutlet var journalTextView: UITextView?
    ....

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
     ...
        self.cellNode = LayoutNode(
            view: UIView.self,
            children: [
                 LayoutNode(
                      view: UITextview.self,
                      outlet: #keyPath(self.journalTextView),
                 )
            ]
       )  
   ....
     return cellNode.view as! UITableViewCell
}
sidhenn commented 5 years ago

So I eventually added this to inside the tableView(_:cellForRowAt:) method with still not success... :( . I'll keep plugging away.

In case you are wondering. This is the table view controller:
https://github.com/den2k/TIMII/blob/master/TIMII/Views/TimelineTableScreen.swift

self.layoutNode = LayoutNode(
     view: UITableViewCell.self,
     children: [
          LayoutNode(
              view: UIView.self,
              children: [
                  LayoutNode(
                     view: UITextView.self,
                     outlet: #keyPath(journalTextView)
                 )
              ]
         )
     ]
 )
   ...
sidhenn commented 5 years ago

I'm really struggling to find an answer:

So far I've look at

I doubt I'm doing this correctly so any support would be wonderful.

nicklockwood commented 5 years ago

@den2k sorry for not getting back sooner.

If I understand correctly, you are trying to store a reference to the UITextView inside your table cell in a property in your TableScreen UIViewController called journalTextView, and the set the text of the UITextView to match the last value it was set to whenever the cell is recycled so that you won't lose the text if the cell moves off the screen?

In general, if you have a nontrivial table cell layout, it's best to create a subclass to use for your cell, then you can bind subviews directly to the cell. So that would look something like this (this code is untested, so apologies if there are typos):

class MyCustomCell: UITableViewCell {
    @objc var journalTextView: UITextView?
}
<MyCustomCell
    reuseIdentifier="Cell"
    ....
    >
    ....
    <UIView>
        <UITextView
            outlet="journalTextView // this will be bound to the cell itself, not the view controller"
            text="{journal}"
           ...
        />
    </UIView>
</MyCustomCell>

Once you have that, you can then reference the journalTextView directly from the cell, by using:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cellNode = tableView.dequeueReusableCellNode(withIdentifier: "Cell", for: indexPath)
    cellNode.setState([
        "journal": journalTextView?.text as Any,
    ])
    let cell = cellNode.view as! MyCustomCell
    // copy the journalTextView property of the cell to the journalTextView property of the controller
    self.journalTextView = cell.journalTextView
    return cell
}

Let me know if that doesn't work.

nicklockwood commented 5 years ago

@den2k I should point out that this solution won't work if you have more than one MyCustomCell in your table, because the cells will keep overwriting the single journalTextView property of the controller as they are created.

In general, it would be better to do something like bind the UITextView delegate to your view controller (many text views can all share the same delegate) and then store the text whenever it changes. You could then have an array of text values for each cell.

But if you only have one cell containing a TextField then this should work OK for now.

sidhenn commented 5 years ago

I have a table where each of the rows represented by 1 cell has 1 text view only. So given the 1 cell to 1 Textview, I will try and see if your method works. By the way thank you.

I'll post if this works out. I'll likely investigate binding the textview delegate to the VC at some point, when there is more time.

sidhenn commented 5 years ago

I can now pull value back into the controller from my UITextView. There is a separate issue but should not be related to Layout, so THANKS!