I am fetching previously selected categorylist from the server. say for an example.cateogrylist i fetched from the server was in following formate
categoryid : 2,6,12,17
now what i need to do is want to enable checkmark in my tableview based on this categorylist,for that purpose i converted this list into an [Int] array like this :
func get_numbers(stringtext:String) -> [Int] {
let StringRecordedArr = stringtext.components(separatedBy: ",")
return StringRecordedArr.map { Int($0)!}
}
in viewDidLoad() :
selectedCells = self.get_numbers(stringtext: UpdateMedicalReportDetailsViewController.catId)
print(myselection)
while printing it's giving me results like this : [12,17,6,8,10]
i want to enable checkimage based on this array.I tried some code while printing its giving me the right result like whatever the categories i selected at the time of posting ,i am able to fetch it but failed to place back this selection in tableview.Requirement : while i open this page it should show me the selection based on the categorylist i fetched from the server.
var selectedCells : [Int] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell1 = table.dequeueReusableCell(withIdentifier: "mycell") as! catcell
cell1.mytext.text = categoriesName[indexPath.row]
if UpdateMedicalReportDetailsViewController.flag == 1
{
selectedCells = self.get_numbers(stringtext: UpdateMedicalReportDetailsViewController.catId)
cell1.checkimage.image = another
print(selectedCells)
}
else
{
selectedCells = []
cell1.checkimage.image = myimage
}
return cell1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = table.cellForRow(at: indexPath) as! catcell
cell.checkimage.image = myimage
if cell.isSelected == true
{
self.selectedCells.append(indexPath.row)
cell.checkimage.image = another
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = table.cellForRow(at: indexPath) as! catcell
if cell.isSelected == false
{
self.selectedCells.remove(at: self.selectedCells.index(of: indexPath.row)!)
cell.checkimage.image = myimage
}
}
output :
This is a very common use case in most apps. I'm assuming you have an array of all categories, and then an array of selected categories. What you need to do is in cellForRowAtIndexPath, check to see if the current index path row's corresponding category in the "all categories" array is also present in the "selected categories" array. You can do this by comparing id's etc.
If you have a match, then you know that the cell needs to be selected/checked. A clean way to do this is give your cell subclass a custom load method and you can pass a flag for selected/checked.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "mycell") as! catcell
let category = self.categories[indexPath.row] // Let's say category is a string "hello"
Bool selected = self.selectedCategories.contains(category)
cell.load(category, selected)
return cell
}
So with the code above, let's say that categories is just an array of category strings like hello, world, and stackoverflow. We check to see if the selectedCategories array contains the current cell/row's category word.
Let's say that the cell we're setting up has a category of hello, and selectedCategories does contain it. That means the selected bool gets set to true.
We then pass the category and selected values into the cell subclass' load method, and inside that load method you can set the cell's title text to the category and you can check if selected is true or false and if it's true you can display the checked box UI.
Related
I am working on an small project where I have an app that takes in tvshow information entered by the user and displays it in a custom tableview cell. I would like to sort the shows as they are entered based on which current episode the user is on. I know this code works because I tested it with print statements and it sorts the array but it does not sort on the simulator. So I just was curious where I should place this so that it sorts on the app side.
func sortShows() {
let sortedShows = tvShows.sorted { $0.currentEpisode > $1.currentEpisode}
TVShowTableView.reloadData()
print(sortedShows)
}
Here is where I am currently placing it inside my view controller
extension TVShowListViewController: AddTVShowDelegate {
func tvShowWasCreated(tvShow: TVShow) {
tvShows.append(tvShow)
dismiss(animated: true, completion: nil)
TVShowTableView.reloadData()
sortShows()
}
}
In this part of your code:
func sortShows() {
// here you are creating a NEW array
let sortedShows = tvShows.sorted { $0.currentEpisode > $1.currentEpisode}
// here you tell the table view to reload with the OLD array
TVShowTableView.reloadData()
print(sortedShows)
}
In your controller class, you probably have something like:
var tvShows: [TVShow] = [TVShow]()
and then you populate it with shows, like you do with a new show:
tvShows.append(tvShow)
Then your controller is doing something like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tvShowCell", for: indexPath) as! TVShowCell
cell.tvShow = tvShows[indexPath.row]
return cell
}
What you want to do is add another var to your class:
var sortedShows: [TVShow] = [TVShow]()
then change your sort func to use that array:
func sortShows() {
// use the existing class-level array
sortedShows = tvShows.sorted { $0.currentEpisode > $1.currentEpisode}
// here you tell the table view to reload
TVShowTableView.reloadData()
print(sortedShows)
}
and change your other funcs to use the sortedShows array:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// use sortedShows array
return sortedShows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tvShowCell", for: indexPath) as! TVShowCell
// use sortedShows array
cell.tvShow = sortedShows[indexPath.row]
return cell
}
and you'll want to call sortShows() at the end of viewDidLoad() (or wherever you are getting your initial list of shows).
Edit
Another way you might use cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tvShowCell", for: indexPath) as! TVShowCell
// use sortedShows array
let tvShow = sortedShows[indexPath.row]
cell.showTitleLable.text = tvShow.title
cell.showDecriptionLable.text = tvShow.description
return cell
}
I am trying to access an array's indexPath inside a function to update this array's data but I don't know how to pass the indexPath as a parameter (espacially what to pass when calling) to the function or if this is even the solution.
I included cellForRowAt to illustrate how this function access indexPath.
var cryptosArray: [Cryptos] = []
extension WalletTableViewController: UITableViewDelegate, UITableViewDataSource, CryptoCellDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let crypto = cryptosArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.setCrypto(crypto: crypto)
cell.delegate = self
cell.amountTextField.delegate = self
return cell
}
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let crypto = cryptosArray[indexPath.row] //<---- How to do that?
crypto.amount = walletTableViewCell.amountTextField.text
//Then update array's amount value at correct index
walletTableViewCell.amountTextField.text = ""
}
}
Instead of hacking something, simply ask the tableView to tell you the indexPath of the given cell:
// use indexPath(for:) on tableView
let indexPath = tableView.indexPath(for: walletTableViewCell)
// then you can simply use it
let crypto = cryptosArray[indexPath.row]
UITableView.indexPath(for:) documentation says:
Returns an index path representing the row and section of a given table-view cell.
And this is exactly what you want, you don't want to hack the indexPath to the cell. indexPath should be taken care of by the tableView, not the cell. Ideally, cell should be completely oblivious of its indexPath.
Always try to use the standard way to solve your problems. In general, when you are trying to solve something, I would recommend you to first look at the documentation of the UITableView, there are many useful methods there.
if you want to get index path.row when user clicked on cell , you should get index path.row when user clicked and then use this to your func
for ex :
var indexrow : int = 0
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// table cell clicked
indexrow = indexPath.row
}
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let crypto = cryptosArray[indexrow]
crypto.amount = walletTableViewCell.amountTextField.text
//Then update array's amount value at correct index
walletTableViewCell.amountTextField.text = ""
}
I have list of product items to display in my table view. At the same time i have i have some other api call, Where i will pass my prodcut item name to check. If that product item is available then that particular data or item cell alone will be highlighted and it will be disabled.
Now what i need is, when i do api call, and after that if that particular data or product name is available in that api, instead of highlight and disable... I should not show that particular data in my table view.
How to do that:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! AllConnectionsTableViewCell
if let contact = filtered?[indexPath.row]{
cell.emailOutlet.text = AccountDataCache.sharedInstance.displayMaskAccnt(items: product.name)
cell.nameOutlet.text = product.name
if let _ = self.checkapicall(items: product.name){
// here if my product name is availble in api, then only the backgroudnd and it will be disabled
if let product = filtered?[indexPath.row]{
cell.namelabel.text = product.name
if let _ = self.checkapicall(items: product.vpa){
cell.cellOuterView.backgroundColor = UIColor.red
cell.isUserInteractionEnabled = false
}else{
cell.cellOuterView.backgroundColor = UIColor.white
cell.isUserInteractionEnabled = true
}
}
}
Instead of chnaging BG, Disable..i should not show that data in that tableview cell.How to do that.?
Thanks
As you described, if your data looks like this:
name1, name2,name3, name4
Then you want to show four rows in your tableView.
If name2 is available in your API call then you want to show this:
name1, name3, name4
So what you need to do is to get all the names before you start updating the tableView. This is because you need to set how many rows you want to display in your tableView.
You could do something like this (I´m not sure how you fetch your data today, but this is an example to get you started):
// check add edit to your product
var products = [Product(name: "name1", vpa: "1"), Product(name: "name2", vpa: "2"), Product(name: "name3", vpa: "3"), Product(name: "name4", vpa: "4")]
// set the produts count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
// just set the name here, don´t make any checks
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StartPageCell", for: indexPath)
cell.namelabel.text = product.name
return cell
}
// check the names here and then reload the tableView
func checkNames() {
for product in products {
if self.checkapicall(items: product.vpa){ {
if let index = products.index(where: { $0.vpa == vpa }) {
products.remove(at: index)
}
}
}
tableView.reloadData()
}
First of all don't make such checks in cellForRow, make it in viewDidLoad or viewWillAppear
Add a property var isAvailable = false to your product model
In viewDidLoad or viewWillAppear check the availability of the products and set isAvailable accordingly.
Create the data source array var filtered = [Product]() (assuming Product is the data model) and filter the items filtered = allItems.filter { $0.isAvailable }
Reload the table
I have a tableview and in each cell there is a checkbox. I also have a "select all" button.
My problem is that when I click select all I want to update all the checkboxes to checked state. So from a list of 100 cells, all get checked but every 13th cell does not. To make it clearer, on my simulators screen are 12 cells visible that all get checked. When I start scrolling, the first cell that comes up is unchecked, and is then followed by 12 checked ones :S
When I scroll a little and click "select all" again, the skipped ones become also checked..
Anyone have a clue what am I missing?
This is the cell code:
class ListTableViewCell: UITableViewCell {
#IBOutlet weak var checkbox: UIButton!
var buttonState = false{
didSet{
if buttonState{
checkbox.setImage(#imageLiteral(resourceName: "checked"), for: .normal)
}else{
checkbox.setImage(#imageLiteral(resourceName: "unchecked"), for: .normal)
}
}
}
#IBAction func checkboxAction(_ sender: UIButton) {
if buttonState {
buttonState = false
}else{
buttonState = true
}
}
func simulateCheck(){
buttonState = true
}
And here are some snipets from my controller:
private var articleValues: [ArticleValue] = []{
didSet{
tableView.reloadData()
}
}
func selectAll(){
for i in 0..<articleValues.count{
let cell = tableView.cellForRow(at: IndexPath(item: i, section: 0)) as? ListTableViewCell
cell?.simulateCheck()
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
}
return cell
}
Your UITableView is backed by a data source. This means that you shouldn't change cells directly like you do here:
cell?.simulateCheck()
tableView.reloadData()
Instead you should keep a list of all the checked positions, maybe another array that has bools for each corresponding articleValue (this is not the best design).
var checkedValues = Bool
In your
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method you would then set the state of the cell:
articleValueCell.buttonState = checkedValues[indexPath.row]
In your selectAll method fill this array with true values and then call tableView.reloadData()
private var checkedValues = [Bool]()
private var articleValues: [ArticleValue] = []{
didSet{
checkedValues = Array(repeating: false, count: articleValues.count)
tableView.reloadData()
}
}
func selectAll(){
checkedValues = Array(repeating: true, count: articleValues.count)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
articleValueCell.buttonState = checkedValues[indexPath.row]
}
return cell
}
Another mistake is that you should never iterate on all the cells in the table because they are reused, no point in going through your data source and getting a cell for each. It only makes sense to iterate through tableView.visibleCells. But like in your case, most of the time you don't need that either, you should just update your data source accordingly and reload the table or just the modified cell.
It's not recommended that you refer to cells directly within a table view. The reason is that UITableViews have an efficient method of only loading the cells as they are needed (and deallocating them when they are no longer needed, e.g. the cell scrolls off screen). Because of this the cell you are try to refer to may not be loaded.
Instead you should interact with it via the cellForRowAt method. If you want to "select all" cells, you should create a property that stores the value of checked or not checked via a Bool and then set all of the ArticleValue elements to true for that property and reload the data inside selectAll().
It could work something like this:
func selectAll() {
articleValues.forEach {
$0.checked = true
}
tableView.reloadData()
}
// ...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "articleValueItem", for: indexPath)
// Cell Configuration
let articleValue = articleValues[indexPath.row]
if let articleValueCell = cell as? ListTableViewCell{
articleValueCell.articleValue = articleValue
if articleValue.checked {
articleValueCell.simulateCheck()
}
}
return cell
}
I have a one view app with embedded UITableView that displays a list of "stores"(Realm object). By default I populate the table view of all the Store objects. IF the user wants to then narrow the results they can do so by using any combination of text fields in MasterVC. When they hit search - simply update TableView with 'filtered' Realm objects.
What works:
Populate UITableView with objects from the Realm.
Create new Realm entries via text field entries in MasterVC and repopulate table in ResultsVC.
Swipe to delete object on table / and Realm object.
What sort of works:
If user enters a search term then 'filter' the Realm object (Stores) and repopulate the table. This correctly reloads and returns the number of results. However the First Cell (0) of the TableView is always the exact same and never updates.. If there are 20 returned results in the search then Rows 1-18 are correctly displayed. Row 0 is static and never changes its text. Any obvious reasons why?
Results Table View Controller
class ResultsVC: UITableViewController {
// data source
var stores: Results<Store> = {
let realm = try! Realm()
return realm.objects(Store.self)
}()
var token: NotificationToken?
...
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stores.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self)
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}
}
Here is how I'm accessing the ResultsVC from MasterVC
Master View Controller
class MasterViewController: UIViewController {
...
#IBAction func searchDatabase(_ sender: Any) {
let CVC = childViewControllers.first as! UINavigationController
let resultVC = CVC.viewControllers[0] as? ResultsVC
result.stores = stores.filter("address = '1234 Blue Street'")
result.tableView.reloadData()
}
...
}
Turns out I had a duplicate variable which was overwriting the orig from above.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! ResultsCustomViewCell
let stores = realm.objects(Store.self) // <- OVERWRITING ORIGINAL //
let currentStore = stores[indexPath.row]
cell.storeNumber.text = "#\(currentStore.storeNumber)"
cell.storeName.text = "\"\(currentStore.storeName)\""
return cell
}