Is there a way of styling UITableViewCell in swift? - ios

I have a table view and I am adding several cells to it based on my json.
So far the code for adding cells looks as follows:
override func viewWillAppear(animated: Bool) {
let frame:CGRect = CGRect(x: 0, y: 90, width: self.view.frame.width, height: self.view.frame.height-90)
self.tableView = UITableView(frame: frame)
self.tableView?.dataSource = self
self.tableView?.delegate = self
self.view.addSubview(self.tableView!)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CELL")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
}
let user:JSON = JSON(self.items[indexPath.row])
cell!.textLabel?.text = user["description"].string
var photoURL = "/path/to/my/icon/google.png"
if let data = NSData(contentsOfFile: photoURL)
{
cell!.imageView?.image = UIImage(data: data)
}
return cell!
}
Besides the description in my json I have also username and price. So far - since I'm adding only imageView and description, 3 cells look like this:
Is there a way to style it so that each cell looks similar to this:
(price and username are grey here`)? How can I achieve this effect?
===EDIT:
this is how I populate my table:
I'm fetching data from rest webservice to json:
func getAllUsers() {
RestApiManager.sharedInstance.getUsers { json in
let results = json
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
self.items.addObject(user)
dispatch_async(dispatch_get_main_queue(),{
self.tableView?.reloadData()
})
}
}
}
and I invoke this method in my viewWillAppear function

You can make your table use custom UITableViewCells and style them to your liking.
In a nutshell, you create a prototype cell in Storyboard that looks like the example you posted and connect it to a custom UITableViewCell class with the elements you created. At cellForRowInIndexPath you return your custom cell rather than regular UITableViewCells.
Check out this tutorial for details: http://shrikar.com/uitableview-and-uitableviewcell-customization-in-swift/

Create the layout of the cell using a custom style. Place labels and imageView like you would anywhere else in storyborad.
You will need to create a UITableViewCell file. The one I used is named ExampleTableViewCell. Make note of the subclass.
Now connect your cell to the ExampleTableViewCell you just created.
Now we can make outlets from the labels and imageView of the cell into the ExampleTableViewCell. Control drag from each element into the ExampleTableViewCell.
The final step is to configure the cell using the cellForRowAtIndexPath func. Make note of the var cell. We now cast this to the ExampleTableViewCell. Once we do this we can use the outlets in the ExampleTableViewCell to set our labels and image. Make sure you set the resuseIdentifier for the cell in the storyboard. If you are unfamiliar with this leave a comment and I can add instructions for this.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier") as! ExampleTableViewCell
cell.imageDisplay.image = yourImage
cell.descriptionLabel.text = yourDescription
cell.priceLabel.text = yourPrice
cell.usernameLabel.text = yourUsername
return cell
}

Subclass UITableViewCell. You can go to the TableView on your storyboard and go to one of the prototypes and set it's class to your custom class and it's style to Custom and then you can ctrl+click & drag outlets/actions to the UITableViewCell subclass the same way you would for a basic view controller.

Related

After tableView scrolled data puts in cells in wrong order

in my View:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TransactionTableCell", for: indexPath) as! TransactionTableCell
let newItem = getTransactionsInSection(section: sectionHeader[indexPath.section])[indexPath.row]
cell.configure(item: newItem)
}
in my TransactionTableCell
func configure(item: TransactionModel) {
guard let withdrawalBonuses = item.withdrawalBonuses,
withdrawalBonuses < 0,
let accruedBonuses = item.accruedBonuses,
accruedBonuses > 0 else {
configureWithOneOperation(item)//shows one line of operation
return
}
//show 2 lines of operations
firstOperationAmountLabel.text = "+\(Int(accruedBonuses))"
secondOperationAmountLabel.text = "\(Int(withdrawalBonuses))"
}
When I scroll the cell , second operation line is appears in wrong cells where its shouldn't be, even If I reload my table , that also has this problem.
You should use prepareForReuse() method
Simply just clear data of your labels:
override func prepareForReuse() {
super.prepareForReuse()
firstOperationAmountLabel.text = nil
secondOperationAmountLabel.text = nil
}
There are few things to check here.
Make sure you reset all fields before configure a new cell.
If you have created a cell using xib or storyboard, make sure you haven't filled labels with static text.
Is your guard statements passing for every item?
Else block for guard configures cell with a single operation, Is it handling all ui elements in cell?

Linking multiple custom UITableViewCell's to single xib

Question
I'm creating a re-usable custom UITableViewCell and then subclassing it for re-use. How do I set these various subclasses to link to the same xib file?
Background
Let's call my custom UITableViewCell PickerTableViewCell. This cell includes a UIPickerView, as well as all the implementations as to how the picker view looks and behaves. When I want to use this cell, the only thing I need to give it is the data for the picker view. So I subclass PickerTableViewCell, then simply create the data source I need and assign it to the picker view. So far this has all worked well.
Here are the relevant parts of PickerTableViewCell:
class PickerTableViewCell: UITableViewCell {
var picker: UIPickerView! = UIPickerView()
var pickerDataSource: PickerViewDataSource!
override func awakeFromNib() {
super.awakeFromNib()
self.picker = UIPickerView(frame: CGRect(x: 0, y: 40, width: 0, height: 0))
self.picker.delegate = self
self.assignPickerDataSource()
}
// Must be overriden by child classes
func assignPickerDataSource() {
fatalError("Must Override")
}
Here is an example of a subclass:
class LocationPickerTableViewCell: PickerTableViewCell {
override func assignPickerDataSource() {
self.pickerDataSource = LocationPickerDataSource()
self.picker.dataSource = self.pickerDataSource
}
}
Problem
Since I am using these cells all over the place, with different data sources, I created a xib file which defines how the cell looks called PickerTableViewCell.xib, and assign it to the class PickerTableViewCell. In the view controllers I want to use it for, I register the cell with the table view inside viewDidLoad(). Then, inside func tableView(_:, cellForRowAt) I dequeue the subclass I want like this:
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! LocationPickerTableViewCell
return cell
This is where the problem happens. The cell that is created is a PickerTableViewCell, not its subclass LocationPickerTableViewCell. This runs into the fatal error I placed in the parent class which is overriden by the child class.
The only way I have found to solve this is to create a separate xib file for each subclass I want to create, and assign it to the relevant subclass. While this solution does work, it feels wrong to have all of these xib files which are practically identical (except for which class they are assigned to) inside my project.
Is there a way I can overcome this problem, and have all of these cells link to the same single xib file?
Thanks! :)
Add view loaded by xib to UITableViewCell classes in which you want to use it.
Create your xib as per your require design, in your example PickerTableViewCell.xib
Create UITableViewCell sub-classes in which you want to use that view. I am using FirstTableViewCell & SecondTableViewCell for this.
in constructor of table cell load the xib and add it to table cell.
let nib = Bundle.main.loadNibNamed("PickerTableViewCell", owner: nil, options: nil)
if let view = nib?.first as? UIView{
self.addSubview(view)
}
if xib have any #IBOutlet then get them by viewWithTag function and assign to class variables
if let label = self.viewWithTag(1) as? UILabel{
self.label = label
}
override reuseIdentifier var of each tableviewCell subclass with different name
override var reuseIdentifier: String?{
return "FirstTableViewCell"
}
Now You can use these classes where you want, for using this follow below steps:
register this tableviewCell subclass with xib with tableview:
tableView.register(FirstTableViewCell.self, forCellReuseIdentifier:"PickerTableViewCell")
now in cellForRowAt indexPath method use it.
var cell = tableView.dequeueReusableCell(withIdentifier: "FirstTableViewCell", for: indexPath) as? FirstTableViewCell
if cell == nil {
cell = FirstTableViewCell()
}
cell?.label?.text = "FirstTableViewCell"
Don't use subclassing to assign different data sources.
Approach 1: Assign pickerDataSource in tableView(_:cellForRowAt:)
In the table view controller, you need to assign pickerDataSource
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! PickerTableViewCell
cell.pickerDataSource = LocationPickerDataSource()
return cell
}
Handle additional work needed after the assignment of pickerDataSource with a didSet.
class PickerTableViewCell: UITableViewCell {
var picker: UIPickerView! = UIPickerView()
var pickerDataSource: PickerViewDataSource! {
didSet {
self.picker.dataSource = self.pickerDataSource
}
}
…
}
Approach 2: Extend PickerTableViewCell in all the needed ways.
Here instead of subclassing add the needed logic to a uniquely named setup method each defined in their own extension.
extension PickerTableViewCell {
func setupLocationPickerDataSource() {
self.pickerDataSource = LocationPickerDataSource()
self.picker.dataSource = self.pickerDataSource
}
}
then in tableView(_:cellForRowAt:)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! PickerTableViewCell
cell.setupLocationPickerDataSource()
return cell
}

Swift 4 UITableView make one cell as UITextView

I have a dynamic Table as UITableView and all cells are as normal (retrieved form my array)
However I need only one cell as TextView to be able input text. On text Change I need to retrieve the text user input.
How to make this?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count+1 //to allow this input field
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row < array.cont){
//normal cell from array
let cell = Table.dequeueReusableCell(withIdentifier: "myCell")
cell?.textLabel?.text = array[indexPath.row]
cell?.isUserInteractionEnabled = true;
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.textAlignment = .center;
return cell!;
}else{
//create input text field (DON'T KNOW HOW)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(indexPath.row < array.cont){
//.. perform action ..
}else{
//retrieve input text (DONT know How)
}
}
Creating UITextView inside UITableViewCell is quite simple :
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 20, width: 311.00, height: 50.00)) // Set frames as per requirements
textView.textAlignment = NSTextAlignment.justified
cell.contentView.addSubView(textView)
But this would lead to incorrect values while scrolling the table. Best approach would be to create a custom cell and add UITextView there. Here is the custom cell. Keep the constraints intact.
Before using the custom cell, you need to register it in your table. So :
let nib = UINib(nibName: "TextCell", bundle: nil)
Table.register(nib, forCellReuseIdentifier: "TextCell")
Don't forget to put identifier of cell in xib.
Now in cellForRowAtIndexPath :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row < array.cont){
//normal cell from array
let cell = Table.dequeueReusableCell(withIdentifier: "myCell")
cell?.textLabel?.text = array[indexPath.row]
cell?.isUserInteractionEnabled = true;
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.textAlignment = .center;
return cell!;
}else{
//create input text field (DON'T KNOW HOW)
let cell = tableView.dequeueReusableCell(withIdentifier: "TextCell", for: indexPath) as! TextCell
// Access UITextView by cell.textView
return cell
}
}
The main issue is - dynamic cell size as per UITextView height. But that entirely depends on your requirement. You can follow this post for that.
You can achieve this with delegation pattern or NSNotification.
Here's the solution for this using delegation pattern.
Create new UITableViewCell using xib and add the textView on contentView of cell, set the reuse identifier and than register the xib in the ViewController with
tableView.register(UINib(nibName: "name of the xib file", bundle: nil), forCellReuseIdentifier: "Identifier here")
Now define protocol anywhere outside of the class
// You can give any name
// Here we are confirming to class to avoid any retain cycles
protocol CustomCellDelegate :class {
func returnText(text :String)
}
Now initialise " var delegate : CustomCellDelegate? " in same class of UITableViewCell that we created above while creating xib.
and confirm to protocol UITextViewDelegate and than in the cell class write this
override func awakeFromNib() {
super.awakeFromNib()
textView.delegate = self
}
after that add these functions in same class of tableViewCell
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
delegate.returnText(text : textView.text ?? "")
}
Now in the ViewController class
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == array.count { // this will call the cell with TextView at the end , you can play with any indexPath to call the cell
let cell = tableView.de... // Deque the cell with TextView here using reuse identifier
cell.delegate = self
return cell
}
else {
// deque other cells
}
}
we'll write an extension of ViewController and confirm to our custom protocol
extension ViewController : CustomCellDelegate {
// this function will get called when you end editing on textView
func returnText(text :String) {
print(text) // you may save this string in any variable in ViewController class
}
}
Add a TextView in to your custom cell, hide it, show when you need
if you want to have the textView on the top of all cells, drag and drop a UIView inside the tableView before the cell.
this view will scroll with cells.
design this view as you need insert a textView inside it, and use textView's delegate methods to perform operations.

Swift: How can I post data from three separate text fields onto labels in a custom cell, as the result of pressing a button?

I am new to swift, and I am trying to take text from one textfield and values from two other text fields (one value from each text field) on a ViewController, and then when a button is pressed, have all three pieces of information displayed horizontally in a cell in a tableview.
This is an example of what I am trying am trying to do:
I would like the data from text fields one, two and three to be displayed in labels one, two and three, once the button is pressed.
#IBAction func btn_reload(sender: AnyObject)
{
/*lbl1.text = txtfield1.text!
lbl2.text = txtfield2.text!
lbl3.text = txtfield3.text!*/
var arrmute: [AnyObject] = [AnyObject]()
var arrmute2: [AnyObject] = [AnyObject]()
var arrmute3: [AnyObject] = [AnyObject]()
arrmute.append(txtfield1.text!)
arrmute2.append(txtfield2.text!)
arrmute3.append(txtfield3.text!)
self.tbl_out.reloadData()
}
First setup set delegate and datasource of UITabelView in your storyboard.
Give reuseIdentifier of row as given in code: cell
Make outlet of your UITableView.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return arrmute.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell: UITableViewCell
if cell == nil
{
cell = UITableViewCell(style: .Default, reuseIdentifier: "cell")
}
cell.lbl1.text = arrmute[indexPath.row]
cell.lbl2.text = arrmute2[indexPath.row]
cell.lbl3.text = arrmute3[indexPath.row]
return cell
}

Adding various content type within Table View Cell

I am new to IOS programming and will need some direction here.
I am trying to create a tableview with each rows having a image and some text.
I am able to take a TableViewController and programmatically was able to add basic text and rows. But can you please tell me how should i add more complex content. Trying to achieve something like this using program.
My current code looks like this in my TableViewController and its able to print a text message on each row.
override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
var mycell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("frontcell", forIndexPath: indexPath) as UITableViewCell
mycell.textLabel.text = "Just a generic message"
return mycell
}
There is a
mycell.contentView
which takes in UIView. But how does it works? How should this object be created?
Appreciate your help.
------Update -----
Thanks to Suryakant for helping out. Answer below for his step by step how to do. Any one who needs the source code can use this. http://pastebin.com/ZfNqK4tW
Though you can achieve it by default UITableViewCell also, as #meda mention in his answer,
but it seems, you want different UIImageView size and 2 UILabels with different font size or may be some more controls there. For Achieving that you need to customize UITableViewCell and you can do that by subclassing UITableViewCell class.
Create a class by subclassing UITableViewCell.
e.g. your subclass say MyCell look like —
2.Go to storyboard and select prototypeCell and select Identity inspector, in Class type your custom class name (e.g MyCell )in place of UITableViewCell.
drag-n-drop all the controls you need and link with their IBOutlets (From MyCell to prototypeCell).
This goes as below..
3.Now goto Attributes Selector and give some Identifier to your MyCell, you can give any string you want.
4.Goto the class where you implemented UITableView delegates and update your cellForIndexPath as bellow
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath:
NSIndexPath!) -> UITableViewCell! {
let kCellIdentifier:String = "cell"
var cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as MyCell!
if cell == nil {
// register Custom UITableView Class to UITableView
tableView.registerClass(MyCell.classForCoder(), forCellReuseIdentifier: kCellIdentifier)
cell = MyCell(style: UITableViewCellStyle.Default, reuseIdentifier: kCellIdentifier)
}
if var label = cell.cellMyCity{
label.text = cityList[indexPath.row]
}
if var label = cell.cellMyCountry{
label.text = countryList[indexPath.row]
}
if var imageView = cell.imageView{
imageView.image = UIImage(named :"img.png")
}
return cell
}
For reference you can see example code here.
You would create a subclass of UITableViewCell and then assign values to the property of your cell
mycell.textLabel.text = "Just a generic message"
mycell.detailTextLabel.text = "Just a detail message"
mycell.imageView.text = myImage
And for that you would use only one prototype Cell no need to duplicate them.

Resources