Swift Tableview checkmark disappeared after relaunch viewcontroller - ios

In my case, I am loading JSON data into tableview. Here, the tableview cell multiple cell selection checkmark and uncheckmark options implemented. If go previous viewcontroller and comeback again tableview controller then last selected checkmark disappeared. How to store it?
JSON Codable
// MARK: - Welcome
struct Root: Codable {
let status: Bool
let data: [Datum]
}
// MARK: - Datum
struct Datum: Codable, Hashable {
let userid, firstname, designation: String?
let profileimage: String?
}
Custom Cell Class
class MyCustomCell: UITableViewCell {
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var nameCellLabel: UILabel!
#IBOutlet weak var subtitleCellLabel: UILabel!
}
Code for Tableview Checkmark
var studentsData = [Datum]()
var sessionData = Set<Datum>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
let item = self.studentsData[indexPath.row]
cell.nameCellLabel.text = item.firstname
cell.subtitleCellLabel.text = item.designation
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = self.studentsData[indexPath.row]
if let cell = tableView.cellForRow(at: indexPath) {
if cell.accessoryType == .checkmark {
cell.accessoryType = .none
// UnCheckmark cell JSON data Remove from array
self.sessionData.remove(item)
print(sessionData)
} else {
cell.accessoryType = .checkmark
// Checkmark selected data Insert into array
self.sessionData.insert(item)
print(sessionData)
}
}
}

Create a means to store the checkmark status within your data struct
struct Datum: Codable, Hashable {
let userid, firstname, designation: String?
let profileimage: String?
var selected: Bool = false
}
Set the value when you create a cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
let item = self.studentsData[indexPath.row]
cell.nameCellLabel.text = item.firstname
cell.subtitleCellLabel.text = item.designation
cell.accessoryType = item.selected ? .checkmark : .none
return cell
}
And then in the didSelectRowAt replace the if block with the below to save the change back to the student data and then reset the checkmark accordingly:
self.studentsData[indexPath.row].selected.toggle()
cell.accessoryType = studentsData[indexPath.row].selected ? .checkmark : .none

You should always save the checkMark status in another array or variable.
In case only one item can be selected:
var SELECTED_ITEMS= [YOUR_DATA_TYPE]()//It must be global within tableViewController
in case multiple selection allowed
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = self.studentsData[indexPath.row]
if SELECTED_ITEMS.contain(item){
SELECTED_ITEMS.remove(item)
}else{
SELECTED_ITEMS.append(item)
}
}
Remember SELECTED_ITEM should be Array of your tableviewdata and SELECTED_ITEM is just the same type of your tableview data.
Also if you are initializing your model in ViewDidLoad or ViewWillAppear in tableview controller, make should SELECTED_ITEMS and SELECTED_ITEM are not reset when tableview appears.
then
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
........
cell.accessoryView = SELECTED_ITEMS.contain(item) ? .checkmark:.none// Multi Selection
.........
}
In general, you update your model, a variable or array or whatever fits in your code to keep a track of which indexpath is select/unselected. Then in cellForRowAt you can check the above variable/array... to set accessory.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath){}
will also work

Related

pass text from selected tableView cell.row to fill textbox xcode

I have a tableView that allows users to make multiple selections from an array of data,
When the user clicks done, I would like the selected text to be then transferred over to another tableViews textView
Is there a way to transfer over the selected text and have the text separated by a , ?
I am coding programmatically.
var checked = [Int]()
var items = [String]()
var selectedItems = [String]()
#objc func done() {
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Saving!"
hud.show(in: view)
dismiss(animated: true, completion: nil)
hud.dismiss()
let aCell = aboutCell(style: .default, reuseIdentifier: nil)
aCell.textField3.text = selectedItems.joined(separator: ",")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
if selectedItems.contains(items[indexPath.row]) {
selectedItems.remove(at: selectedItems.firstIndex(of: items[indexPath.row])!)
} else {
selectedItems.append(items[indexPath.row])
}
checked.append(indexPath.row)
}
}
According to my understanding to the question, these are my thoughts:
1. First setup necessary variables
var items = [String]() // data to display in tableview
var selectedItems = [String]() // here all the selected datas are stored
2. Store the selected items data from the didSelectRowAt delegate method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedItems.contains(items[indexPath.row]) { //check if the selected already contains the items and if contains remove it
selectedItems.remove(at: selectedItems.firstIndex(of: items[indexPath.row])!)
} else { // append the required items
selectedItems.append(items[indexPath.row])
}
// ..... other codes here
}
3. on done button
let requiredText = selectedItems.joined(separator: ",")
// pass this data through delegate method
There is no need to create an array for the selected items. You can simply call tableview method selectRow(at:animated:scrollPosition:) when selecting a row and when you need to get the selected rows just call tableview instance property indexPathsForSelectedRows. Then you just need to join the selected rows with a comma and use the resulting string in your textview or textfield. Don't forget to implement didDeselectRowAt item method as well to deselectRow.
import UIKit
class TableViewController: UITableViewController {
var items: [String] = ["1st", "2nd", "3rd", "4th", "5th"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
func done() {
if let indexPaths = tableView.indexPathsForSelectedRows {
// note that this will preserve the order that the rows where selected. Just sort the indexPaths if you need it sorted.
let string = indexPaths.map { items[$0.row] }.joined(separator: ",")
print(string)
// your code
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellID", for: indexPath) as! TableViewCell
cell.textLabel?.text = items[indexPath.row]
cell.accessoryType = cell.isSelected ? .checkmark : .none
return cell
}
}

Combine Migration?

How to replace closure or delegation callback which get called when user tap on a button of a table view cell with Combine framework ?
problem - if Subscriber is added from the view controller and store returned AnyCancellable in a Set ; 1. storage of anyCancellable is getting heigh as cell return .2. many subscribers receive value when user tap on one button of a cell
I used built in subscriber Sink
In ViewController
var myAnyCancellableSet: Set<AnyCancellable> = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mycell")
cell.doSomethingSubject.sink {
print("user tap button on cell")
}.store(in: &myAnyCancellableSet)
return cell
}
In tableview cell
import UIKit
import Combine
class MyTableViewCell: UITableViewCell {
private lazy var myDoSomethingSubject = PassthroughSubject<Void, Never>()
lazy var doSomethingSubject = myDoSomethingSubject.eraseToAnyPublisher()
#IBAction func buttonTapped(_ sender: UIButton) {
myDoSomethingSubject.send()
}
}
Don't store all your tickets together. Store them by index path.
var ticketForIndexPath: [IndexPath: AnyCancellable] = [:]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mycell") as! MyCell
ticketForIndexPath[indexPath] = cell.doSomethingSubject.sink {
print("user tap button on cell")
}
return cell
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
ticketForIndexPath[indexPath] = nil
}

How to calculate values that are selected from xcode tableView

i would like to make something like shopping, whenever the user selects a cell from the UITableView the price of that item will show in the total Label and when he selects another item the sum of the 2 items will show in the total label. i made a plist and store the items there. what i want to do now is how to assign the price values into the items and when the user selects a cell "item" the price will be shown. this is what i have made so far. please help me.
this is the view controller:
(https://imgur.com/sQnz1YY)
import UIKit
class ServiceListViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tablelbl: UITableView!
var SerArr :[ServiceOB] = []
var ArrService: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
tablelbl.delegate = self
tablelbl.dataSource = self
let path = Bundle.main.path(forResource: "ServiceList", ofType: "plist")
let serArray : NSArray = NSArray(contentsOfFile: path!)!
for service in serArray {
print(service)
let serviceOBJ = ServiceOB()
// i change your plist to array
serviceOBJ.Service = service as! String
// check now..
SerArr.append(serviceOBJ)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return SerArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tablelbl.dequeueReusableCell(withIdentifier: "ServiceListCell") as! ServiceTableViewCell
let obj = SerArr[indexPath.row]
cell.servicelbl.text! = obj.Service
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none }
else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
}
Create a delegate of tablecell and call delegate method from button click in tablecell.
protocol tableCellDelegate {
func callMethod()
}
class tableCell: UITableViewCell{
var delegate: tableCellDelegate?
#IBAction func selecteItem(_ sender: Any) {
delegate?.callMethod()
}
class VC: UIViewController{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = .....
cell.delegate = self
return cell
}
func callMethod(){
// Do some thing}
}
It's easy. Just fetch the table cell with the original cell type that was used and keep a variable to store the value for total price:
#IBOutlet weak var priceLabel: UILabel!
var price: Double = 0.0 {
didSet {
// You can use localisation to change the currency. But that is currently not a part of the problem statement.
priceLabel?.text = "$\(price)"
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? ServiceTableViewCell {
// Assuming cell has a property for storing price: itemPrice
if cell.accessoryType == .checkmark {
totalPrice -= cell.itemPrice
cell.accessoryType = .none
} else {
totalPrice += cell.itemPrice
cell.accessoryType = .checkmark
}
}
}
NOTE: You don't need plist file for storing the selected values. But if you need it for some of the other reasons not mentioned here, you can alter the contents of the plist based on selection as well.

how to make limited multiple checkmark in Table View Cell in Swift?

I am new in programming and iOS Development, I need to make table view that has multiple limited checkmark.
I mean, I want to allow the user to select maximum 3 items (not just 1, but also not all of item in the table view can be selected) in the table view, I have tried but I haven't gotten what I want, I just can select one only item in table view
here is the code I use
import UIKit
class CreateEventStep2VC: UIViewController {
#IBOutlet weak var eventTypeNameLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var newEvent : [String:Any]!
var eventTypeAvailableData = [String]()
var selectedEventTypes = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// initial value
eventTypeNameLabel.text = ""
// get event Type Data list from EventType data model
eventTypeAvailableData = EventType.allValues.map { $0.toString() }
}
}
extension CreateEventStep2VC : UITableViewDataSource {
//MARK: - UITableViewDatasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventTypeAvailableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventTypeCell", for: indexPath) as! CreateEventStep2Cell
cell.eventTypeNames = eventTypeAvailableData[indexPath.row]
return cell
}
}
extension CreateEventStep2VC : UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
}
could you please help me ?
You can't simply add the checkmark to the cell; cell objects will be re-used as the tableview scrolls, so you will lose checkmarks and end up with checkmarks in cells that shouldn't have them.
You need to track the checked cells in another structure; I suggest using a Set<IndexPath>. You can either allow multi-selection in your tableview, or (my preference) deselect the row after you add the checkmark.
You also need to ensure that your cellForRowAt: sets the accessory type correctly
class CreateEventStep2VC: UIViewController {
#IBOutlet weak var eventTypeNameLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var newEvent : [String:Any]!
var eventTypeAvailableData = [String]()
var selectedEventTypes = Set<IndexPath>()
override func viewDidLoad() {
super.viewDidLoad()
// initial value
eventTypeNameLabel.text = ""
// get event Type Data list from EventType data model
eventTypeAvailableData = EventType.allValues.map { $0.toString() }
}
}
extension CreateEventStep2VC : UITableViewDataSource {
//MARK: - UITableViewDatasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventTypeAvailableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventTypeCell", for: indexPath) as! CreateEventStep2Cell
cell.eventTypeNames = eventTypeAvailableData[indexPath.row]
cell.accessoryType = selectedEventTypes.contains(indexPath) ? .checkMark:.none
return cell
}
}
extension CreateEventStep2VC : UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
if selectedEventTypes.contains(indexPath) {
selectedEventTypes.remove(indexPath)
} else if selectedEventTypes.count < 3 {
selectedEventTypes.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], animated:.none)
}
}
You can have array of indexPath rows allArr like this
1- when user selects more than 3 the first one will be automatically dropped
var allArr = [Int]()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
allArr.append(indexPath.row)
}
if(allArr.count == 4)
{
allArr.dropFirst()
}
}
2- add this to cellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EventTypeCell", for: indexPath) as! CreateEventStep2Cell
cell.eventTypeNames = eventTypeAvailableData[indexPath.row]
if allArr.contains(indexPath.row) {
cell.accessoryType = .checkmark
}
else
{
cell.accessoryType = .none
}
return cell
}
3- remove code in didSelectRowAt

Custom MKMarkerAnnotationView not displayed correctly in UITableViewCell the first time

I have a tableview where I want to display in UITableViewCell a custom MKMarkerAnnotationView. This tableview is displayed in a dedicated ViewController and the ViewController is in a TabBarController. The second Tab display a Map (but it's not important here).
I have created a class "EdgePinViewAnnotation" that inherits from the MKMarkerAnnotationView
In my storyboard, in the TableView I have added a UITableViewCell that contains some labels and a view with the class "EdgePinViewAnnotation"
In the implementation of a UITableViewCell, I'm initializing my custom "EdgePinViewAnnotation".
Unfortunately when the Cell is displayed in the table, it's a default MKMarkerAnnotationView that is displayed and not my customized "EdgePinViewAnnotation".
When I scroll my TableView and the cell goes out of screen and then it's refreshed it displays my "EdgePinViewAnnotation" correctly.
Why it's not displaying correctly the first time?
Here the code I have implemented for my custom MKMarkerAnnotationView
class EdgePinViewAnnotation: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {
if let edgeAnnotation = newValue as? EdgePin {
animatesWhenAdded = true
isDraggable = true
canShowCallout = true
initRenderingProperties(pin: edgeAnnotation)
}
}
}
func initWith(edgePin:EdgePin) {
animatesWhenAdded = false
isDraggable = false
canShowCallout = false
initRenderingProperties(pin: edgePin)
}
func initRenderingProperties(pin edgePin:EdgePin) {
glyphTintColor = UIColor.white
glyphText = "\(edgePin.index)"
markerTintColor = edgePin.renderingColor
}
}
Here the code from my UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
if let cell = tableView.dequeueReusableCell(withIdentifier: CellId.DistanceConfigurationCellId, for: indexPath) as? DistanceConfigurationTableViewCell {
cell.theLabel.text = findMe.edgePoints[indexPath.row].address
let coord = findMe.edgePoints[indexPath.row].coordinate
cell.coordinates.text = "Lat:\(coord.latitude) / Long:\(coord.longitude)"
cell.theTextField.text = "\(findMe.edgePoints[indexPath.row].distance)"
cell.theTextField.delegate = self
cell.theTextField.tag = indexPath.row
cell.theMarker.initWith(edgePin:findMe.edgePoints[indexPath.row])
return cell
}
} else {
return UITableViewCell()
}
}
class DistanceConfigurationTableViewCell: UITableViewCell {
#IBOutlet weak var theLabel: UILabel!
#IBOutlet weak var coordinates: UILabel!
#IBOutlet weak var theTextField: UITextField!
#IBOutlet weak var theMarker: EdgePinViewAnnotation!
}
I found the solution, even if I don't really understand it.
The MKMarkerAnnotationView must be configured in
tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
instead of
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
and then the following code is working and the MKMarkerAnnotationView is displayed correctly in each UITableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
if let cell = tableView.dequeueReusableCell(withIdentifier: CellId.DistanceConfigurationCellId, for: indexPath) as? DistanceConfigurationTableViewCell {
cell.theLabel.text = findMe.edgePoints[indexPath.row].address
let coord = findMe.edgePoints[indexPath.row].coordinate
cell.coordinates.text = "Lat:\(coord.latitude) / Long:\(coord.longitude)"
cell.theTextField.text = "\(findMe.edgePoints[indexPath.row].distance)"
cell.theTextField.delegate = self
cell.theTextField.tag = indexPath.row
return cell
}
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let theCell = cell as? DistanceConfigurationTableViewCell {
theCell.theMarker.initWith(edgePin:findMe.edgePoints[indexPath.row])
}
}

Resources