I am trying to use RxSwift/RxDataSource with TableView but I am not able to assign configureCell with an existing function. Code below:
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class BaseTableViewController: UIViewController {
// datasources
let dataSource = RxTableViewSectionedReloadDataSource<TableSectionModel>()
let sections: Variable<[TableSectionModel]> = Variable<[TableSectionModel]>([])
let disposeBag: DisposeBag = DisposeBag()
// components
let tableView: UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setDataSource()
}
func setupUI() {
attachViews()
}
func setDataSource() {
tableView.delegate = nil
tableView.dataSource = nil
sections.asObservable()
.bindTo(tableView.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag)
dataSource.configureCell = cell
sectionHeader()
}
func cell(ds: TableViewSectionedDataSource<TableSectionModel>, tableView: UITableView, indexPath: IndexPath, item: TableSectionModel.Item) -> UITableViewCell! {
return UITableViewCell()
}
func sectionHeader() {
}
}
Xcode throws the following error:
/Users/.../BaseTableViewController.swift:39:36: Cannot assign value of type '(TableViewSectionedDataSource, UITableView, IndexPath, TableSectionModel.Item) -> UITableViewCell!' to type '(TableViewSectionedDataSource, UITableView, IndexPath, TableSectionModel.Item) -> UITableViewCell!'
the error is thrown at line
dataSource.configureCell = cell
Do you have any idea?
Thanks
You just need to remove ! from return type UITableViewCell! of your cell method.
func cell(ds: TableViewSectionedDataSource<TableSectionModel>, tableView: UITableView, indexPath: IndexPath, item: TableSectionModel.Item) -> UITableViewCell {
return UITableViewCell()
}
In this way your function became type compatible with type expected by RxDataSource's configureCell property:
public typealias CellFactory = (TableViewSectionedDataSource<S>, UITableView, IndexPath, I) -> UITableViewCell
I, personally, prefer the following syntax for initialising configureCell:
dataSource.configureCell = { (_, tableView, indexPath, item) in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Your configuration code goes here
return cell
}
Related
I want to learn Combine framework for Swift and I have found a tutorial video:
https://www.youtube.com/watch?v=hbY1KTI0g70
Unfortunately, I get:
No exact matches in call to initializer
error on the line which defines tableView and some other errors when I try to call the tableView, but I hope they will resolve after I fix the issue with initialising this element.
The code:
import UIKit
import Combine
class MyCustomTableCell: UITableViewCell{ }
class ViewController: UIViewController, UITableViewDataSource {
private let tableView = UITableView {
let table = UITableView()
table.register(MyCustomTableCell.self,
forceCellReuseIdentifier: "cell")
return table
}()
(...)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.dataSource = self
tableView.frame = view.bounds
(...)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCustomTableCell else {
fatalError()
}
cellTextLabel?.text = models(indexPath.row)
return cell
}
The whole code is long as hell. That is why I copied only the crucial parts of it (where the tableView occurs).
You can see the full code in the video:
https://www.youtube.com/watch?v=hbY1KTI0g70
It's either
private let tableView = UITableView( ... )
or
private let tableView : UITableView = { ... }()
my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}
I am trying to put 2 diff data from same json on same tableview but I am not able to do! In every case, I can put only one. I want to display .name and .email at the same time
Thanks
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var userInfo = [UserData]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
JsonDownload
{
self.tableView.reloadData()
}
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userInfo.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = userInfo[indexPath.row].name
return cell
}
func JsonDownload(completed: #escaping () -> ()) {
let source = URL(string: "https://jsonplaceholder.typicode.com/users")
URLSession.shared.dataTask(with: source!) { (data, response, error) in
if let data = data {
do
{
self.userInfo = try JSONDecoder().decode([UserData].self, from: data)
DispatchQueue.main.async
{
completed()
}
}
catch
{
print("Json Error")
}
}
}.resume()
}
}
here is how you can display .name and .email at the same time.. but instead of creating new cell each time .. use deque
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell"){
cell.textLabel?.text = userInfo[indexPath.row].name
cell.detailTextLabel?.text = userInfo[indexPath.row].email
return cell
}
return UITableViewCell()
}
If you want to use the default cell provided by Apple.
You can you textLabel and detailTextLabel. Another better way is creating your custom tableviewcell.
I want to update a tableview with server data.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var aedList = [AED]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.aedList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
Server().getJsonInformationFromServer(url: "aeds") { (response) -> Void in
self.aedList = JSONHelper().parseAEDInformation(json: response["data"])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.textLabel?.text = self.aedList[indexPath.row].street
self.tableView.reloadData()
})
}
return cell
}
The data is being downloaded correctly, but the tableview is empty.
I guess this is due to the async behaviour of my download and the numberOfRowsmethod tries to count an still empty array.
How do I correct this to display the data correctly? Do I need to use a dispatch_async there as well?
cellForRowAtIndexPath as the method name implies, its called by the table view for each cell so you should not fetch your entire data there, its for populating the cells individually. Here are a couple of fixes in your code:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var aedList = [AED]()
//In addition to viewDidLoad, you can call this method whenever you wanna update your table
func fetchData() {
Server().getJsonInformationFromServer(url: "aeds") { (response) -> Void in
self.aedList = JSONHelper().parseAEDInformation(json: response["data"])
self.aedList = JSONHelper().parseAEDInformation(json: response["data"])
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.tableView.reloadData()
}
})
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return aedList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
cell.textLabel?.text = self.aedList[indexPath.row].street
return cell
}
Loading data from tableView:cellForRowAtIndexPath: won't never work. This method is called for every cell, that means:
If there are no cells (data is not loaded yet), the method won't never be called and your data won't be loaded (which is what is happening now).
If the data were loaded, you would trigger a new loading for every displayed cell, that is, if you have 5 items, you trigger 5 loadings. And that's recursive because reloadData will redisplay all cells.
Solution:
Put your loading code to a better place, e.g. viewDidAppear method of your controller.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
Server().getJsonInformationFromServer(url: "aeds") { (response) -> Void in
self.aedList = JSONHelper().parseAEDInformation(json: response["data"])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
cell.textLabel?.text = self.aedList[indexPath.row].street
return cell
}
thanks for all the help so far. I need, when a cell in UITableView is clicked, to re-populate the view with an array read from another class - just can find a way to refresh the view. Code as follows:
Thanks in advance - the help so far has been great for this newbie.
import UIKit
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
let textCellIdentifier = "TextCell"
var catRet = XnYCategories.mainCats("main")
//var catRet = XnYCategories.subCats("Sport")
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// MARK: UITextFieldDelegate Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return catRet.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as! UITableViewCell
let row = indexPath.row
cell.textLabel?.text = catRet[row]
return cell
}
// MARK: UITableViewDelegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
//let currentCell = tableView.cellForRowAtIndexPath(indexPath)
//var selectedText = currentCell!.textLabel?.text
//println(selectedText)
let catRet2 = XnYCategories.mainCats(catRet[row])
println(catRet2)
println(catRet[row])
//catRet = catRet2
}
}
Call reloadData() on your tableView as follow
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
//let currentCell = tableView.cellForRowAtIndexPath(indexPath)
//var selectedText = currentCell!.textLabel?.text
//println(selectedText)
let catRet2 = XnYCategories.mainCats(catRet[row])
println(catRet2)
println(catRet[row])
// *** New code added ***
// remove the comment
catRet = catRet2
// call reloadData()
tableView.reloadData()
// *** New code added ***
}
just do this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
catRet = XnYCategories.mainCats(catRet[row])
tableView.reloadData()
}