How could UILabel always be nil -- Unexpectedly found nil while implicitly unwrapping an Optional value - ios

As many people encountered, I tried to build tableView. I found many similar questions but it seems answers are not helping. I would be very grateful if anyone could help me. The problem I encountered:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This is a description Xcode gives me
Here's what I did:
(1) I connected Labels in the storyboard to the class it related to, which should be right as it's not hollow.
(2) I used tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath), and I tried to print cell I got, all cells aren't nil and belongs to CollegeTableViewCell, which is correct.
(3) I changed the identifier of tableViewCell to Cell which matches, and I changed it's class to CollegeTableViewCell too.
My program crashed directly when it executes following code. I only works when I make labels optional. So the problem is what did I do wrong so that labels in cell are always nil?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CollegeTableViewCell
let college = colleges[indexPath.row]
cell.collegeName.text = college.name // <-CRASH
cell.collegeGeo.text = college.city + ", " + college.state
return cell
}
Following is my CollegeTableViewCell class:
class CollegeTableViewCell: UITableViewCell {
#IBOutlet weak var collegeName: UILabel!
#IBOutlet weak var collegeGeo: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
EDIT: more codes related to this problem.
class CollegeChooseViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var colleges = [CollegeInfo]()
let searchController = UISearchController(searchResultsController: nil)
let collegeApiUrl = "https://api.collegeai.com/v1/api/autocomplete/colleges?api_key=b47484dd6e228ea2cc5e1bf6ca&query="
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(CollegeTableViewCell.self, forCellReuseIdentifier: "Cell")
getColleges(contentInSearch: "MIT")
}
func getColleges(contentInSearch: String) {
guard let url = URL(string: (collegeApiUrl + contentInSearch)) else { return }
URLSession.shared.fetchData(for: url) {(result: Result<Initial, Error>) in
switch result {
case .success(let initial):
self.colleges = initial.collegeList
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failure(let error):
print("failed fetching college list from API: \(error)")
}
}
}
}
extension URLSession {
func fetchData<T: Decodable>(for url: URL, completion: #escaping (Result<T, Error>) -> Void) {
self.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
}
if let data = data {
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(.success(object))
} catch let decoderError {
completion(.failure(decoderError))
}
}
}.resume()
}
}
extension CollegeChooseViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CollegeTableViewCell
let college = colleges[indexPath.row]
cell.collegeName.text = college.name // <-CRASH
cell.collegeGeo.text = college.city + ", " + college.state
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(colleges.count)
return colleges.count
}
}
class CollegeTableViewCell: UITableViewCell {
#IBOutlet weak var collegeName: UILabel!
#IBOutlet weak var collegeGeo: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(true, animated: true)
}
}

This is a sample of your tableview programmatically way... If I don't know where your data comes from, I used a simulation of your arrays... conform your controller to UITableViewDelegate and Datasource:
class YourController: UIViewController, UITableViewDelegate, UITableViewDataSource
Now set tableView and constraints
var name = ["Mike", "Jhon", "Carl", "Steve", "Elon", "Bill", "Bruce"] // simulation of your array
var city = ["Milano", "New Yor", "Paris", "Los Angeles", "Madrid", "Amsterdam", "Tokyo"] // simulation of your array
var state = ["Italia", "USA", "France", "USA", "Spain", "Holland", "Japan"] // simulation of your array
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkBlue
tableView.backgroundColor = .white
tableView.register(CollegeTableViewCell.self, forCellReuseIdentifier: "cellId") // register cell
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorColor = .lightGray
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// this is my extension to configure navigation bar, you can configure it as you want
configureNavigationBar(largeTitleColor: .red, backgoundColor: .black, tintColor: .red, title: "Sample", preferredLargeTitle: true)
}
After that set your tableView Delegate and DataSource:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
// Mark: - set number of rows with your array.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return name.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let name = name[indexPath.row]
let city = city[indexPath.row]
let state = state[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! CollegeTableViewCell
cell.collegeName.text = name
cell.collegeGeo.text = "\(city), \(state)"
return cell
}
This is how your cell look like:
class CollegeTableViewCell: UITableViewCell {
let collegeName: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 16, weight: .semibold)
label.backgroundColor = .clear
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let collegeGeo: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 14, weight: .semibold)
label.backgroundColor = .clear
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .ultraDark
let stackView = UIStackView(arrangedSubviews: [collegeName, collegeGeo]) // use stack view for automatic table view dimension
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 2
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
this is the result:
EDIT based on new information full code and Json decoder:
struct CollegeInfo: Decodable {
let collegeList: [MyDataResults]
}
struct MyDataResults: Decodable {
let id: String
let name: String
let city: String
let state: String
}
class tableController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var myData = [MyDataResults]() // simulation of your array
let urlString = "https://api.collegeai.com/v1/api/autocomplete/colleges?api_key=b47484dd6e228ea2cc5e1bf6ca&query="
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkBlue
tableView.backgroundColor = .white
tableView.register(CollegeTableViewCell.self, forCellReuseIdentifier: "cellId") // register cell
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorColor = .lightGray
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// this is my extension to configure navigation bar, you can configure it as you want
configureNavigationBar(largeTitleColor: .red, backgoundColor: .black, tintColor: .red, title: "Sample", preferredLargeTitle: true)
fetchJson { [weak self] (res) in
switch res {
case .success(let dataResults):
dataResults.forEach { (dataresult) in
self?.myData.removeAll()
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
self?.myData = dataresult.collegeList
self?.tableView.reloadData()
}
}
case .failure(let err):
print("Failed to fetch json", err)
}
}
}
fileprivate func fetchJson(completion: #escaping (Result<[CollegeInfo], Error >) -> ()) {
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, resp, err in
if let err = err {
completion(.failure(err))
return
}
do {
guard let data = data else { return }
let results = try JSONDecoder().decode(CollegeInfo.self, from: data)
//succesful
completion(.success([results]))
} catch let jsonErr {
completion(.failure(jsonErr))
}
}.resume()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
// Mark: - set number of rows with your array.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myResults = myData[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! CollegeTableViewCell
cell.collegeName.text = myResults.name
cell.collegeGeo.text = "\(myResults.city), \(myResults.state)"
return cell
}
The cell:
class CollegeTableViewCell: UITableViewCell {
let collegeName: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 16, weight: .semibold)
label.backgroundColor = .clear
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let collegeGeo: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 14, weight: .semibold)
label.backgroundColor = .clear
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .ultraDark
let stackView = UIStackView(arrangedSubviews: [collegeName, collegeGeo]) // use stack view for automatic table view dimension
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 2
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The result:

you're using the wrong bundle name for dequeueReusableCell.
instead of cell use CollegeTableViewCell
it should be :
let cell = tableView.dequeueReusableCell(withIdentifier: "CollegeTableViewCell", for: indexPath) as! CollegeTableViewCell

Related

Tableview cells disappear upon scrolling down an up except for the last cell from each section which turns grey color

I am creating a tableview programmatically which is not a problem,
but making a programmatic table view cell is being a headache, it is the first time I do this.
I don't know what I am doing wrong as much as I have tried, I cannot debug this.
Here is the code for the view controller
import Foundation
import UIKit
extension CountryPhoneCodeList {
class ViewController : UIViewController {
var viewModel: ViewModel!
var router: Router!
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.translatesAutoresizingMaskIntoConstraints = false
table.dataSource = self
table.delegate = self
table.register(CountryPhoneCodeListCell.self, forCellReuseIdentifier: "CountryPhoneCodeListCell")
table.layer.backgroundColor = UIColor.white.cgColor
table.estimatedRowHeight = 44
table.rowHeight = UITableView.automaticDimension
table.separatorStyle = .none
return table
}()
var updateTextFieldCode : ((String, String) -> ())? = nil
override func viewDidLoad() {
self.configureUI()
}
private func configureUI(){
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
}
extension CountryPhoneCodeList.ViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedViewModel = viewModel.cellViewModelAt(section: indexPath.section, row: indexPath.row)
updateTextFieldCode?(selectedViewModel.flag, selectedViewModel.countryPhoneCode)
router.dismiss()
}
}
extension CountryPhoneCodeList.ViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.countryCodeListCellViewModels.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let character = viewModel.getAlphabetCharacterFromIndex(index: section)
guard let countryCodesStartingWithCharacter = viewModel.countryCodeListCellViewModels[character] else {fatalError("Could not get codes starting with letter \(character)")}
return countryCodesStartingWithCharacter.count
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect.zero)
let label = UILabel(frame: CGRect(x: 14, y: -8, width: 50, height: 50))
label.text = viewModel.getAlphabetCharacterFromIndex(index: section)
label.customise(for: .bodyBold)
view.addSubview(label)
view.backgroundColor = .white
return view
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "CountryPhoneCodeListCell", for: indexPath) as? CountryPhoneCodeListCell {
cell.viewModel = viewModel.cellViewModelAt(section: indexPath.section, row: indexPath.row)
cell.selectionStyle = .none
return cell
}
return UITableViewCell()
}
}
class CountryPhoneCodeListViewController : CountryPhoneCodeList.ViewController {}
And this is the code for the tableview cell
import UIKit
struct CountryPhoneCodeListCellViewModel {
var countryName = ""
var countryPhoneCode = ""
var flag = ""
}
class CountryPhoneCodeListCell: UITableViewCell {
private var countryNameLabel: UILabel = .buildBodyLabel()
private var countryPhoneCodeLabel: UILabel = .buildBodyLabel()
var viewModel: CountryPhoneCodeListCellViewModel? {
didSet {
guard let viewModel = viewModel else {fatalError("Cannot unwrap viewModel")}
countryNameLabel.text = viewModel.countryName
countryNameLabel.customise(for: .body)
countryPhoneCodeLabel.text = viewModel.countryPhoneCode
countryPhoneCodeLabel.customise(for: .body)
}
}
override func prepareForReuse() {
countryNameLabel.text = nil
countryPhoneCodeLabel.text = nil
}
override func awakeFromNib() {
super.awakeFromNib()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(countryNameLabel)
contentView.addSubview(countryPhoneCodeLabel)
NSLayoutConstraint.activate([
countryNameLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: .constant16),
countryNameLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: .constant16),
countryNameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .constant4),
countryPhoneCodeLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .constant16),
countryPhoneCodeLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: .constant16),
countryPhoneCodeLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .constant16)
])
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}

Creating a custom view when a tableview cell is selected

How would I create a new view that displays unique information for the selection of a specific tableview cell? For example, if row 1 is selected then the new image will have unique information to that cell which would be different from the view if row 2 was selected.
Here is the code for my tableview.
import UIKit
struct Data {
var sectionTitle = String()
var rowTitles = [String]()
}
class ViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var dataArray = [Data(sectionTitle: "section 1", rowTitles: ["row 1", "row 2", "row 3"]),
Data(sectionTitle: "section 2", rowTitles: ["row 1"]),
Data(sectionTitle: "section 3", rowTitles: ["row 1", "row 2"])
]
var searchArray = [Data]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchArray[section].rowTitles.count
} else {
return dataArray[section].rowTitles.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell")
cell?.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell?.textLabel?.numberOfLines = 3
if searching {
cell?.textLabel?.text = self.searchArray[indexPath.section].rowTitles[indexPath.row]
} else {
cell?.textLabel?.text = self.dataArray[indexPath.section].rowTitles[indexPath.row]
}
return cell!
}
func numberOfSections(in tableView: UITableView) -> Int {
if searching {
return searchArray.count
} else {
return dataArray.count
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return dataArray[section].sectionTitle
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let index = indexPath.row
let data = dataArray[index]
let destVC = SecondViewController(data: data)
navigationController?.pushViewController(destVC, animated: true)
}
}
extension ViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
view.endEditing(true)
tableView.reloadData()
} else {
searching = true
searchArray = dataArray.filter({$0.sectionTitle.lowercased().contains(searchBar.text!.lowercased()) || $0.rowTitles.map{$0.lowercased()}.contains(searchBar.text!.lowercased())})
tableView.reloadData()
}
}
}
Here is my second view controller which shows my generic stack view that I would like to change to display different images and labels depending on the cell that is selected in the tableview.
import UIKit
class SecondViewController: UIViewController {
var data: Data!
var dataView: DataView { return self.view as! DataView}
init(data: Data) {
self.data = data
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func loadView() {
self.view = DataView(frame: UIScreen.main.bounds)
}
}
class DataView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
setupViews()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
self.addSubview(stack)
stack.distribution = .fillProportionally
stack.spacing = 10
}
func setupConstraints() {
stack.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
stack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
stack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
stack.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
dataImage.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
dataImage.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 60).isActive = true
dataImage.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: -60).isActive = true
}
let dataNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textAlignment = .center
label.numberOfLines = 2
label.text = "Organization"
return label
} ()
let dataDescriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
label.textAlignment = .center
label.numberOfLines = 8
label.text = "Description"
return label
} ()
let dataImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "")?.withRenderingMode(.alwaysOriginal)
imageView.backgroundColor = .gray
imageView.layer.cornerRadius = 8
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 260).isActive = true
return imageView
} ()
let visitButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Visit", for: .normal)
return button
} ()
lazy var stack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [dataImage, dataNameLabel, dataDescriptionLabel, visitButton])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
return stack
} ()
}
The objective is to have a user select a tableview cell, and for the stack view to display specific information that corresponds to that cell. How would I create this?
I'm not sure if you're asking how to pass information to a new view controller when an item in your TableViewController has been selected.
But I'm interpreting your question, as you know how to pass information, but you want to pass different information, maybe an image to your second view controller, based on which TableViewCell was selected.
The way that I would do this, is to have an array with images in your SecondViewController. When an item is selected in your TableViewController, also pass the indexPath.row to the SecondViewController. From there, you can load the image from your imagesArray at the indexPath.row that you have passed to it. You can do this by having an integer value in your SecondViewController, that is populated right before you segue.

Problem in fetching API data and putting the result in UITableView

I have UICollectionView in UITableViewCell (Autolayout programmatically) and I'm having a problem with fetching API data and putting the result in a UITableView, the API should be called when the UICollectionViewCell clicked. But, there is no result from the API.
I tried to debug but I didn't get the problem.
HomeView:
protocol HomeViewDidSelectActionDelegate: class {
func homeView(_ view: HomeView, didSelectCategoryWithTitle title: String)
}
class HomeView: UIView {
var recipes: Recipes?
var recipesDetails = [Recipe]()
let indicator = ActivityIndicator()
weak var homeViewDidSelectActionDelegate: HomeViewDidSelectActionDelegate?
override init( frame: CGRect) {
super.init(frame: frame)
layoutUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var foodTableView: UITableView = {
let foodTableView = UITableView()
foodTableView.translatesAutoresizingMaskIntoConstraints = false
foodTableView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
foodTableView.delegate = self
foodTableView.dataSource = self
foodTableView.register(CategoriesTableViewCellCollectionViewCell.self, forCellReuseIdentifier: "CategoriesTableViewCellCollectionViewCell")
foodTableView.register(PopularRecipesTableViewCellCollectionViewCell.self, forCellReuseIdentifier: "PopularRecipesTableViewCellCollectionViewCell")
foodTableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "HomeTableViewCell")
foodTableView.rowHeight = UITableView.automaticDimension
foodTableView.estimatedRowHeight = 100
foodTableView.showsVerticalScrollIndicator = false
foodTableView.separatorStyle = .none
return foodTableView
}()
func setupFoodTableView() {
NSLayoutConstraint.activate([
foodTableView.topAnchor.constraint(equalTo: topAnchor),
foodTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
foodTableView.leadingAnchor.constraint(equalTo: leadingAnchor),
foodTableView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func addSubview() {
addSubview(foodTableView)
}
func layoutUI() {
indicator.setupIndicatorView(self, containerColor: .customDarkGray(), indicatorColor: .white)
addSubview()
setupFoodTableView()
DispatchQueue.main.async {
self.fetchData()
}
}
func fetchData() {
AF.request("https://apilink.com").responseJSON { (response) in
if let error = response.error {
print(error)
}
do {
if let data = response.data {
self.recipes = try JSONDecoder().decode(Recipes.self, from: data)
self.recipesDetails = self.recipes?.recipes ?? []
DispatchQueue.main.async {
self.foodTableView.reloadData()
}
}
} catch {
print(error)
}
self.indicator.hideIndicatorView(self)
}
}
}
extension HomeView: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else {
return recipesDetails.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoriesTableViewCellCollectionViewCell", for: indexPath) as! CategoriesTableViewCellCollectionViewCell
cell.recipesDidselectActionDelegate = self
cell.collectionView.reloadData()
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "PopularRecipesTableViewCellCollectionViewCell", for: indexPath) as! PopularRecipesTableViewCellCollectionViewCell
cell.collectionView.reloadData()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
let url = URL(string: recipesDetails[indexPath.row].image ?? "Error")
cell.foodImage.kf.setImage(with: url)
cell.foodTitle.text = recipesDetails[indexPath.row].title
if let readyInMin = recipesDetails[indexPath.row].readyInMinutes {
cell.cookingTimeInfoLabel.text = "\(readyInMin) Minutes"
}
if let pricePerServing = recipesDetails[indexPath.row].pricePerServing {
cell.priceInfoLabel.text = "$\(Int(pricePerServing))"
}
if let serving = recipesDetails[indexPath.row].servings {
cell.servesInfoLabel.text = "\(serving)"
}
return cell
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 2 {
return "Random recipes"
} else {
return ""
}
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
(view as! UITableViewHeaderFooterView).contentView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
(view as! UITableViewHeaderFooterView).textLabel?.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
(view as! UITableViewHeaderFooterView).textLabel?.textColor = .customDarkGray()
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30.0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 130
} else if indexPath.section == 1 {
return 180
} else {
return UITableView.automaticDimension
}
}
}
extension HomeView: RecipesDidselectActionDelegate {
func categoriesTableViewCell(_ cell: UICollectionView, didSelectTitle title: String) {
homeViewDidSelectActionDelegate?.homeView(self, didSelectCategoryWithTitle: title)
}
}
HomeViewController:
class HomeViewController: UIViewController {
let searchController = UISearchController(searchResultsController: nil)
let leftMenuNavigationController = SideMenuNavigationController(rootViewController: SideMenuTableViewController())
lazy var mainView: HomeView = {
let view = HomeView(frame: self.view.frame)
view.homeViewDidSelectActionDelegate = self
return view
}()
override func loadView() {
super.loadView()
view = mainView
}
override func viewDidLoad() {
super.viewDidLoad()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isNavigationBarHidden = false
setNeedsStatusBarAppearanceUpdate()
setupNavigationWithLargeTitle()
setupLeftSideMenu()
setupNavigation()
}
func setupLeftSideMenu() {
SideMenuManager.default.leftMenuNavigationController = leftMenuNavigationController
leftMenuNavigationController.leftSide = true
leftMenuNavigationController.statusBarEndAlpha = 0
leftMenuNavigationController.presentationStyle = .viewSlideOut
leftMenuNavigationController.allowPushOfSameClassTwice = false
leftMenuNavigationController.menuWidth = view.frame.width * (3/4)
leftMenuNavigationController.navigationBar.isHidden = true
}
}
extension HomeViewController: UISearchControllerDelegate, UISearchBarDelegate {
func setupNavigationWithLargeTitle() {
navigationController?.navigationBar.prefersLargeTitles = true
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.searchBar.searchTextField.backgroundColor = .white
searchController.searchBar.searchTextField.textColor = .customDarkGray()
searchController.searchBar.searchTextField.font = UIFont(name: "AvenirNext-Regular", size: 14)
searchController.searchBar.tintColor = UIColor.CustomGreen()
self.navigationItem.searchController = searchController
self.title = "Home"
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.CustomGreen()]
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.CustomGreen(), .font: UIFont(name: "AvenirNext-Heavy", size: 36)!]
navigationController?.navigationBar.tintColor = .white
navigationController?.navigationBar.standardAppearance = navBarAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "heart.fill"), style: .plain, target: self, action: #selector(saveButtonTapped))
navigationItem.rightBarButtonItem?.tintColor = UIColor.CustomGreen()
navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named: "menu"), style: .plain, target: self, action: #selector(menuButtonTapped))
navigationItem.leftBarButtonItem?.tintColor = UIColor.CustomGreen()
}
#objc func saveButtonTapped() {
print("OK")
}
#objc func menuButtonTapped() {
self.present(leftMenuNavigationController, animated: true, completion: nil)
}
}
extension HomeViewController: HomeViewDidSelectActionDelegate {
func homeView(_ view: HomeView, didSelectCategoryWithTitle title: String) {
let vc = RecipesTableViewDetails()
vc.categoryTitle = title
self.show(vc, sender: nil)
}
}
CollectionView:
protocol RecipesDidselectActionDelegate: class {
func categoriesTableViewCell(_ cell: UICollectionView, didSelectTitle title: String)
}
class CategoriesTableViewCellCollectionViewCell: UITableViewCell, UICollectionViewDelegateFlowLayout {
weak var recipesDidselectActionDelegate: RecipesDidselectActionDelegate?
let categories: [String] = [
"Main course",
"Beef",
"Chicken",
"Seafood",
"Vegetarian",
"Breakfast",
"Side dish",
"Drink",
"Sauce",
"Soup",
"Snacks",
"Dessert"
]
let categoriesTitle: [String] = [
"maincourse",
"beef",
"chicken",
"seafood",
"vegetarian",
"breakfast",
"sidedish",
"drink",
"sauce",
"soup",
"snacks",
"dessert"
]
let categoriesImages: [UIImage] = [
UIImage(named: "maincourse")!,
UIImage(named: "beef")!,
UIImage(named: "chicken")!,
UIImage(named: "seafood")!,
UIImage(named: "vegetarian")!,
UIImage(named: "breakfast")!,
UIImage(named: "sidedish")!,
UIImage(named: "drink")!,
UIImage(named: "sauce")!,
UIImage(named: "soup")!,
UIImage(named: "snacks")!,
UIImage(named: "dessert")!
]
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
selectionStyle = .none
self.backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var categoriesNameLabel: UILabel = {
let categoriesNameLabel = UILabel()
categoriesNameLabel.text = "Categories"
categoriesNameLabel.textColor = .customDarkGray()
categoriesNameLabel.textAlignment = .left
categoriesNameLabel.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
categoriesNameLabel.translatesAutoresizingMaskIntoConstraints = false
return categoriesNameLabel
}()
lazy var seeAllCategoriesButton: UIButton = {
let seeAllCategoriesButton = UIButton()
seeAllCategoriesButton.setTitle("See all", for: .normal)
seeAllCategoriesButton.setTitleColor(.CustomGreen(), for: .normal)
seeAllCategoriesButton.titleLabel?.font = UIFont(name: "AvenirNext-Regular", size: 14)
seeAllCategoriesButton.translatesAutoresizingMaskIntoConstraints = false
seeAllCategoriesButton.addTarget(self, action: #selector(test), for: .touchUpInside)
return seeAllCategoriesButton
}()
#objc func test() {
print("Test worked")
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CategoriesCollectionViewCell.self, forCellWithReuseIdentifier: "CategoriesCollectionViewCell")
return collectionView
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0)
])
}
func setupCategoriesNameLabelConstraints() {
NSLayoutConstraint.activate([
categoriesNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
categoriesNameLabel.centerYAnchor.constraint(equalTo: seeAllCategoriesButton.centerYAnchor)
])
}
func setupSeeAllCategoriesButtonConstraints() {
NSLayoutConstraint.activate([
seeAllCategoriesButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
seeAllCategoriesButton.topAnchor.constraint(equalTo: containerView.topAnchor)
])
}
func setupCollectionViewConstraints() {
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: seeAllCategoriesButton.bottomAnchor, constant: 0),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
])
}
func addSubviews() {
addSubview(containerView)
containerView.addSubview(categoriesNameLabel)
containerView.addSubview(seeAllCategoriesButton)
containerView.addSubview(collectionView)
}
func layoutUI() {
addSubviews()
setupCollectionViewConstraints()
setupContainerViewConstraints()
setupCategoriesNameLabelConstraints()
setupSeeAllCategoriesButtonConstraints()
}
}
extension CategoriesTableViewCellCollectionViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categories.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoriesCollectionViewCell", for: indexPath) as! CategoriesCollectionViewCell
cell.categoriesImage.image = categoriesImages[indexPath.row]
cell.categoryName.text = categories[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
recipesDidselectActionDelegate?.categoriesTableViewCell(collectionView, didSelectTitle: categoriesTitle[indexPath.row])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let w: CGFloat = self.frame.width * 0.4
let h: CGFloat = collectionView.frame.size.height - 6.0
return CGSize(width: w, height: h)
}
}
RecipesTableViewDetailsView:
class RecipesTableViewDetailsView: UIView {
let indicator = ActivityIndicator()
let recipesTableVC = RecipesTableViewDetails()
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var foodTableView: UITableView = {
let foodTableView = UITableView()
foodTableView.translatesAutoresizingMaskIntoConstraints = false
foodTableView.backgroundColor = #colorLiteral(red: 0.9568627451, green: 0.9568627451, blue: 0.9568627451, alpha: 1)
foodTableView.delegate = self
foodTableView.dataSource = self
foodTableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "HomeTableViewCell")
foodTableView.rowHeight = UITableView.automaticDimension
foodTableView.estimatedRowHeight = 100
foodTableView.showsVerticalScrollIndicator = false
foodTableView.separatorStyle = .none
return foodTableView
}()
func setupFoodTableView() {
NSLayoutConstraint.activate([
foodTableView.topAnchor.constraint(equalTo: topAnchor),
foodTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
foodTableView.leadingAnchor.constraint(equalTo: leadingAnchor),
foodTableView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func addSubview() {
addSubview(foodTableView)
}
func layoutUI() {
addSubview()
setupFoodTableView()
}
}
extension RecipesTableViewDetailsView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recipesTableVC.recipesDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
let newURL = URL(string: recipesTableVC.recipesDetails[indexPath.row].image ?? "Error")
cell.foodImage.kf.setImage(with: newURL)
cell.foodTitle.text = recipesTableVC.recipesDetails[indexPath.row].title
if let readyInMin = recipesTableVC.recipesDetails[indexPath.row].readyInMinutes {
cell.cookingTimeInfoLabel.text = "\(readyInMin) Minutes"
}
if let pricePerServing = recipesTableVC.recipesDetails[indexPath.row].pricePerServing {
cell.priceInfoLabel.text = "$\(Int(pricePerServing))"
}
if let serving = recipesTableVC.recipesDetails[indexPath.row].servings {
cell.servesInfoLabel.text = "\(serving)"
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
RecipesTableViewDetailsViewController:
class RecipesTableViewDetails: UIViewController {
var categoryTitle: String?
var recipes: Recipes?
var recipesDetails = [Recipe]()
let indicator = ActivityIndicator()
lazy var mainView: RecipesTableViewDetailsView = {
let view = RecipesTableViewDetailsView(frame: self.view.frame)
view.backgroundColor = .white
return view
}()
override func loadView() {
super.loadView()
view = mainView
}
override func viewDidLoad() {
super.viewDidLoad()
if let categoryTitle = categoryTitle {
fetchData(categoryTitle)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isNavigationBarHidden = false
}
func fetchData(_ category: String) {
indicator.setupIndicatorView(view, containerColor: .customDarkGray(), indicatorColor: .white)
AF.request("https://api.link.com/recipes/random?number=25&tags=\(category)").responseJSON { (response) in
if let error = response.error {
print(error)
}
do {
if let data = response.data {
self.recipes = try JSONDecoder().decode(Recipes.self, from: data)
self.recipesDetails = self.recipes?.recipes ?? []
DispatchQueue.main.async {
self.mainView.foodTableView.reloadData()
}
}
} catch {
print(error)
}
self.indicator.hideIndicatorView(self.view)
}
}
}
Recipes:
// MARK: - Recipes
struct Recipes: Codable {
let recipes: [Recipe]
}
// MARK: - Recipe
struct Recipe: Codable {
let title: String?
let image: String?
let pricePerServing: Double?
let readyInMinutes, servings: Int?
}
In your RecipesTableViewDetailsView class, you are creating a new instance of RecipesTableViewDetails:
let recipesTableVC = RecipesTableViewDetails()
However, in your RecipesTableViewDetails, you were setting the view to another new instance of RecipesTableViewDetails:
lazy var mainView: RecipesTableViewDetailsView = {
let view = RecipesTableViewDetailsView(frame: self.view.frame)
view.backgroundColor = .white
return view
}()
So one instance is fetching the data, but your table is trying to use data from the other instance.
You can fix it by changing:
let recipesTableVC = RecipesTableViewDetails()
to:
var recipesTableVC: RecipesTableViewDetails!
and then in RecipesTableViewDetails assign that var:
override func loadView() {
super.loadView()
// assign here
mainView.recipesTableVC = self
view = mainView
}

UITableViewCell displaying custom labels and images duplicate

Image still shows cells top and bottom
I'm using indexPath.row to determine the type of image and label to display but unfortunately it displays at the top and also very bottom of the table too thereby duplicating cells below is my code:
import Foundation
import UIKit
//custom cell
class menuTableCells: UITableViewCell{
var menuName: String?
var menuIcon: UIImage?
var lblMenuName: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var menuImage: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(lblMenuName)
self.addSubview(menuImage)
//constraints for icons
menuImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
menuImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
menuImage.heightAnchor.constraint(equalToConstant: 35).isActive = true
menuImage.widthAnchor.constraint(equalToConstant: 35).isActive = true
//constraints for label menu
lblMenuName.leadingAnchor.constraint(equalTo: menuImage.trailingAnchor, constant: 20).isActive = true
lblMenuName.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
lblMenuName.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
if let menuName = menuName{
lblMenuName.text = menuName
}
if let menuIcon = menuIcon {
menuImage.image = menuIcon
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//struct to hold data
struct MenuCellData {
let menuName: String?
let menuIcon: UIImage?
}
class SidebarView: UIView, UITableViewDelegate, UITableViewDataSource{
var titleArr = [String]()
var iconsArr = [String]()
var populateData = [MenuCellData]()
weak var delegate: SidebarViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.black.withAlphaComponent(0.80)
self.clipsToBounds = true
titleArr = ["Home", "The Prophet", "Devotions", "Church Events", "Ahofadiekrom", "Branches", "Gallery", "Videos", "Live Streaming", "Live Radio", "Elijah TV", "Contact Us"]
iconsArr = ["home-1", "devotion", "devotion", "events", "worship", "branches", "gallery", "videos", "live", "radio", "logo1", "contact"]
setUpViews()
myTableView.delegate = self
myTableView.dataSource = self
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
myTableView.tableFooterView = UIView()
myTableView.separatorStyle = UITableViewCellSeparatorStyle.none
myTableView.allowsSelection = true
myTableView.bounces = false
myTableView.showsVerticalScrollIndicator = false
myTableView.backgroundColor = UIColor.clear
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return populateData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! menuTableCells
cell.backgroundColor = UIColor.clear
cell.selectionStyle = .none
let menus = MenuCellData(menuName: titleArr[indexPath.row], menuIcon: UIImage(named: iconsArr[indexPath.row]))
populateData.append(menus)
myTableView.reloadData()
cell.menuName = populateData[indexPath.row].menuName
cell.menuIcon = populateData[indexPath.row].menuIcon
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.delegate?.sidebarDidSelectRow(row: Row(row: indexPath.row))
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
let appLogo: UIImageView = {
let view = UIImageView(image: #imageLiteral(resourceName: "logo"))
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let myTableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
func setUpViews(){
self.addSubview(appLogo)
appLogo.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
appLogo.topAnchor.constraint(equalTo: topAnchor).isActive = true
appLogo.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
appLogo.heightAnchor.constraint(equalToConstant: 200).isActive = true
self.addSubview(myTableView)
myTableView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
myTableView.topAnchor.constraint(equalTo: appLogo.bottomAnchor).isActive = true
myTableView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
myTableView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Instead of adding UIImage and UILable at run time try using custom cell. For more details about how to use custom and default cell please follow this tutorial Here
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
let headline = headlines[indexPath.row]
cell.textLabel?.text = headline.title
cell.imageView?.image = UIImage(named: headline.image)
return cell
}
//My custom class table cells
import Foundation
import UIKit
class menuTableCells: UITableViewCell{
var menuName: String?
var menuIcon: UIImage?
var lblMenuName: UILabel = {
let view = UILabel()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var menuImage: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(lblMenuName)
self.addSubview(menuImage)
//constraints for icons
menuImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
menuImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
menuImage.heightAnchor.constraint(equalToConstant: 35).isActive = true
menuImage.widthAnchor.constraint(equalToConstant: 35).isActive = true
//constraints for label menu
lblMenuName.leadingAnchor.constraint(equalTo: self.menuImage.trailingAnchor, constant: 20).isActive = true
lblMenuName.topAnchor.constraint(equalTo: self.topAnchor, constant: 25).isActive = true
// lblMenuName.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10).isActive = true
lblMenuName.font = UIFont.systemFont(ofSize: 18)
lblMenuName.textColor = .white
}
override func layoutSubviews() {
super.layoutSubviews()
if let menuName = menuName{
lblMenuName.text = menuName
}
if let menuIcon = menuIcon {
menuImage.image = menuIcon
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//Struct variables
struct MenuCellData {
let menuName: String?
let menuIcon: UIImage?
}
var populateData = [MenuCellData]()
//populated my array with menu items
populateData = [MenuCellData.init(menuName: "Home", menuIcon: #imageLiteral(resourceName: "home-1")), MenuCellData.init(menuName: "The Prophet", menuIcon: #imageLiteral(resourceName: "devotion")), MenuCellData.init(menuName: "Devotions", menuIcon: #imageLiteral(resourceName: "devotion")), MenuCellData.init(menuName: "Church Events", menuIcon: #imageLiteral(resourceName: "events")), MenuCellData.init(menuName: "Ahofadiekrom", menuIcon: #imageLiteral(resourceName: "worship")), MenuCellData.init(menuName: "Branches", menuIcon: #imageLiteral(resourceName: "branches")), MenuCellData.init(menuName: "Gallery", menuIcon: #imageLiteral(resourceName: "gallery")), MenuCellData.init(menuName: "Videos", menuIcon: #imageLiteral(resourceName: "videos")), MenuCellData.init(menuName: "Live Streaming", menuIcon: #imageLiteral(resourceName: "live")), MenuCellData.init(menuName: "Live Radio", menuIcon: #imageLiteral(resourceName: "radio")), MenuCellData.init(menuName: "Elijah TV", menuIcon: #imageLiteral(resourceName: "logo1")), MenuCellData.init(menuName: "Contact Us", menuIcon: #imageLiteral(resourceName: "contact"))]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return populateData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! menuTableCells
cell.backgroundColor = UIColor.clear
cell.selectionStyle = .none
cell.menuName = populateData[indexPath.row].menuName
cell.menuIcon = populateData[indexPath.row].menuIcon
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.delegate?.sidebarDidSelectRow(row: Row(row: indexPath.row))
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
Viola!!!

How to put a floating action button in a tableView in swift in iOS?

I am trying to use an floating action button in iOS to impose on a table view so that I can add items in the tableview with that . please help me with the code.
Here is the complete code for it. It has been done without using storyboard.
TableView:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
let nameArray = ["India","Usa","UK"]
let tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
let btnFloating : UIButton = {
let floating = UIButton()
floating.translatesAutoresizingMaskIntoConstraints = false
floating .backgroundColor = .cyan
floating.setTitle("ADD", for: .normal)
return floating
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.addSubview(btnFloating)
tableView.dataSource = self
setuoConstrain()
//Set the action of add button
btnFloating.addTarget(self, action: #selector(btnAddTapp(sender:)), for: .touchUpInside)
}
func setuoConstrain(){
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
//Constrain For Button :
btnFloating.heightAnchor.constraint(equalToConstant: 64).isActive = true
btnFloating.widthAnchor.constraint(equalToConstant: 64).isActive = true
btnFloating.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24).isActive = true
btnFloating.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -36).isActive = true
}
//This function is for add button . What action you want , can put inside this function
#objc func btnAddTapp(sender: UIButton){
print("add button tapp")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let nameCell = NameTableCell(style: .default, reuseIdentifier: "NameTableCell")
nameCell.lblName.text = nameArray[indexPath.row]
return nameCell
}
}
TableViewCell:
import UIKit
class NameTableCell: UITableViewCell {
let lblName: UILabel = {
let name = UILabel()
name.translatesAutoresizingMaskIntoConstraints = false
return name
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(lblName)
constrain()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func constrain(){
lblName.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
}
}
func setupFloatingActionButton() {
Floaty.global.button.buttonImage = UIImage(named: "icon-social")
Floaty.global.button.buttonColor = UIColor.white
let facebookItem = FloatyItem()
facebookItem.icon = UIImage(named: "icon-facebook")
facebookItem.title = "Facebook"
Floaty.global.button.addItem(item: facebookItem)
let gmailItem = FloatyItem()
Floaty.global.button.addItem("Gmail", icon: UIImage(named: "icon-gmail"), handler: {_
in
print("Gmail Button tapp")
})
let twitterItem = FloatyItem()
Floaty.global.button.addItem("Twitter", icon: UIImage(named: "icon-twitter"), handler: {_ in
print("twitter Button tapp")
})
//Floaty.global.button.animationSpeed = 0.50
Floaty.global.button.openAnimationType = .fade
//Floaty.global.button.rotationDegrees = 90.00
Floaty.global.show()
}

Resources