I have a view controller with a tableview. I want to register my tableview with a custom uitableviewcell that i created programmatically with a xib file. inside the custom cell i have a function that would change the outlets labels values and this is where i am getting a fatal found nil error. here is my code for the view controller.
class AllTasksViewController: UIViewController{
var tableView = UITableView()
func configureTableView(){
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 50
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.register(CustomCellNSB2.self, forCellReuseIdentifier: "CustomCellNSB2")
}
func setTableViewDelegates(){
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLoad(){
super.viewDidLoad()
configureTableView()
print("allTasksView loaded")
//add some positioning and size constraints here for allTasksView
}
}
extension AllTasksViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allTasks.count
}
// Cell Data and Configuration
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let task = allTasks[indexPath.row] // creating a new task from the already stored task depending on the indexpath.row if indexPath.row is 3 then the task is tasks[3]
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCellNSB2", for: indexPath) as! CustomCellNSB2 // setting the identifier ( we have already set in the storyboard, the class of our cells to be our custom cell)
cell.setTask(task: task) `// this is where my app is crashing`
if (task.value(forKey: "isComplete") as! Bool == true){
cell.labelsToWhite()
cell.backgroundColor = Colors.greencomplete
cell.selectionStyle = .none
} else {
cell.backgroundColor = .white //adjust for nightmode later
cell.labelsToBlack()
}
print("CellData Task :", task.value(forKey: "isComplete") as! Bool, task.value(forKey: "name") as! String)
// if !(task.value(forKey: "isComplete") as! Bool){
// cell.backgroundColor = task.isImportant ? .purple : .white //according to the task isImportant attribute we set the background color
// }
return cell
}
and here is my custom uitableview cell
import UIKit
import CoreData
class CustomCellNSB2: UITableViewCell {
#IBOutlet weak var taskLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setTask(task: NSManagedObject ){ debugger leads me here. this is where it's getting nil
taskLabel.text = task.value(forKey: "name") as? String
dateLabel.text = task.value(forKey: "date") as?
String
}
func labelsToWhite() {
taskLabel.textColor = .white
dateLabel.textColor = .white
}
func labelsToBlack() {
taskLabel.textColor = .black
dateLabel.textColor = .red
}
}
You have to register your NIB ... all you are doing is registering the class.
Change this:
tableView.register(CustomCellNSB2.self, forCellReuseIdentifier: "CustomCellNSB2")
to this:
tableView.register(UINib(nibName: "CustomCellNSB2", bundle: nil), forCellReuseIdentifier: "CustomCellNSB2")
If you have the xib/nib configured correctly, that may be all you need to do.
Related
I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}
I'm tired of searching about what I want, so I will ask here and hope you guys help if possible.
I have a tableview contain segments in each cell and I want to save all cells segment using button [outside the tableview] so I can show them in another table later.
Here is my tableview
here is my view Controller:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let arr = ["item 1",
"item 2",
"item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "cell")
}
#IBAction func saveButtonAction(_ sender: UIButton) {
// I want to save the cells segmented control selectedSegmentIndex???
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
90
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.textView.text = arr[indexPath.row]
return cell
}
}
and here is my Custom Cell and it contain TextView and Segment Controller I tried to save the segments changes in array but I don't know what to do after lol:
import UIKit
class CustomCell: UITableViewCell {
let textView: UITextView = {
let tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.font = UIFont.systemFont(ofSize: 16)
tv.isEditable = false
return tv
}()
// var rowIndexPath: Int?
var segmentArray: [Int] = []
lazy var itemSegmentedControl: UISegmentedControl = {
let sc = UISegmentedControl(items: ["1", "2", "3"])
sc.translatesAutoresizingMaskIntoConstraints = false
sc.tintColor = UIColor.white
sc.selectedSegmentIndex = 0
sc.addTarget(self, action: #selector(handlePointChange), for: .valueChanged)
return sc
}()
let stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
#objc func handlePointChange() {
let segChange = itemSegmentedControl.titleForSegment(at: itemSegmentedControl.selectedSegmentIndex)!
segmentArray.append(Int(segChange)!)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
stackView.addSubview(textView)
stackView.addSubview(itemSegmentedControl)
addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
stackView.widthAnchor.constraint(equalToConstant: 400).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 85).isActive = true
itemSegmentedControl.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
itemSegmentedControl.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 5).isActive = true
itemSegmentedControl.widthAnchor.constraint(equalTo: textView.widthAnchor).isActive = true
itemSegmentedControl.heightAnchor.constraint(equalToConstant: 40).isActive = true
textView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true
textView.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
textView.widthAnchor.constraint(equalTo: stackView.widthAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
Regards and thanks.
First: in the ViewController I added the empty array:
// I moved the segment array from the custom cell class to here
var segmentArray: [Int:Int] = [:]
override func viewDidLoad() {
super.viewDidLoad()
// I made this array to store default state to the items segments
segmentArray = [0:0,1:0,2:0]
}
// with this func I can update the segmentArray
func getSegmentNumber(r: Int, s: Int) {
segmentArray[r] = Int(s)
}
#IBAction func saveButtonAction(_ sender: UIButton) {
// Here I can save the new state for the segment to the firebase or anywhere.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.textView.text = arr[indexPath.row]
// here to store the index path row
cell.rowIndexPath = indexPath.row
// you need to add the link because it won't work
cell.link = self
return cell
}
Second: in the Custom Cell Class:
// I removed the store array
var segmentArray: [Int] = [] // removed
// I made link to the ViewController
var link: ViewController?
// variable to get the index path row
var rowIndexPath: Int?
// here I can update the state of the segment using the link
#objc func handlePointChange() {
let segChange = itemSegmentedControl.selectedSegmentIndex
link?.getSegmentNumber(r: rowIndexPath!, s: segChange)
}
That worked for me, and I hope you got it.
If you need anything you can ask.
Again thanks to Kudos.
Regards to all
I have a tableview cell in my story i'm trying to register it with my custom tableview. inside the tableview cellforrow function i'm manipulating my cell labels and it's giving me nil. I believe since the cell is not registered yet. does the cellforrow loads before the viewdidload method? my code is as following.
import UIKit
import CoreData
class AllTasksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
override func viewDidLoad(){
super.viewDidLoad()
self.storyboard?.instantiateInitialViewController()
tableView.register(CustomCell.self, forCellReuseIdentifier:"taskCell")
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
tableView.reloadData()
print(coreTasks.count)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return coreTasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let task = coreTasks[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell") as! CustomCell
cell.setTask(task: task) // this changes the label and date text since an instance of the task contains both the task and the date
return cell
}
}
The error that i am facing is in my custom tableview cell function "setTask".
class CustomCell: UITableViewCell {
#IBOutlet var taskLabel: UILabel!
#IBOutlet var dateLabel: UILabel!
// #IBOutlet var iconsCell: UIImageView!
func setTask(task: NSManagedObject ){
taskLabel.text = task.value(forKey: "name") as? String
dateLabel.text = task.value(forKey: "date") as?
String
}
func labelsToWhite() {
taskLabel.textColor = .white
dateLabel.textColor = .white
}
func labelsToBlack() {
taskLabel.textColor = .black
dateLabel.textColor = .red
}
So what i think is your problem is that you are setting the dataSource before setting the delegate. This means that you are creating a standard UITableView with the correct data, and then saying by the way i want it to look like this but its already set up so nothing is happening.
This is how you have done it:
override func viewDidLoad(){
super.viewDidLoad()
...
tableView.dataSource = self
tableView.delegate = self
...
}
What you should do is flipping this like this:
override func viewDidLoad(){
super.viewDidLoad()
...
tableView.delegate = self
tableView.dataSource = self
...
}
I'm trying to build a tableView with a custom cell using UITableView programmatically without using a Storyboard but after I finish the tutorial and try to run the app the CustomCell I build,
The custom cell shows but once I click on tableView the custom cell hiding
Can someone check if I miss something?
Or maybe it's wrong to use xib with programmatically made tableView?
HomeViewController.swift
class HomeViewController: UIViewController {
fileprivate let homeTableView: UITableView = {
let htm = UITableView()
htm.translatesAutoresizingMaskIntoConstraints = false
return htm
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
homeTableView.register(CusTableViewCell.nib, forCellReuseIdentifier: CusTableViewCell.identifier)
setupTableView()
}
func setupTableView() {
let viewModel = HomeViewModel()
homeTableView.delegate = viewModel
homeTableView.dataSource = viewModel
homeTableView.rowHeight = 100
view.addSubview(homeTableView)
// homeTableView.separatorStyle = .none
homeTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
homeTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
homeTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
homeTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
}
CusTableViewCell.swift
class CusTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
nameLabel.text = "Something"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
HomeViewModel.swift
extension HomeViewModel: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: CusTableViewCell.identifier, for: indexPath) as? CusTableViewCell {
cell.backgroundColor = UIColor(red:0.17, green:0.73, blue:0.83, alpha:1.0)
return cell
}
return UITableViewCell()
}
}
The problem is related to weak reference to viewModel.
Put viewModel variable to the controller class as follows:
class HomeViewController {
var viewModel: HomeViewModel!
...
func setupTableView() {
self.viewModel = HomeViewModel()
...
}
}
I am making a simple tableview with a customCell. and a searchBar above. but getting a strange behavior from tableView. customCell is showing but above it defaultCell is showing as well and data is getting populated into the defaultCell though i am setting data on my customCell.
this is the output i am getting
https://i.imgur.com/xsTIsiz.png
if you look at it closely you will see my custom cell UI is showing under the default cell.
My custom cell:
https://i.imgur.com/J4UpRle.png
This is my code from viewcontroller
import UIKit
class SuraSearchController: UIViewController {
let searchController = UISearchController(searchResultsController: nil)
let reciters = [Reciter(name: "Abdul Basit Abdus Samad", downloadUrl: ""),
Reciter(name: "Abdul Rahman Al-Sudais", downloadUrl: ""),
Reciter(name: "Ali Bin Abdur Rahman Al Huthaify", downloadUrl: ""),
Reciter(name: "Mishary Rashid Alafasy", downloadUrl: ""),
Reciter(name: "Cheik Mohamed Jibril", downloadUrl: ""),
Reciter(name: "Mohamed Siddiq El-Minshawi", downloadUrl: ""),
Reciter(name: "Mahmoud Khalil Al-Hussary", downloadUrl: ""),
Reciter(name: "Ibrahim Walk (English Only)", downloadUrl: ""),
Reciter(name: "Abu Bakr Al Shatri", downloadUrl: "")]
var filteredReciters = [Reciter]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// definesPresentationContext = true
initNavBar()
initTableView()
// Do any additional setup after loading the view.
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
func initNavBar() {
// show navbar
self.navigationController?.setNavigationBarHidden(false, animated: true)
// set search bar delegates
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
// Customize Search Bar
searchController.searchBar.placeholder = "Search Friends"
let myString = "Cancel"
let myAttribute = [ NSAttributedStringKey.foregroundColor: UIColor.white ]
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = myString
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(myAttribute, for: .normal)
if #available(iOS 11.0, *) {
let scb = searchController.searchBar
scb.tintColor = UIColor.white
scb.barTintColor = UIColor.white
if let textfield = scb.value(forKey: "searchField") as? UITextField {
textfield.textColor = UIColor.blue
if let backgroundview = textfield.subviews.first {
// Background color
backgroundview.backgroundColor = UIColor.white
// Rounded corner
backgroundview.layer.cornerRadius = 10
backgroundview.clipsToBounds = true
}
}
}
// Set search bar on navbar
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
func initTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "SuraSearchCell", bundle: nil), forCellReuseIdentifier: "SuraSearchCell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredReciters = reciters.filter({( reciter : Reciter) -> Bool in
return reciter.name.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
/*
// 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.
}
*/
}
extension SuraSearchController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredReciters.count
}
return reciters.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SuraSearchCell", for: indexPath) as? SuraSearchCell {
let candy: Reciter
if isFiltering() {
candy = filteredReciters[indexPath.row]
} else {
candy = reciters[indexPath.row]
}
cell.textLabel!.text = candy.name
return cell
}
return UITableViewCell()
}
}
extension SuraSearchController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
And code of the tableview cell
import UIKit
class SuraSearchCell: UITableViewCell {
#IBOutlet weak var itemTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configureCell(item: Reciter) {
itemTitle.text = item.name
}
}
cell.textLabel is default UITableViewCell property. you just need to set value to your customCell itemTitle label.
Replace cell.textLabel!.text = candy.name with cell. itemTitle!.text = candy.name like below.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SuraSearchCell", for: indexPath) as? SuraSearchCell {
let candy: Reciter
if isFiltering() {
candy = filteredReciters[indexPath.row]
} else {
candy = reciters[indexPath.row]
}
cell. itemTitle!.text = candy.name
//OR
cell.configureCell(candy) // IF your candy is of Reciter type
return cell
}
return UITableViewCell()
}
There may be a mistake with reuse identifier name. Match the name with the used one in the code.
Let update cellForRowAtIndex as below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "SuraSearchCell", for: indexPath) as? SuraSearchCell {
let candy: Reciter
if isFiltering() {
candy = filteredReciters[indexPath.row]
} else {
candy = reciters[indexPath.row]
}
cell.configureCell(candy)
return cell
}
return UITableViewCell()
}