I'm using the tableView using DiffableDataSource and only issue which I've faced in header.I'm using viewForheaderSections method for calling header header view appear but on bottom position not top of the list please see the code thanks.
extension SinlgeTableViewcontroller:UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: Header.self)) as? Header else {return UIView()}
cell.lblHeader.text = "Top Mens"
return cell
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 40
}
func createDataSource(){
dataSource = UITableViewDiffableDataSource<ProductsSections,AnyHashable>(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in
switch self.sectionsData[indexPath.section]{
case .first:
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProductCell.self)) as? ProductCell else {return UITableViewCell()}
cell.products = self.products[indexPath.row]
return cell
case .second:
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MensCell.self)) as? MensCell else {return UITableViewCell()}
cell.mens = self.mens[indexPath.row]
return cell
}
})
}
func createSnapshot(){
var snapshot = NSDiffableDataSourceSnapshot<ProductsSections,AnyHashable>()
sectionsData.forEach{ $0
snapshot.appendSections([$0])
}
// snapshot.appendSections([.first,.second])
snapshot.appendItems(products, toSection: .first)
snapshot.appendItems(mens, toSection: .second)
dataSource?.apply(snapshot, animatingDifferences: true, completion: nil)
}
According to your code you are not using viewForHeaderInSection. Instead of that you are using viewForFooterInSection. So instead of a header, a footer will appear in the bottom.
If you only need a header change all the methods related to the footer to the header.
Related
Here is how i use RxSwift in my code:
My news variable in ViewModel:
var news = PublishSubject<[Article]>()
Extensions for using Rxswift with tableview.
extension HomeViewController {
func designTableView() {
newsTable.separatorStyle = .none
newsTable.showsVerticalScrollIndicator = false
}
func createCellView() {
homeViewModel.news.bind(to: newsTable.rx.items(cellIdentifier: "cell", cellType: NewsCell.self)) { _, news, cell in
cell.newsTitle.text = news.title
cell.newsImage?.sd_setImage(with: URL(string: news.urlToImage ?? ""), placeholderImage: UIImage(systemName: "photo"))
}.disposed(by: disposeBag)
}
func whenNewsSelected() {
newsTable.rx.modelSelected(Article.self).bind { article in
let vc = self.storyboard?.instantiateViewController(withIdentifier: "NewsDetail") as? NewsDetails
vc?.article = article
self.navigationController?.pushViewController(vc!, animated: true)
}.disposed(by: disposeBag)
}
func setDelegateForTableView() {
newsTable.rx.setDelegate(self).disposed(by: disposeBag)
}
}
What I want to do is, adding a Favorite action to cell. I tried the code belove but I don't how can I access the news data on that row.
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let FlagAction = UIContextualAction(style: .normal, title: "Fav", handler: { (_: UIContextualAction, _: UIView, success: (Bool) -> Void) in
print("osman")
success(true)
})
FlagAction.backgroundColor = .orange
return UISwipeActionsConfiguration(actions: [FlagAction])
}
Can someone help me about this?
Instead of var news = PublishSubject<[Article]>() use let news = BehaviorSubject<[Article]>(value: []) (Note: Subjects should always be let constants, never var.)
Then when you need access to the articles, you can do:
let articles = (try? homeViewModel.news.value()) ?? []
I don't normally recommend using Subjects but the alternative here is to write your own DataSource type or use RxDataSources and that may be too deep down the rabbit hole for you.
if you do want to go down the rabbit hole
Bring in RxDataSources or write your own data source and make it a member of your view controller:
let dataSource = MyDataSource<Article, NewsCell>(cellIdentifier: "cell") { _, news, cell in
cell.newsTitle.text = news.title
cell.newsImage?.sd_setImage(with: URL(string: news.urlToImage ?? ""), placeholderImage: UIImage(systemName: "photo"))
}
use it like this:
func createCellView() {
homeViewModel.news
.bind(to: newsTable.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
Then you can access the articles like this:
let articles = dataSource.elements
Here's what a basic data source looks like. This does everything the default RxCocoa data source does.
class MyDataSource<Element, Cell>: NSObject, UITableViewDataSource, RxTableViewDataSourceType
where Cell: UITableViewCell {
private(set) var elements: [Element] = []
private let cellIdentifier: String
private let configureCell: (Int, Element, Cell) -> Void
init(cellIdentifier: String, configureCell: #escaping (Int, Element, Cell) -> Void) {
self.cellIdentifier = cellIdentifier
self.configureCell = configureCell
}
func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Element]>) {
guard case let .next(elements) = observedEvent else { return }
self.elements = elements
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? Cell else {
return UITableViewCell()
}
let element = elements[indexPath.row]
configureCell(indexPath.row, element, cell)
return cell
}
}
I'm trying to make a filter. I got a tableview. When I click any section, it expands and collapses.
My problem is that when I open and close other sections after clicking on the checkboxes, unselected checkboxes in other sections appear as selected and selected ones are unselected. What should I do? Can you show me some code? Thanks!
https://ibb.co/0htP7Hz // Filter image
var hiddenSections = Set<Int>()
var filtersArray = Set<String>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "FilterCell", for: indexPath) as? FilterTableViewCell else
{
fatalError("Product Group Cell not found")
}
guard let item = self.filterElementListVM.itemfilterviewmodelAtIndex(indexPath) else {
return UITableViewCell()
}
cell.setupCell(title: item.definition ?? "", buttonTag: item.id ?? 0, filterArray: self.filtersArray)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? FilterTableViewCell {
let selectedFilterItem = self.filterElementListVM.itemfilterviewmodelAtIndex(indexPath)
if cell.buttonCheck.isSelected {
self.filtersArray.remove(String(selectedFilterItem?.definition ?? ""))
} else {
self.filtersArray.insert(String(selectedFilterItem?.definition ?? ""))
}
cell.buttonCheckTap()
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let viewHeader = UIView.init(frame: CGRect.init(x: 0.0, y: 0.0, width: tableView.frame.size.width, height: 67.0))
viewHeader.backgroundColor = .white
let filterVM : FilterViewModel = self.filterElementListVM.filterViewModelAtIndex(section)
let viewFilterHeader : ViewFilter = ViewFilter.init(title: filterVM.definition,
rightImage: UIImage.init(named: "arrow_down")!, isPropertiesChanged: false, isArrowHidden: false)
viewFilterHeader.tag = section
let tap = UITapGestureRecognizer(target: self, action: #selector(hideSection(_:)))
viewFilterHeader.addGestureRecognizer(tap)
viewHeader.addSubview(viewFilterHeader)
viewFilterHeader.snp.makeConstraints { (make) in
make.top.equalTo(7.0)
make.bottom.equalTo(0.0)
make.leading.trailing.equalTo(0)
}
return viewHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 67.0
}
#objc private func hideSection(_ sender: UITapGestureRecognizer? = nil) {
guard let section = sender?.view?.tag else { return }
func indexPathsForSection() -> [IndexPath] {
var indexPaths = [IndexPath]()
for row in 0..<self.filterElementListVM.numberOfRowsInSection(section) {
indexPaths.append(IndexPath(row: row,
section: section))
}
return indexPaths
}
if self.hiddenSections.contains(section) {
self.hiddenSections.remove(section)
self.tableviewFilter.insertRows(at: indexPathsForSection(),
with: .fade)
} else {
self.hiddenSections.insert(section)
self.tableviewFilter.deleteRows(at: indexPathsForSection(),
with: .fade)
}
}
Looks like your configuration works wrong and when you toggle your cell old data applied to your cell. So you need to clear all your data in prepareFroReuse() method inside your UIColelctionViewCell class
More information: https://developer.apple.com/documentation/uikit/uitableviewcell/1623223-prepareforreuse
I have a weird situation where I swipe a cell to grey it out and it greys every 4th or 6th cell instead of only the single cell that was swiped.
The tableview is initialized as follows:
func setupView() {
view.backgroundColor = .white
tableView.register(EntityCell.self, forCellReuseIdentifier: "entityCell")
tableView.separatorStyle = .none
tableView.dataSource = self
tableView.delegate = self
}
Here is my query to get the data:
func getEntities(taxId : String) {
dispatchGroup.enter()
db.collection("Taxonomy").whereField("entityId", isEqualTo: entityId).whereField("status", isEqualTo: 401).getDocuments { (orderSnapshot, orderError) in
if orderError != nil {
self.showError(show: "Error", display: orderError!.localizedDescription)
} else {
self.entitiesArray.append(contentsOf: (orderSnapshot?.documents.compactMap({ (orderDocuments) -> Order in
Order(dictionary: orderDocuments.data(), invoiceId: orderDocuments.documentID, opened: false)!
}))!)
self.dispatchGroup.leave()
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Here are the standard override functions to populate the tableview:
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return entitiesArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "entityCell", for: indexPath) as? EntityCell else { return UITableViewCell() }
let entityRow = entitiesArray[indexPath.row]
cell.selectionStyle = .none
cell.setTaxonomy(entity: entityRow) // Setting up the cell with the array values
return cell
}
Everything is working fine upto this point. And finally here is the override func for swipe action:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let complete = UIContextualAction(style: .normal, title: "Verified") { (action, view, completionHandler) in
self.db.collection("Taxonomy").document(self.entitiesArray[indexPath.row].entityId).updateData(["status": 411]) { (error) in
if error == nil {
let cell = tableView.cellForRow(at: indexPath) as? EntityCell
cell?.changeStatus(currentEntity: self.entitiesArray[indexPath.row])
}
}
completionHandler(true)
}
complete.image = UIImage(named: "icon_approved")
complete.backgroundColor = UIColor(hex: Constants.Colors.secondary)
let swipe = UISwipeActionsConfiguration(actions: [complete])
return swipe
}
So I swipe right from the trailing edge of the cell and I see the underlying color and icon as expected. And the cell turns grey via this function via a protocol:
extension EntityCell : EntityStatusDelegate {
func changeStatus(currentEntity: EntityObject) {
entityCellBackground.backgroundColor = .systemGray4
}
}
The cell turns grey. And then I scroll down and I see every 4th or 6th cell is grey as well. Any idea what is going wrong? I am pretty flummoxed at this point.
Cells get recycled. You need either configure them completely or overwrite the prepareForReuse function of the cell or give each cell an unique reuseidentifyer so the tableview can recycle them.
(Last option is the worst as it cost a lot more memory)
Option 1:
Just set the backgroundcolor:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "entityCell", for: indexPath) as? EntityCell else { return UITableViewCell() }
let entityRow = entitiesArray[indexPath.row]
cell.selectionStyle = .none
cell.setTaxonomy(entity: entityRow) // Setting up the cell with the array values
cell.entityCellBackground.backgroundColor = (whatever the default color is)
return cell
}
I have an application where I have two sections the issue I have now is if I select an item in section 1, it automatically selects a cell in section 2 which is not suppose to be. I want Items to be selectable in section 1 without affecting section two.
below is my selection
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0:
showCustomDialog(subD: sub[indexPath.row])
case 1:
let cell = tableView.cellForRow(at: indexPath) as! VasListCell
cell.checkBox.setOn(true, animated: true)
default: break
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 1:
let cell = tableView.cellForRow(at: indexPath) as! VasListCell
cell.checkBox.setOn(false, animated: true)
default: break
}
}
where I am using the selected index
func selectedIndex(viewcontroller: UIViewController) {
let selectedRows = tableView.indexPathsForSelectedRows
guard let vasRow = selectedRows?.map ({ vas[$0.row] }) else { return }
selectedVasData = vasRow
let vasData = selectedVasData
let subData = selectedSubData
let vcr = viewcontroller as! CheckoutVC
vcr.vas = vasData
vcr.sub = subData
let tot1 = subData.compactMap {$0.price}
let tot2 = vasData.compactMap {$0.amount}
let tot = tot1 + tot2
let reduced = tot.compactMap(Double.init).reduce(0, +)
vcr.tableView.reloadData()
self.present(viewcontroller, animated: true, completion: nil)
print("CELL INDEX vas \(StaticFunc.convertDoubleToCurrency(amount: reduced))")
}
I am maintaining UISegmentControl and Search with a single tableview. Here, I am loading the tableview data from a JSON (language list).
Now I have two segment buttons like Source language and Target language and both segments tableviews also have same data. Here, whenever user selects source language a particular row is check marked and if then user clicks target language segment, the same check mark shows. I need to maintain separate data selections, also, I am going to use search bar.
Can you please provide me a solution for two different segment controller buttons but maintaining a single tableview and its data and UI look the same. Checkmark selection should be different and persistent.
My Code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "languagecell", for: indexPath) as! LangCustomCell
let item = langData[indexPath.row]
cell.flag_img.sd_setImage(with:url, placeholderImage: UIImage(named: "usa.png"))
cell.language_label.text = item.languageName
cell.language_label.textColor = UIColor.gray
cell.selectionStyle = .none
//configure you cell here.
if(indexPath.row == selectedIndex) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
Create two separate variables to store selected languages for from and to.
In tableView didSelectRowAt method check save in appropriate variable based on the selectedSegmentIndex. In TableView cellForRowAt check the selected languages with current language. If selectedSegmentIndex and selected language matches use .checkmark else use .none
And create two arrays with type [Language]. In searchBar textDidChange method filter the languages array and reload the tableView.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
struct Language: Equatable {
var title: String
var icon: UIImage?
}
var allLanguages = [Language]()
var filteredLanguages = [Language]()
var selectedFromLanguage:Language?
var selectedToLanguage:Language?
let segmentedControl = UISegmentedControl()
let tableView = UITableView()
let searchBar = UISearchBar()
override func viewDidLoad() {
super.viewDidLoad()
allLanguages = [Language(title: "English", icon: UIImage(named:"uk"))]
filteredLanguages = allLanguages
// add constraints segmentedControl, tableView, searchBar in view
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredLanguages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = filteredLanguages[indexPath.row].title
cell.imageView?.image = filteredLanguages[indexPath.row].icon
if segmentedControl.selectedSegmentIndex == 0 && selectedFromLanguage == filteredLanguages[indexPath.row] {
cell.accessoryType = .checkmark
} else if segmentedControl.selectedSegmentIndex == 1 && selectedToLanguage == filteredLanguages[indexPath.row] {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
// MARK: - Table view Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if segmentedControl.selectedSegmentIndex == 0 {//from
selectedFromLanguage = filteredLanguages[indexPath.row]
} else {//to
selectedToLanguage = filteredLanguages[indexPath.row]
}
tableView.reloadData()
}
// MARK: - Search bar Delegate
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
filteredLanguages = allLanguages
} else {
filteredLanguages = allLanguages.filter({ $0.title.localizedCaseInsensitiveContains(searchText) })
}
tableView.reloadData()
}
}
Use computed properties like this to persist the selected languages
var selectedFromLanguage:Language? {
get {
if let data = UserDefaults.standard.value(forKey: "fromLanguage") as? Data,
let language = try? JSONDecoder().decode(Language.self, from: data) {
return language
}
return nil
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: "fromLanguage")
}
}
}
var selectedToLanguage:Language? {
get {
if let data = UserDefaults.standard.value(forKey: "toLanguage") as? Data,
let language = try? JSONDecoder().decode(Language.self, from: data) {
return language
}
return nil
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: "toLanguage")
}
}
}
setup an action for your UISegmentControl:
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
// do what you need with your tableView
case 1:
// do what you need with your tableView
default:
return
}
}
when the index change setup your tableView and reload your data