How to use a custom UITableView and UITableViewCell inside a .xib file? - ios

I'm creating a couple of cards with information to display. Each card is going to be represented with a UIView, so I'm creating each card in different .xib files.
One of my cards contains a custom UITableView and cells which are going to be loaded and managed with the UITableViewDataSource and UITableViewDelegate protocols.
However when I search for the Table View component and drag it to my .xib file it loads a predefined UITableView which I can't customize.
I'm fairly new to using separate .xib files (I've been using storyboards) so I don't know how to customize my TableView in this context so any help would be appreciated.

Xibs dont have the tableviewcell prototypes like in the storyboard, unfortunately its a storyboard only feature. Xibs are a bit archaic but still have their purpose. But you can have the different cells within the same xib file as your tableview, they will just be separate to the tableview.
you would load the xib like
var objects: NSArray?;
NSBundle.mainBundle().loadNibNamed("MyTableView", owner: self, topLevelObjects: &objects)
then in the array, depending on the order of the elements in the xib, you will get back an array of all the elements in your xib. so probably at position 0 will be your tableview, then 1 will be a cell, 2 a cell etc.. depending how many you have. you'll probably only load the cells in cellForRowAtIndexPath: only though, while the table will be in viewDidLoad or something

make a tableview and create a tableview instance.
write this code in viewdidload...
self.tableView.registerNib(UINib(nibName: "PackageDetailTVCell", bundle: nil), forCellReuseIdentifier: "Cell")
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.arrayDict.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PackageDetailTVCell
let dict = self.arrayDict[indexPath.row]
cell.labelOfferPrice.text = (dict .objectForKey("offer_price") as! String)
cell.labelName.text = (dict .objectForKey("title") as! String)
cell.labelRegularPrice.text = (dict .objectForKey("price") as! String)
return cell
}

Related

Reusing dynamic TableViewCells without having to maintain of each cell's state in Controller

I have a UITableView that uses a cell that has 3 expandable and collapsable subviews in them. I would prefer to maintain the state of these views in my UITableViewCell class itself (states as in collapsed or expanded)
Since they are reusable cells, currently, if I expand view 1 in cell A, and then scroll down to cell B, it's view 1 will be expanded. I don't want this. I want it collapsed. But, if I scroll back up to cell A, I want it to still be expanded.
Other than storing all of these states in an array or dictionary
var expandedViewOneCells: [Int] = []
var expandedViewTwoCells: [Int] = []
etc.
I would prefer to have the cells essentially of act individually and maintain their own state... But how would I do this when cells are reused? Keep in mind, I will always only have at most 3 of these kinds of cells, so can I set something like only reuse after 3 cells.
Would it be wise to keep an array of the cells I load, and then on cellForRowAt load the cell from that array based on the index and return it?
In your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell function try not to deque a cell but create a new instance of your cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCustomCell()
return cell
}
If you are loading your cell from a xib file you need a way to create your custom cell from that nib. Add the following method to your CustomCell class
static func loadFromNib() -> RequestTableViewCell {
let nib = UINib(nibName: "\(MyCustomCell.self)", bundle: Bundle.main)
let cell = nib.instantiate(withOwner: self, options: nil)[0] as! MyCustomCell
return cell
}
Then in your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell this will create a new cell for every row and not reuse a cell when scrolling
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCustomCell.loadFromNib()
return cell
}
A solution like this may not be optimal if your table view has a lot of rows but for a SMALL amount of rows this should be okay
I see 2 solutions to your problem:
Use 3 View Controllers. They should never get destroyed, and add / remove the corresponding VC's view on top of the .contentView of the cell as it appears or goes off-screen. See the solution here http://khanlou.com/2015/04/view-controllers-in-cells/ The Custom Cell itself is just a view, shouldn't really be concerned with the state, but if we move that logic to a View Controller - we should be fine, an we are not violating MVC. Plus, the View Controller can keep track of the height of the view, based on the state, and heightForRow(at:) can ask it for that
I'd use a Stack View as this is a perfect scenario for it. I'd probably represent the Cell itself as another stack view. Not sure exactly what the views look like and how they change, but it may end up as simple as hiding / unhiding the second view from the Stack View that represent a "cell".

iOS analog for inflating a view from a layout (xib)

I'm new to iOS and trying to rewrite an app from android.
What I do in Android - I have a layout - same as nib in xcode
And then I inflate this view as many times I want - same as ListView (TableView's android analog) is working
I need to do this on iOS :
which means having some container like Android's horisontal LinearLayout where I can add nib's with their class - like UITableViewCell and fill data.
When I asked one person who is iOS developer, he told me that it is almost impossible to do due to compexity and lack of android-like ViewGroups and that it's better to do in a WebView than natively.
So please, tell me , is there a solution - to inflate as many views as needed from a nib into container-views one under another ? Please answer in Swift, I don't know Obj-c at all
Yes you can do it by registering your tableView with Xib in viewDidLoad
tableView.registerNib(UINib(nibName: "CustomCellXib", bundle: nil), forCellReuseIdentifier: "CustomCell")
Then do this in delegate method
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
cell.middleLabel.text = items[indexPath.row]
cell.leftLabel.text = items[indexPath.row]
cell.rightLabel.text = items[indexPath.row]
return cell
}
where CustomCellXib is your Xib file name.
CustomCell is your class of CustomCellXib
and #"CustomCell" is string identifier for reusing cells
Note down that you will have to implement other few delegates methods too for complete working of TableView.
There is nothing extremely complex here. It's a common UITableView with multiple sections.
Red text labels for every sections should be implemented as section headers
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//create a view here
}
Custom UITableViewCell with grey labels, red - and + buttons in it.
Custom cell should contain a UIImageView below it's content. Since it is different for first, middle and last rows, you should set one of three images in cellForRow method depending on the cell's indexPath.row.
P.S. Don't listen to your iOS developer. You should not use UIWebView whenever you need to implement UI a bit more complex than default UITableView. Most probably he was joking ;)

Two TableViews that have same prototype cell, constraints behave differently

I created two ViewControllers and two TableViews. Then i added prototype cell to one TableView, set it up according to my needs, copied it to the other TableView, changed its class and identifier and linked it up in ViewController that is datasource and delegate for each one.
The problem is, FEEDING one is behaving good, having constraints as expected, and the WALKING one is not, but i have no idea why since they have all same properties in each one's:
ViewControllers:
FEEDING
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myFeedingTableView.dequeueReusableCellWithIdentifier("feedingcell", forIndexPath: indexPath) as! FeedingCell
cell.time.text = self.vremena[indexPath.row]
return cell
}
WALKING
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myWalkingTableView.dequeueReusableCellWithIdentifier("walkingcell", forIndexPath: indexPath) as! WalkingCell
cell.time.text = self.vremena[indexPath.row]
return cell
}
CustomCell files
each one is connected to its class
FeedingCell is class of feeding prototype cell
WalkingCell is class of feeding prototype cell
Constraints
and the constraints are same, as you can see on the picture.
Here is the image providing different results and constraints:
image
Solved by changing rowHeight settings in TableView. Thanks #SilentLupin

How do I fix the illegal configuration?

I have a .xib file and I want that to be a table view controller, but when I create a table view controller in the .xib file I get the error:
Table views with embedded sections and cells are only supported in storyboard documents.
How do I fix this?
Below is my code for the actual table view:
self.add = [Play(name: title!), Play(name: artist!), Play(name: album!)]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.add.count
//return count of objects in array
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
var play: Play
play = add[indexPath.row]
cell.textLabel?.text = play.name
return cell
}
Xibs are sort of out dated, and when they were invented, didn't have prototype cells that you could make in the Interface Builder.
When storyboards were introduced this functionality was made as well, except not back ported to the xib editor, so you can't use prototype cells in a xib unfortunately, you will need to make separate xibs for the cells layout.

Creating a Cocoa file

I created a Cocoa file with a subclass of UITableViewCell so I could have an outlet to a prototype cell. Would this work?
Create an array with the titles for all your labels. For instance:
var titleLabels: [String] = ["Eggs", "Cheese", "Milk"]
Load your custom cell class (put this in viewDidLoad):
var nib = UINib(nibName: "YourCellSubclass", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
Then implement cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.detailLabel?.text = self.titleLabels[indexPath.row]
return cell
}
(Make sure you remember to set this class as the datasource for your tableview).
To include the quantities on the cells, you'll need another array of numbers (or better yet, an array of objects, each with a name and quantity attribute). Then (assuming your cell has two labels - one for the title, one for the quantity) you can set both the titleLabel and quantityLabel on your cell in the same manner as shown above.
This article has a pretty good walkthrough on creating custom tableview cells: https://www.weheartswift.com/swifting-around/
You could create the custom UITableViewCell with two UILabels, for example titleLabel and detailLabel. In the custom initWithStyle method, you can set the desired position of the labels within the cell.
After initializing the custom cell, you could write another method in the custom UITableViewCell class that sets the text values of titleLabel and detailLabel, and call that method from cellForRowAtIndexPath. The desired values could come from an NSArray, and you would use the cell row, indexPath.row, to fetch the corresponding values from the array.

Resources