I have the following situation
A TableViewController (ExploreViewController) has custom cells - CategoryTableViewCell. Each cell has a collection view with UICollectionViewCells.
I would like the CategoryTableViewCell to contact the ExploreViewController when displaying the collection view cells to determine what data it needs to display. The data is dependent on the index path row of the table view cell and on the index of the collection view cell.
How can I accomplish this using delegates?
I wouldn't do it that way. I would pass the data each cell needs to it as a model object in the table view controller's cellForRow(at:) method. If a cell's data changes, pass it a new model object. Then the cell can use that model data to respond to data source calls from its child collection view.
If you are determined to use the delegate pattern, or something like it:
Let's call it a data source instead of a delegate. Design a CategoryTableViewCellDataSource protocol. When you create a CategoryTableViewCell, set the table view controller as its data source.
Have the cell ask it's data source for the data to display. The delegate protocol would use the cell's position to figure out it's index path, and then fetch the data from the model and return it. (You'd find the midpoint of the cell's content view, convert the point to the table view's coordinate system, and then use the table view's indexPathForRow(at:) method to figure out the index path.
Related
I have an NSFetchedResultsController and a dynamic table view. My table view cells have a text label. How should I bind objects from the fetched results controller to the cell?
In WWDC 2019 230, there’s a code snippet where a core data object is bound to a view...
if let tag = try? fetchRequest.execute().first {
nameSubscription = tag.publisher(for: \.name).assign(to: \.text, on: tagLabel)
colorSubscription = tag.publisher(for: \.color).map({ $0 as? UIColor}).assign(to: \.textColor, on: tagLabel)
}
Where should this happen in the context of a table view controller with a cell that has a label? Where should the subscriber (AnyCancellable) go? Will I need a collection of subscribers since there’s an indefinite number of cells?
The binding like this was shown in WWDC session for the detail view, I guess.
Table view cells are being reused during scrolling.
That's why you're not supposed to bind the particular data model's property change to the update of the label in the cell.
Instead, you should observe the changes to the data model on the controller level and reload table view cells and / or sections whenever changes happen.
Your situation isn't comparable to the video's situation. The video doesn't perform this kind of binding from a fetched results controller, but rather from a single fetch request; it keeps the display current in case the underlying data changes. As the video goes on to say, the way to keep a table view updated from a fetched results controller is to use the delegate method to derive a snapshot and feed it to a diffable data source.
I pass the cell title label from "collection view A" to "collection view B". So now in collection View B, I have the cell's title label.
In the viewDidAppear method for collection View B, I'd like to use scrollToItemAtIndexPath to scroll to the proper index containing the cell with that title label.
How would I go about doing that?
Collections work like TableViews. Most people have a backing store of data behind their collection or tableview, which is an array with data in the proper order. Thus, when your cellForRowAtIndexPath or cellForItemAtIndexPath call is invoked, you simply stuff in the value from your backing store. If your model is the same as this you would simply search your collection view B backing store array for the label, and make note of what row it is in.
As the title describes, I've got a tableview inside each of my collection view cells. For me, it makes sense that the superview's controller should control the view, but since in this case each tableview contains different data, I have made each superview (collection view cell) the controller for its tableview. I hope that makes sense. I know making a view to also be a controller violates the MVC paradigm, but I'm not sure what the proper way is achieve MVC compliance in this case. I also need to send messages to the table view based on what happens in the CollectionViewController. Do I need to subclass UITableViewController and make a reference to it in my collectionviewcell.h file?
Sorry if that was confusing. Thanks for the help.
I think your instinct is correct that having a view object serve as a data source is a violation of MVC. I would suggest having the owning view controller either serve as the data source for all the table views, or set up a separate model object for each table view that serves up the cells for that table view.
If you use a single data source you'll have to have a switch statement that figures out which table view is asking and fills the cells with the appropriate data.
My gut would be to create a thin table view data source class who's only job is to serve up the cells for the table view inside a collection cell (and respond to the other collection view data source protocol methods). Use a custom subclass of UICollectionViewCell that has a strong property that points to the data source object. You could make your custom cell class create an empty data source object at init time and hook up it's outlet to the table view.
Then in your cellForItemAtIndexPath method, pass the appropriate data to the cell's data source object. If you reuse a cell, it would already have a data source object, so you'd just replace the data with new data and trigger the reloadData method.
Your controller object would mediate between the model and the view, like it should. It would set up the model data for each cell, and then the data source object for each cell would act as the model for that cells table view.
If you later come up with several different types of collection cells that display different data, using separate data source objects for each cell would keep the code simple. You'd just subclass your data source object based on the cell type.
I have a tableview that is based on a array of DB results, these results contains a date field. I have a custom cell that contains an image and labels. I'm trying to do:
At cellForRowAtIndexPath I verify if the date of current item (objectAtIndex:indexPath.row) has date field bigger than the last item (objectAtIndex:indexPath.row-1). If this is true: I want to add a cell filling the ImageView with a certain image, but I need to add a new row just for show this image.
How can I do this? I'm already doing this verification, but I need to add a new cell...
Do not use the cellForRowAtIndexPath to decide how many cells you want to have. At the point this method is called you should have already setup the data source to provide table view with all information needed.
Here is what you need to do. Refactor your code in a way so you:
Setup the data source first.
Force reload of the table view either by calling the reloadData method.
hey you can add the object in your data base(for example ns array) and refresh the table view with method
[tableView reloadData];
then the method cell for row at index path will be called again and it will refresh the table view's items.just make sure the method cellforrawantindexpath in your code knows to handle the new data type(make validations).
Your tableView data source should not contain any of that logic where the content of once cell depends on the content of another cell. Instead, you should have a data item for each requested indexPath and that data item should contain ALL logic necessary for the cell to be configured. If an action on that cell has an effect on how another cell should look, you apply a change to the corresponding data-item, and then call reloadRowsAtIndexPaths: for the indexPaths.
In short: configure cells ONLY in tableView:cellForRowAtIndexPath: or tableView:willDisplayCellAtIndexPath, and ONLY do configuring. Other logic should be placed in some data(-controller) object.
As suggested, you should add an item to your data-array. Then call -insertRowAtIndexPath: on the tableView. ReloadData is like a big ugly hammer that you only use when ALL of the data for that tableView changes.
I have a TableView with a prototype cell, in this prototype cell I have a text field.
Well, what I need is to get a information from this text field when it changes. I need to do that to feed a object.
How can I do that ?
Short answer - you don't.
Slightly more in depth answer...
The UITableViewCell is a "view". It is used only to display information to the screen. It is not for storing data in.
In a UITableViewController (i.e. UITableViewDatasource and UITableViewDelegate) you should have an NSArray (or an NSFetchedResultsController) that you use to store information in.
Then using the delegate methods you populate the table with that data.
If the data changes then you reload the table to update the cells.
What should never happen is the following...
Load the table by passing in data to the cell.
The cell then changes its own data and changes what is on the screen.
The controller then reads that data out of the cell.
No, no, no, don't do this.
What should happen is this...
Load the table and configure the cell display to represent the correct part of the data.
When the button is pressed (or text field is changed, etc...) in the cell then call a method back in the controller to update the data accordingly.
Now the data has changed, reload the cell.
It will now show the correct information based on the new data.
You need a custom cell with a public property (IBOutlet) UITextfield. Then you can set your ViewController as the textField's delegate in cellForRowAtIndexPath,
cell.textField.delegate = self;
in this case your ViewController has to implement UITextFieldDelegate protocol.