I am relatively new to UIKit. Currently, I am trying to create a UISwitch that will show up on a specific UITableView cell. However, I can't seem to figure out how to do this. Instead, I am getting a UISwitch on every single cell in the UITableView.
My code is below:
import UIKit
class SettingsVC: UIViewController {
var tableView = UITableView(frame: .zero, style: .insetGrouped)
let cells = ["Change Accent Color", "Change Currency Symbol", "Vibrations"]
let cellReuseIdentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
createTableView()
setTableViewDelegates()
}
func createTableView() {
view.addSubview(tableView)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
func setTableViewDelegates() {
tableView.delegate = self
tableView.dataSource = self
}
}
extension SettingsVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
return UITableViewCell()
}
cell.textLabel?.text = cells[indexPath.row]
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
cell.accessoryView = switchView
return cell
}
}
This is how my UITableView looks currently in the simulator.
This is how I would like the UITableView to look.
How would I be able to achieve the look I'm going for? Any help would be greatly appreciated.
The method tableView(_:cellForRowAt:) is used to create all cells for a table, so the code inside this method is called for each cell. You need to figure out a condition that distinguishes the cell with a UISwitch and run the corresponding piece conditionally. Conceptually, something like this:
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
return UITableViewCell()
}
cell.textLabel?.text = cells[indexPath.row]
if isSwitchNeeded { // Here.
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
cell.accessoryView = switchView
}
return cell
}
There are some architectural options that might allow you do that. One of them is to rely on the index path. For instance, this should work in your raw example:
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
return UITableViewCell()
}
cell.textLabel?.text = cells[indexPath.row]
if indexPath.row == 2 {
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
cell.accessoryView = switchView
}
return cell
}
And a million other ways.
First of all most likely you want to save the value of the switch, so create a property on the top level of the view controller
var enableVibrations = false
Second of all cells are reused. Even if there are only three cells it's good practice to set all UI elements to a defined state, that means to set the accessory view to nil if there is no switch.
And there is a dequeueReusableCell API which returns a non-optional cell.
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let title = cells[indexPath.row]
cell.textLabel?.text = title
if title == "Vibrations" {
let switchView = UISwitch(frame: .zero)
switchView.setOn(enableVibrations, animated: true)
switchView.addTarget(self, action: #selector(toggleVibrations), for: .valueChanged)
cell.accessoryView = switchView
} else {
cell.accessoryView = nil
}
return cell
}
And add the action method
#objc func toggleVibrations(_ sender : UISwitch) {
self.enableVibrations = sender.isOn
}
Related
I have a controller EditProfileController: UITableViewController and a cell EditProfileCell: UITableViewCell
extension EditProfileController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! EditProfileCell
cell.delegate = self
return cell
}
}
My EditProfileCell
This is my TextField:
class EditProfileCell: UITableViewCell {
lazy var infoTextField: UITextField = {
let tf = UITextField()
tf.borderStyle = .none
tf.font = UIFont.systemFont(ofSize: 14)
tf.textAlignment = .left
tf.textColor = .white
tf.isUserInteractionEnabled = true
return tf
}()
}
The Problem: I can edit the infos for the textfields using the simulator. But when I'm running the app on my iPhone, I'm not able to even select the Textfield, not even the keyboard shows up.
I've tested adding cell.infoTextField.becomeFirstResponder() on the extension, and it works, but this makes only the last textfield become editable.
What I'm doing wrong?
You have to set contentView.isUserInteractionEnabled to false
extension EditProfileController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! EditProfileCell
cell.delegate = self
cell.contentView.isUserInteractionEnabled = false
return cell
}
}
I want my tableView to have subtitle as well as being able to dequeue properly. I have referred to this link but it does not work for my code. What should I do?
My code is currently like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
//Calling tableview for a reusable cell here will always return a cell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = developerArray[indexPath.row].developerName
cell.detailTextLabel?.text = developerArray[indexPath.row].developerHP
return cell
}
Swift 5
//Declare the variable cell Identifier
let reuseCellIdentifier = “cellIdentifier”;
//Implementation of cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: reuseCellIdentifier)
if (!(cell != nil)) {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: reuseCellIdentifier)
}
cell?.textLabel?.text = //Title text
cell?.detailTextLabel?.text = //Subtitle text
return cell!
}
You can create custom cell using nib by adding labels
For creating custom cell refer this link:
Custom UITableViewCell from nib in Swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.title.text = yourTitleArray[indexPath.row]
cell.detailLbl.text = yourDetailArray[indexPath.row]
return cell
}
So first of all, did you create your table using storyboard or code?
Either way you need to make sure you set the datasource and delegate to self, provided the class they are in conforms to :
UITableViewDataSource
and
UITableViewDelegate
myTable?.delegate = self
myTable?.dataSource = self
Also make sure you register your cell
myTable?.register(myCell.self, forCellReuseIdentifier: "myCell")
And when declaring your cell, you need to force it as the type
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! myCell
Below is a working sample of tableview with a created cell. I hope this helps.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// Create the tableview object
var myTable:UITableView?
override func viewDidLoad() {
// Set the size and location of the tableview
myTable = UITableView(frame: CGRect(x: 0, y: 0, width: 300, height: 600))
// register your cell
myTable?.register(myCell.self, forCellReuseIdentifier: "myCell")
// set the background color of the table, note this wont make a difference unless the cell background is changed.
myTable?.backgroundColor = UIColor.clear
// set the datasource and delegate to self
myTable?.delegate = self
myTable?.dataSource = self
// This is jsut for style, wether there should be seperators or not, and if the user can select multiple lines
myTable?.separatorStyle = .none
myTable?.allowsMultipleSelection = true
// Add the table to your view
self.view.addSubview(myTable!)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// This is declaring how many rows you want in your table. I have 1 but you can do it according to the size of your array.
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create the cell
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! myCell
// set the title text for this cell
cell.title.text = "HelloWorld"
// return the cell
return cell
}
}
and this is the class for the cell we referenced above.
class myCell: UITableViewCell {
var title = UILabel()
var detail = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
title.font = UIFont.boldSystemFont(ofSize: 16)
title.textAlignment = .center
self.contentView.addSubview(title)
self.contentView.addSubview(detail)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
title.frame = CGRect(x: self.contentView.frame.width / 2 - 200, y: 6, width: 400, height: 20)
detail.frame = CGRect(x: 150, y: 10, width: 280, height: 20)
}
}
Let me know if this helps. if you are doing it from story board let me know and I'll adjust.
I've created a UITableView inside my ViewController. Here's the code I've added to fill the TableView with content. However, no content is showing up. I've created a customized tableView Cell which I am calling in the cellNib as PostTableViewCell. When the program is running, it shows the prototype cells with the divider lines, but none of the custom content.
import Foundation
import UIKit
import Firebase
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView:UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
//tableView.backgroundColor = UIColor.blue
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
layoutGuide = view.layoutMarginsGuide
}
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
// Do any additional setup after loading the view.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
You're not actually configuring the cell in your tableView:cellForRowAt: method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
return cell
}
Typically you need to insert values into the elements of your custom cell. The "content" of your cell label text, images etc is usually inserted into the cells elements in this method. If you look at the code in a new Xcode project you will see:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let object = objects[indexPath.row] as! NSDate
cell.textLabel!.text = object.description
return cell
}
Note the line retrieving the data: let object = objects[indexPath.row] as! NSDate
Followed by the line in setting the text value of the textLabel:
cell.textLabel!.text = object.description
Code looks right..
Make sure you have assign content to table cell in cellForRowAt datasource methods.
I am creating a UITableView using code, I am not using any storyboard or IB.
Following is my code:
class UserListController: UIViewController , UITableViewDelegate, UITableViewDataSource {
var chatTableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
func setChatTableView(){
chatTableView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
chatTableView.topAnchor.constraint(equalTo: chatSegmentControl.bottomAnchor).isActive = true
chatTableView.widthAnchor.constraint(equalTo:view.widthAnchor).isActive = true
chatTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(chatTableView)
setChatTableView()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
let cellIdentifier = "cellId"
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath
// ) as UITableViewCell
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
cell.textLabel?.text = "LaLaLa"
return cell
}
I have created chatTableView which is my TableView and have add this on the main view i am not using UITableViewController.
I can see the table view on screen but there is no message in rows so what I should do to populate table view please help.
I am new to the ios and using a swift 4.
Thank you in advance.
Please provide UITableViewDataSource and UITableViewDelegate
var chatTableView: UITableView = {
let table = UITableView()
table.dataSource = self
table.delegate = self
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
You can also get full tutorial over Here.
I do know how to input background colours for my row, but I don't really know how I can filter it by only the bottom 5 rows are "cell.backgroundColor = UIColor.red;" whereas the rest stays the same. Appreciate those who can help me this thanks!
P.S: Sorry as my swift is quite rusty.
UITableView Controller
import UIKit
import FirebaseDatabase
var postData2 = [String]()
var postData3 = [String]()
var tableDataArray = [tableData]()
class ResultsController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference() //set the firebase reference
// Retrieve the post and listen for changes
databaseHandle = ref?.child("Posts3").observe(.value, with: { (snapshot) in
postData2.removeAll()
postData3.removeAll()
tableDataArray.removeAll()
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let value = String(describing: snap.value!)
let rating = (value as NSString).integerValue
postData2.append(key)
postData3.append(value)
tableDataArray.append(tableData(boothName: key, boothRating: rating))
}
postData2.removeAll()
postData3.removeAll()
let sortedTableData = tableDataArray.sorted(by: { $0.boothRating > $1.boothRating })
for data in sortedTableData {
postData2.append(data.boothName)
let value = String(describing: data.boothRating)
postData3.append(value)
}
self.tableView.reloadData()
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postData2.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.font = UIFont.init(name: "Helvetica", size: 23)
cell.textLabel?.text = postData2[indexPath.row]
cell.detailTextLabel?.text = postData3[indexPath.row] + " ♥"
cell.detailTextLabel?.textColor = UIColor.red;
cell.detailTextLabel?.font = UIFont.init(name: "Helvetica", size: 23)
// cell.backgroundColor = UIColor.red;
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 80
}
}
class tableData {
var boothName: String
var boothRating: Int
init(boothName: String, boothRating: Int) {
self.boothName = boothName
self.boothRating = boothRating
}
}
A simple way is to have an conditional check to see if the indexPath.row value is within the last five.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(indexPath.row >= postData2.count-5){
cell.backgroundColor = UIColor.red
}else{
cell.backgroundColor = UIColor.white /* Remaining cells */
}
return cell
}
Some of the other answers will work - but it is nicer to use cells that have a known configuration when they are dequeued by cellForRowAt, not deal with a bunch of possible starting conditions each time you dequeue a cell. To do this subclass the UITableViewCell and override prepareForReuse(). This function will be called just before a cell is returned by dequeueReusableCell. Then cells can be set to a known starting point before you configure them. If cells could be received configured any possible way in cellForRowAt, you soon wind up with a very long function with a lot of if/else conditions.
The condition
if indexPath.row >= postData2.count - 5 {
cell.backgroundColor = UIColor.red
}
can be used as it is, and prepareForReuse takes care of the cells not keeping any settings when they are recycled. Here's an example:
override func prepareForReuse() {
super.prepareForReuse()
backgroundColor = UIColor.white
}
With this one simple setting it's a wash whether you do the if/else approach or use subclassing to make the most of prepareForReuse. But as soon as you have more than one thing to set in a cell you will find it is far less complex to use this function and results in far fewer mistakes with the appearance of cells - consider what would happen if there were more than one possible color a cell could be, or there were multiple elements in the cell to be configured with multiple possible values...
You can add simple logic
if indexPath.row >=(postData2.count-5) {
cell.backgroundColor = UIColor.red
}else {
cell.backgroundColor = UIColor.white
}
Just check a condition for setting the red colour for last five rows.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(indexPath.row >= postData2.count-5){
cell.backgroundColor = UIColor.red;
}else {
cell.backgroundColor = UIColor.white; //white colour for other rows
}
return cell
}
This method is recommended by the system, this method is more circumventing reuse in some cases (like when you modify the contents of a control in the cell surface)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "cell")
// Not the type of cell, if the queue will return nil, at this time requires create ⼀ cell
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
}
}
If it involves data processing, you can create a new NSMutableSet(), Used to store your operations (ordinary data is lost, stored in the didSelecetRow inside indexPath like) save anyway, a unique tag.
These are just solve the problem of multiplexing, to deal with discoloration, refer to the above solution.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if(indexPath.row >= postData2.count-5){
cell.backgroundColor = UIColor.red
}else{
cell.backgroundColor = UIColor.white /* Remaining cells */
}
return cell
}