I have a UITableView that always has 4 sections. The first section always has 1 row, the second section always has 3 rows. Then the 3rd and 4th section can have any number of rows. Although the first two sections are essentially static cells, the content in them changes.
The first two sections are all I had to begin with so I just used static cells with outlets to the content directly in the TableViewController. Then I added two more sections which can have any number of rows.
I found that to do this, now I have to consider the whole table as using dynamic prototypes and I have to implement all the delegate/datasource classes for every cell now. In addition to that, I can no longer have my outlets (even for the first 2 sections) directly in the tableViewController but instead create custom subclasses for them so I can change their content.
This seems silly because they are so simple. Is there a better way I can do this? Maybe I can have two tableViews in the TVC, one that is static and handled the same way for the static cells and one that uses dynamic prototypes? If that's the best way, how should I implement that? I have never implemented a TableViewController before that handles more than 1 TableView.
For reference, the View is a profile view with simple data about the person's profile. The first 2 sections are just the person's picture, name, email, and description.
The second 2 sections contain 1) a list of favorited postings on the app. And 2) a list of postings that the user has created.
Here are 2 pictures showing the view:
No, fortunately Apple did add static table cell for UITableViewController but the road ends there. If you want to start modifying content you need to switch to a "real" UITableView, setup arrays and handle the cells correctly.
2 options.
Create IBOutlets to the static cells content and change the objects directly.
or...
Setup the tableView delegate methods and change content using the UITableView delegate and datasource.
class CustomCell: UITableViewCell {
#IBOutlet weak var labelName: UILabel!
}
In reference to the comment about getting the indexPath of a button click from the viewController check out the following code. Basically I created an extension of UITableView that includes the indexPathForView function. This way you can get the exact view(button) you pressed to handle any actions from your UIViewController. I prefer this technique over creating a protocol/delegate on the cell then calling back to the viewController.
#IBAction func pressedItem(sender: AnyObject) {
let button = sender as! UIButton
var indexPath = self.tableView.indexPathForView(button)!
let object = self.arrayOfObjects[indexPath.row]
// Now you can fire any action and have it related to that specific cell
// Create a HelpfulExtensions.swift class to hold your convenience extensions such as this
// MARK: - UITableView
extension UITableView {
func indexPathForView (view : UIView) -> NSIndexPath? {
let location = view.convertPoint(CGPointZero, toView:self)
return indexPathForRowAtPoint(location)
}
}
//
And might I add, EXTENSIONS ROCK!!!
Instead of using two tableViews it may be enough to setup a header view with static content. Probably it's easy to do inside the same and only one table view controller. But also it can be done separately and then a table view below with dynamic content, all within a simple view controller
Related
I have a main ViewController, MainViewController that contains a number of views along with a TableView.
Each row (i.e. tableViewCell) contains different content from different sources/views etc. A number of those rows in the tableView, in turn, contain a CollectionView called SettingsCollectionView. In the tableView's cellForRowAt method, I initialize the SettingsCollectionView for that tableViewCell and also pass it the respective data which that CollectionView subclass uses as its data source. So for example:
tableView row 0 - contains generic content
tableView row 1 - settingsCollectionViewA : SettingsCollectionView as well as a struct OriginalDataA
tableView row 2 - contains generic content
tableView row 3 - settingsCollectionViewB : SettingsCollectionView as well as a struct OriginalDataB
tableView row 4 - settingsCollectionViewC : SettingsCollectionViewas well as a struct OriginalDataC
tableView row 5 - contains generic content
When I initialize settingsCollectionViewA with OriginalDataA, I have a setter in that CollectionView that then sets up the local data under LocalDataA. This allows me to ensure I have the original and the working copy of the data based on the user making changes etc. Any time I call the OriginalDataA variable from the MainViewController, a getter in settingsCollectionViewA does some cleaning up of the data etc. so I can then do what I want with it in the MainViewController.
That part all works well except if those tableView cells are dequeued, when they reappear, I get back the original state for that tableViewCell and in turn collectionView rather than the state the user left it in.
I realize this is because each SettingsCollectionView class is working its own local copy of OriginalDataA, OriginalDataB etc. and appreciate I can just update the original data but then that creates other complexities - like the cleanness of current 'standalone' code built for the SettingsCollectionView subclass as well as the complexity of original vs. updated data. That's why I am stuck on a better programming approach...
Apologies, this might seem like a basic question but I'm new to programming and all the examples etc. I can find all speak to more simple scenarios rather than what I'm trying to do.
I haven't included the code because it's got a whole lot of other content and functionality that I think just confuses the concept outlined above.
Any help would be greatly appreciated.
Create a class that holds each of your data structs:
class MyDataModel {
var dataA: DataA
var dataB: DataB
var dataC: DataC
}
Then you create an instance of MyDataModel and hold a reference to it in a property of your view controller. Pass this same instance to your table view cells. Since it is a class and therefore a reference type, changes made by the cell will actually be made in this one instance.
Consider you have SettingsCollectionViewA which shows originalDataA. CollectionView displays the data what you provide in cellforrowatindex method. Collection view cells reuse memory every time. Only visible cells stays in the memory at any point of time. So user modified data will not be stored explicitly unless you modify the originalDataA.
I Added the UI Tableview,
and created unique Prototype Cell for each one:
I Attached UItableview cells to the unique Prototype Cells and gave each cell an identifier in the storyboard (also i put functions in each class to change the text, etc in that specific cell)
In my main UITableViewController,
I set the size to a costume value that i change later,
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return tableSectionsSize
}
My question now is how do i setup the screen itself,
with all i want and with the headlines and everything?
All the tutorials i find is for one tableview with all the data in the same kind, but in my case i have 8 different Prototype Cell.
Can u refer me to a tutorial for what I want to achieve?
Or explain to me how I can build up the data itself when i have different Prototype Cell and i want to show\hide each one of them ?
You're probably referring to cells and not sections.
All the tutorials you find are talking about Dynamic Prototypes kind of cells. By the Prototype Cells that appears in your image, that's what your UITableView is using. So the first thing is to switch it to Static Cells in the Attribute Inspector:
Then, add and customize each one of your TableViewCell's as you already did. Add as much labels, buttons, switches, etc as you like. They'll be shown in the Document Outliner as follows. You should now have 8 custom cells.
If you wish to group them logically, you should do as Craz1k0ek mentioned and separate them in sections, also in the Attribute Inspector, dragging the cells to their corresponding sections.
No need to use numberOfSections(in tableView:) method here.
I hope this helps.
For tables, we got a method cellForAtIndexPath. Here according to your logic , you can choose and configure cell and then return it.
But if your layout is not gonna change, then it's better to use static tables. In static tables design your cells beforehand. It will work fine.
I have a static UITableView as shown in the first image, but now I need to add a row with a Button that when I touch it adds a dynamic cell to the table as shown in the second image, the issue is that I can't add new cells to a static UITableview.
What would be the best practice to accomplish this?
Basically static TableView is not supposed to be changed at runtime (except cell content). This is clearly mentioned in docs:
Use static cells when a table does not change its layout, regardless of the specific information it displays.
The best practice in this case is to create a dynamic TV and populate it with appropriate amount of cells. You'll need to use DataSource delegate to do so. DataSource itself is typically done through dictionaries or arrays.
E.g. you have a dict 'phoneNumbers' and a button that is supposed to add a new one.
First, you add a selector to the button in cellForRowAtIndexPath: via tag for example. Then button action is going to look like:
-(void)yourButtonClicked:(UIButton*)sender
{
[self.phoneNumbers setObject:phoneNumber forKey:numberKey];
[self.tableView reloadData];
}
//Swift
func yourButtonClicked(sender: UIButton) {
self.phoneNumbers["numberKey"] = phoneNumber
self.tableView.reloadData()
}
(sorry it's Obj-C but I'm quite sure swift isn't much different at this point)
reloadData is needed to refresh TableView layout after changes to DataSource objects are made. It's quite close to 'redraw' in this case.
On the image from Contacts App you showed object is '(555)555-5555' NSString and key is probably 'other'. You can use and store these any way you like
So after all you only need to setup numberOfRowsInSection: so that for section where you want to add cells it returns the count of objects in dictionary phoneNumbers
I have a static UITableView with n columns and 1 section. The cells are detailed with some dynamic content passed from another UIViewController.
My solution is the following: I make a call in viewDidAppear to present the dynamic content and eventually reload the view:
override func viewDidAppear(_ animated: Bool) {
setupDetails()
}
fileprivate func setupDetails() {
tableView.cellForRow(at: [0,0] as IndexPath)?.detailTextLabel?.text = "..."
tableView.cellForRow(at: [0,1] as IndexPath)?.detailTextLabel?.text = "..."
// .... multiple cells
tableView.reloadData()
}
The thing I don't like is that the content is updated after the view is shown to the user (thanks to the viewDidAppear call).
For what concerns working on the cellForRow() method, I could not have it working given the fact that the cell is statically created in my Storyboard.
My question is if there exists a pattern to be used in this specific case which I didn't think of. I basically want the view to be loaded before the view is presented, as if I was working with dynamic cells.
My question is if there exists a pattern to be used in this specific case
Yes. Don't use a static table. Your table is not static! "Static" means "never changes". Your table is the opposite of static; it does change. It is dynamic!
So, use an ordinary dynamic table. Maintain a data model that is consulted by the Three Big Questions (the data source methods). To update the table with new data, first update the data model, and then call reloadData. That will cause cellForRowAt to be called for you, and you'll populate each cell from the data model. That is much more efficient than what you're doing.
As for the flash, that's just a matter of timing; use viewWillAppear instead of viewDidAppear.
Don't use ViewDidAppear if you don't want you change to show to the user. Use ViewWillAppear and the view will already be in its final state when the user first sees it.
I have a doubt how to maintain two tableviews and one view controller in segmented controller. In segment=0 I want display first tableview, In segment=1 to display second tableview and segment=2 to display one view controller, is it possible?
Yes. it is possible. There are two ways.
Use single tableview. And based on segment, change the tableview values.
Use two custom tableview class, and custom Protocal/delegate methods to manage the events.
You really don't need to have 2 Tableviews. Based on the segment selection call [tableview reloaddata] with your data what to display on that segment section. This saves lot of efforts and manipulation.
I think a scrollView can help you solve you problem:
Just add the two tableViews on the scrollView and use the segmentedController's method to set the scrollView's contentOffset.x
Similarly, you can add the ViewController on the scrollView followed the two tableView and scroll to it by click the segment == 2
Hope that my advice can help you :)
Whether you're using one or multiple table views, the main idea is to handle the selection event.
If your segmented control is created in the storyboard, you can use the #IBAction outlet, alternately you can use the 'addTarget' syntax.
e.g:
mySegmentedControl.addTarget(self, #selector(myHandlerMethod(_:), for: .valueChanged)
where the handler method is something like:
(first case)
#IBAction func myHandlerMethod(_ sender: UISegmentedControl) {
}
(second case)
#objc func myHandlerMethod(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 1 {
// handle your table view reloading or switching between first and second table views.
} else {
// otherwise, do something else
}
}
Some additional thoughts:
I would agree with others in using a single table view and giving it a different set of data. You can use different subclasses of UITableViewCell to represent your data if the content format between your data sets is different in structure and meaning.
It might also be worth looking into using child view controllers instead of plain views/table views. That way you can encapsulate logic relating to each specific scene you want to present inside a view controller and manage it independently.