Expanding List UITableView is duplicating - ios

I want to create a dynamic table that expands another data when it selected.
I've follow this tutorial https://www.youtube.com/watch?v=ClrSpJ3txAs but i cant make dynamic (each data is duplicating)
Here's what i want to make
And here's what i've been made
The data itself is duplicating and i dont know how to fix it because when i change some code inside tableView(), it'll crash when i select it or when i'm segue into this view.
Here's my code:
import UIKit
import PopupDialog
struct cellData {
var isOpened = Bool()
var title = [String]()
var sectionData = [String]()
}
class TutorialBankTableViewController: UITableViewController {
var user = DBManager.instance.getUserProfile()
var tableViewData = [cellData]()
var extendedData = ""
var sendTitleNameForGettingExtendedData = ""
var arrayextendedData: [String] = []
var titleName: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
reloadTableView()
// Do any additional setup after loading the view.
}
func reloadTableView() {
if titleName.isEmpty == true {
requestTitleName()
} else {
tableViewData = [cellData(isOpened: false, title: titleName, sectionData: arrayextendedData),
cellData(isOpened: false, title: titleName, sectionData: arrayextendedData),
cellData(isOpened: false, title: titleName, sectionData: arrayextendedData),
cellData(isOpened: false, title: titleName, sectionData: arrayextendedData),
cellData(isOpened: false, title: titleName, sectionData: arrayextendedData)]
DispatchQueue.main.async { self.tableView.reloadData() }
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return tableViewData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if tableViewData[section].isOpened {
return (tableViewData[section].sectionData.count + 1)
}
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
return UITableViewCell()
}
cell.textLabel?.text = tableViewData[indexPath.section].title[indexPath.row]
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
return UITableViewCell()
}
self.tableView.contentInset = UIEdgeInsetsMake(0, 16, 0, 0);
cell.textLabel?.text = tableViewData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if tableViewData[indexPath.section].isOpened {
let indexPath = tableView.indexPathForSelectedRow //optional, to get from any UIButton for example
let currentCell = tableView.cellForRow(at: indexPath!) as! UITableViewCell
print(currentCell.textLabel!.text)
sendTitleNameForGettingExtendedData = currentCell.textLabel!.text!
requestExtendedData()
} else {
tableViewData[indexPath.section].isOpened = true
}
let sections = IndexSet(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
}
func requestTitleName(){
NetworkingService.shared.reqTitle(phone_number: user!.phone_number ?? "") { spinnerResponse in
if (spinnerResponse == nil){
self.view.makeToast("Technical problem, please try again...")
} else {
if (spinnerResponse?.success == Const.ResponseKey.Success){
print(spinnerResponse ?? "")
if let apiResponseData = spinnerResponse!.data{
self.titleName.removeAll()
// filtering data what I want to put
_ = apiResponseData.filter { (dic) -> Bool in
if dic.types == “Testing Request“ {
self.titleName.append(dic.listName ?? "")
}
return true
}
}
print(self.titleName)
self.reloadTableView()
} else if (spinnerResponse?.success == Const.ResponseKey.SessionEnd) {
self.sessionEnd()
} else{
let popup = PopupDialog(title: "Gagal Meminta Data ", message: spinnerResponse?.error)
let buttonOK = DefaultButton(title: "OK"){
}
popup.addButton(buttonOK)
self.present(popup,animated: true,completion: nil)
}
}
}
}
func requestExtendedData() {
print(TransactionNumber.instance.genTransNumber(_jenisTransaksi: .BANK, _phoneNumber: (user?.phone_number)!))
NetworkingService.shared.requestExtendedDataCashin(phone_number: user!.phone_number!, jenis_trx: "VI", nama_lk: sendTitleNameForGettingExtendedData) { responseTutorial in
if (responseTutorial == nil) {
self.view.makeToast("Technical problem, please try again...")
} else {
if responseTutorial?.success == Const.ResponseKey.Success {
self.extendedData = (responseTutorial?.token)!
// convert into array for showing all text in responseTutorial.token
var delimiter = "\n"
self.arrayextendedData = (responseTutorial?.token!.components(separatedBy: delimiter))!
self.reloadTableView()
} else if responseTutorial?.success == Const.ResponseKey.SessionEnd {
self.sessionEnd()
} else if responseTutorial?.success == Const.ResponseKey.Error {
self.view.makeToast((responseTutorial?.token)!)
}
}
}
}
}
How can i fix this duplicating data?
Thank you.

Related

display different custom table view cells based on if a int is > 0

I have a table in my sql database that shows artists, song ,and albums. Each of these has a id. I also have 3 custom cells.
If the id is over 0, i'd like for that song, artist or albums to show in the table view. I am getting this data with arrays.
Whenever this code runs I get a Thread 1: Fatal error: Index out of range crash. It might have something to do with the logic of my if statement. Any suggestions would be appreciated.
var searchActive: Bool = false
var search = [Search]()
var songs = [Songs]()
var artists = [Artist]()
var album = [Album]()
var cleanSong = ""
var artistName = ""
var albumName = ""
var songCover = UIImage()
var artistPic = UIImage()
var albumCover = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(SongTableViewCell.nib(), forCellReuseIdentifier: SongTableViewCell.songCell)
tableView.register(ArtistTableViewCell.nib(), forCellReuseIdentifier: ArtistTableViewCell.artistCell)
tableView.register(AlbumTableViewCell.nib(), forCellReuseIdentifier: AlbumTableViewCell.AlbumCell)
tableView.delegate = self
tableView.dataSource = self
searchesBar.delegate = self
print(search)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (search[indexPath.row].songid > 0) { //CRASH: Thread 1: Fatal error: Index out of range
let cell = tableView.dequeueReusableCell(withIdentifier: "SongTableViewCell", for: indexPath) as! SongTableViewCell
cell.mainLabel!.text = songs[indexPath.row].cleanName
cell.secondLabel!.text = songs[indexPath.row].artistName
cell.cellImage!.image = UIImage(named: songs[indexPath.row].cover)
return cell
} else if (search[indexPath.row].artistid > 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArtistTableViewCell", for: indexPath) as! ArtistTableViewCell
cell.artistLabel.text = artists[indexPath.row].artistName
cell.artiistImage.image = UIImage(named: artists[indexPath.row].picture)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumTableViewCell", for: indexPath) as! AlbumTableViewCell
cell.albumLabel!.text = search[indexPath.row].cleanSong
cell.albumCover.image = UIImage(named: album[indexPath.row].cover)
return cell
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchActive) {
return search.count
} else {
return 1
}
}
it because you have condition in numberOfRowsInSection, you need to check the same condition in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if searchActive {
if (search[indexPath.row].songid > 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "SongTableViewCell", for: indexPath) as! SongTableViewCell
cell.mainLabel!.text = songs[indexPath.row].cleanName
cell.secondLabel!.text = songs[indexPath.row].artistName
cell.cellImage!.image = UIImage(named: songs[indexPath.row].cover)
return cell
} else if (search[indexPath.row].artistid > 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArtistTableViewCell", for: indexPath) as! ArtistTableViewCell
cell.artistLabel.text = artists[indexPath.row].artistName
cell.artiistImage.image = UIImage(named: artists[indexPath.row].picture)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumTableViewCell", for: indexPath) as! AlbumTableViewCell
cell.albumLabel!.text = search[indexPath.row].cleanSong
cell.albumCover.image = UIImage(named: album[indexPath.row].cover)
return cell
}
} else {
// return cell for searchActive == false, I don't know what you want to show, so I return empty UITableViewCell
return UITableViewCell()
}
}
I personally don't like the way you are dealing with the current scenario. We can rather build this through a better design pattern instead of using Int and having three different UITableViewcell class. I would prefer to do that through Bride Pattern. I tried to do this with below code:
//Resource protocol is used to display for cell used in table view cell
//we can use model also in place of different functions to return cell data
protocol ResourceProtocol {
func snippet()
func title()
func image()
func url()
}
class ArtistResource: ResourceProtocol {
func snippet() {
//return snippet
}
func title() {
//return title
}
func image() {
//return image
}
func url() {
//return url
}
}
class SongResource: ResourceProtocol {
func snippet() {
//return snippet
}
func title() {
//return title
}
func image() {
//return image
}
func url() {
//return url
}
}
class AlbumResource: ResourceProtocol {
func snippet() {
//return snippet
}
func title() {
//return title
}
func image() {
//return image
}
func url() {
//return url
}
}
protocol IViewProtocol {
var resource: ResourceProtocol { get set }
func show()
}
class TableViewCellView: IViewProtocol {
var resource: ResourceProtocol
init(source: ResourceProtocol) {
resource = source
}
func show() {
//show snippet
//show title
//show url
//show image
}
}
class ListView {
var dataSource: [ResourceProtocol]?
func fetchData() {
//return data source
}
//show data in cell from UITableViewDataSource functions
}
class DetailView: IViewProtocol {
var resource: ResourceProtocol
init(source: ResourceProtocol) {
resource = source
}
func show() {
//show detail
//show title
//show url
//show image
}
}
You can customize this code, i have just given a prototype, how it should be implemented.

UISearchController Filter Swift

I have a UITableViewCell with 3 subviews that I would like to filter when I search, as seen here:
1.The image view
2.The name label (black text)
3.The street name label (blue text)
This is what I've done so far, I've only managed to understand how to filter 1 array which is the name:
MainTableView.swift
var FilteredNames = [String]
func updateSearchResultsForSearchController(searchController:UISearchController) {
// Filter Names
self.filteredNames = self.names.filter { (name:String) -> Bool in
if name.lowercaseString.containsString(self.searchController.searchBar.text!.lowercaseString){
return true
} else {
return false
}
}
self.resultsController.tableView.reloadData()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.5
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Search
if tableView == self.tableView {
self.streets.count
return self.names.count
} else {
self.filteredStreets.count
return self.filteredNames.count
}
// return names.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = self.tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
if tableView == self.tableView {
cell.photo.image = self.images[indexPath.row]
cell.name.text = names[indexPath.row]
cell.streetName.text = streets[indexPath.row]
} else {
cell.photo.image = self.images[indexPath.row]
cell.name.text = self.filteredNames[indexPath.row]
cell.streetName.text = self.streets[indexPath.row]
}
return cell
}
As of right now, when I search, the image view & the street label are not synchronized with the name. I want to filter all 3 subviews to synchronize correctly. How can I achieve this?
I understand i need to use a struct and filter all 3 with one object but i'v encountered some difficulties managing to do that any help will be appreciated thank you !
Edit:here is my code right now:
override func viewDidLoad() {
super.viewDidLoad()
var searchController : UISearchController!
var resultsController = UITableViewController()
// //
definesPresentationContext = true
self.resultsController.tableView.dataSource = self
self.resultsController.tableView.delegate = self
self.searchController = UISearchController(searchResultsController: self.resultsController)
self.tableView.tableHeaderView = self.searchController.searchBar
self.searchController.searchResultsUpdater = self
self.searchController.dimsBackgroundDuringPresentation = true
self.searchController.searchBar.sizeToFit()
self.searchController.searchBar.barTintColor = UIColor.blackColor()
self.searchController.searchBar.endEditing(true)
self.searchController.searchBar.placeholder = "חפש ברים"
allUsers = createUsers(names: names, streets: streets, images: images)
filteredUsers = allUsers
}
var allUsers: [User]!
var filteredUsers: [User]!
func createUsers(names names: [String], streets: [String], images: [UIImage?]) -> [User] {
var users = [User]()
guard names.count == streets.count && names.count == images.count else { return users }
for (index, name) in names.enumerate() {
let user = User(name: name, streetName: streets[index], image: images[index])
users.append(user)
}
return users
}
//MARK : Search !
func updateSearchResultsForSearchController(searchController:UISearchController) {
if let searchText = searchController.searchBar.text?.lowercaseString {
if searchText.characters.count == 0 {
filteredUsers = allUsers
}
else {
filteredUsers = allUsers.filter {
return $0.name.lowercaseString.containsString(searchText) ||
$0.streetName.lowercaseString.containsString(searchText)
}
}
}
self.resultsController.tableView.reloadData()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return 100.5;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
////////
////////
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredUsers.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = self.tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
let user = filteredUsers[indexPath.row]
cell.photo.image = user.image
cell.name.text = user.name
cell.streetName.text = user.streetName
return cell
}
I would strongly recommend to have only one dataSource instead of separate arrays for names, street names and images. In the following code filteredUsers is always used as dataSource and allUsers is just a stored full array, which is used for updating filteredUsers every time a new text is entered in search bar.
Create a model:
struct User {
var name: String
var streetName: String
var image: UIImage?
}
ViewController:
var allUsers: [User]!
var filteredUsers: [User]!
override func viewDidLoad() {
super.viewDidLoad()
//assuming you already have three arrays with the same amount of elements in each:
allUsers = createUsers(names: names, streets: streets, images: images)
filteredUsers = allUsers
}
func createUsers(names names: [String], streets: [String], images: [UIImage?]) -> [User] {
var users = [User]()
guard names.count == streets.count && names.count == images.count else { return users }
for (index, name) in names.enumerate() {
let user = User(name: name, streetName: streets[index], image: images[index])
users.append(user)
}
return users
}
func updateSearchResultsForSearchController(searchController:UISearchController) {
if let searchText = searchController.searchBar.text?.lowercaseString {
if searchText.characters.count == 0 {
filteredUsers = allUsers
}
else {
filteredUsers = allUsers.filter {
return $0.name.lowercaseString.containsString(searchText) ||
$0.streetName.lowercaseString.containsString(searchText)
} ?? []
}
}
self.resultsController.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredUsers ? filteredUsers.count : 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = self.tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
let user = filteredUsers[indexPath.row]
cell.photo.image = user.image
cell.name.text = user.name
cell.streetName.text = user.streetName
return cell
}

How to return multiple values in swift

I'm starter in swift.
I create tableview and get data from jsonFile to show text and picture.
Then I want to add searchBar on tableview but have problem.
import UIKit
class EpisodesTableViewController: UITableViewController
{
var episodes = [Episode]()
var names = [Episode]()
let searchController = UISearchController(searchResultsController: nil)
var filteredNames = [Episode]()
func filterContentForSearchText(searchText: String) {
filteredNames = self.names.filter { name in
return name.title!.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
override func viewDidLoad()
{
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
tableView.setContentOffset(CGPoint(x: 0, y: searchController.searchBar.frame.size.height), animated: false)
tableView.estimatedRowHeight = tableView.rowHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.separatorStyle = .None
self.episodes = Episode.downloadAllEpisodes()
self.tableView.reloadData()
}
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searchController.active && searchController.searchBar.text != ""{
return filteredNames.count
}else{
return names.count
}
return episodes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("Episode Cell", forIndexPath: indexPath) as! EpisodeTableViewCell
let episode = self.episodes[indexPath.row]
let data: Episode
if searchController.active && searchController.searchBar.text != "" {
data = filteredNames[indexPath.row]
}
else {
data = names[indexPath.row]
}
let titleName = data.title!
cell.episode = episode
cell.textLabel?.text = titleName
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendData"{
if let detailPage = segue.destinationViewController as? detailEpisodeViewController {
if let indexpath = tableView.indexPathForSelectedRow {
let episode = episodes[indexpath.row]
detailPage.episode = episode
}
}
}
}
}
extension EpisodesTableViewController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
this my code.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if searchController.active && searchController.searchBar.text != ""{
return filteredNames.count
}else{
return names.count
}
return episodes.count
}
When I return filteredNames and names interface just show seachbar. If I return filtered names and episodes show error index out of range.
I don't know How to fix that.
If you want to return two values just return a touple like so:
return (DataType, DataType)
so this could be
func returnTouple() -> (String, AnyObject) {
return ("Hello World", 1)
}
then you would access it like so:
let (myString, myObject) = returnTouple()
and myString == "Hello World"
You could also access both throught .0 and .1 like returnTouple().0 == "Hello World"
Next,
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active && searchController.searchBar.text != "" {
return filteredNames.count
} else {
return names.count
}
return episodes.count
}
This function shouldn't work. You have an if {} else {} with a return statement in both sections. Unless you said if {} else if {} this makes the thrid return statement impossible to hit so it shouldn't be there.

Adding Searchbar to table view returns unwrapping nil view

Im attempting to add a search bar to a table view that displays names. Im getting a unwrapping nil error when i try to add it as a subheader to my tableview. Im not sure why. The point where the error occurs is marked.
import UIKit
class WebpageController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchResultsUpdating {
var names: [String] = [String]()
var school: String = String()
var index: Int = Int()
var searchResults: [String] = [String]()
var searchController = UISearchController(searchResultsController: nil)
#IBOutlet weak var tables: UITableView!
var refreshControl: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.sizeToFit()
definesPresentationContext = true
tables.tableHeaderView = searchController.searchBar //returns error
names = HtmlController.loadData() as NSArray as! [String]
clean()
refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(WebpageController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
tables.addSubview(refreshControl)
// Do any additional setup after loading the view.
}
func refresh(sender: AnyObject)
{
names = HtmlController.loadData() as NSArray as! [String]
clean()
tables.reloadData()
refreshControl.endRefreshing()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = searchController.searchBar.text
filterContentForSearchText(searchText!)
tables.reloadData()
}
func filterContentForSearchText(searchText: String)
{
if(searchText == "")
{
searchResults = names
}
else{
searchResults = names.filter({ ( a: String) -> Bool in
let nameMatch = a.rangeOfString(searchText, options:
NSStringCompareOptions.CaseInsensitiveSearch)
return nameMatch != nil
})
}
}
#IBOutlet weak var Table: UITableView!
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func clean()
{
var length = names.count
var i = 0;
var bool = false
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.objectForKey("School") == nil
{
school = "11Staff"
defaults.setObject("11", forKey: "School")
}
else{
school = (defaults.objectForKey("School") as! String) + "Staff"
}
var extra: [String] = [String]()
let bundleidentifier = NSBundle.mainBundle().bundleIdentifier
if let aStreamReader = StreamReader(path: NSBundle.mainBundle().pathForResource(school, ofType: "txt")!)
{
defer {
aStreamReader.close()
}
while let line = aStreamReader.nextLine() {
extra.append(line)
}
}
for String in extra
{
while i < length && bool == false
{
if((String.rangeOfString(names[i].uppercaseString)) != nil)
{
names.removeAtIndex(i)
i -= 1
bool = true
length = names.count
}
i+=1;
}
bool = false
i = 0;
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if searchController.active
{
return searchResults.count
}
else{
return names.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell
if let reusedCell = tableView.dequeueReusableCellWithIdentifier("Cell") {
cell = reusedCell
} else {
cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
}
if let label = cell.textLabel {
label.text = names[indexPath.row + 1].uppercaseString
}
return cell }
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
index = indexPath.row
performSegueWithIdentifier("WebTransfer", sender:self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let Destination: WebsiteController = segue.destinationViewController as! WebsiteController
Destination.index = index
}
}

check / uncheck the check box by tapping the cell in table view and how to know which cell has checked or unchecked

i am new to ios swift 2.2 . I tried to create one table view with custom cell xib. and i am populating some data in my table view. And also i added one custom check box button. And i am creating separate class for that check box button. Now when i click only on my button in my customcell.xib .But when i tap on my cell, my check box are not changing. i need to have both. When i click on my button it should change to check and uncheck image. When i tap on my cell also i need to change my button to check or uncheck image
And when i scroll down and again come back to top, my checked images are automatically chnaged to normalcheck box.
i need to do some action , so for that. When i tap on any cell my check box should check and uncheck.And alos i need to know which row cell has checked image . So that i can perform some action for my checked image row cell alone.
here is my custom check box class:
import UIKit
class CheckBoxButton: UIButton {
// Images
let checkedImage = UIImage(named: "CheckBoxChecked")! as UIImage
let uncheckedImage = UIImage(named: "CheckBoxUnChecked")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(checkedImage, forState: .Normal)
} else {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: #selector(CheckBoxButton.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}
Cutom cell.xib class:
import UIKit
class FavCell: UITableViewCell {
#IBOutlet weak var FLabel1: UILabel!
#IBOutlet weak var FLabel2: UILabel!
#IBOutlet weak var checkbox: CheckBoxButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func checkboxpress(sender: AnyObject) {
}
}
my viewcontroller.swift
import UIKit
class FavVC: UIViewController {
#IBOutlet weak var FavTableView: UITableView!
//var FData = [FavouritesData]()
var arrDict :NSMutableArray=[]
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
override func viewDidLoad() {
super.viewDidLoad()
self.jsonParsingFromURL()
let nib = UINib(nibName:"FavCell", bundle: nil)
FavTableView.registerNib(nib, forCellReuseIdentifier: "FCell")
}
// web services method
func jsonParsingFromURL ()
{
// let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as? String
let url = NSURL(string: "som url")
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// print("done, error: \(error)")
if error == nil
{
dispatch_async(dispatch_get_main_queue())
{
self.arrDict=(try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSMutableArray
if (self.arrDict.count>0)
{
self.FavTableView.reloadData()
}
}
}
}
dataTask.resume()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
// return self.FData.count
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// height for each cell
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return cellSpacingHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
please help me out.
Thanks in advance
In order to solve your issue, as #El Capitan mentioned, you will need to use the didSelectRowAtIndexPath method to change its states. Your codes should look something along the lines of this:
// Declare a variable which stores checked rows. UITableViewCell gets dequeued and restored as you scroll up and down, so it is best to store a reference of rows which has been checked
var rowsWhichAreChecked = [NSIndexPath]()
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell:FavCell = tableView.cellForRowAtIndexPath(indexPath) as! FavCell
// cross checking for checked rows
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkBox.isChecked = true
rowsWhichAreChecked.append(indexPath)
}else{
cell.checkBox.isChecked = false
// remove the indexPath from rowsWhichAreCheckedArray
if let checkedItemIndex = rowsWhichAreChecked.indexOf(indexPath){
rowsWhichAreChecked.removeAtIndex(checkedItemIndex)
}
}
}
To redisplay cells which have been checked before after scrolling the rows out of view, at your cellForRowAtIndexPath, perform the same checking against rowsWhichAreChecked array and set its states accordingly.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkBox.isChecked = true
}else{
cell.checkBox.isChecked = false
}
}
return cell
}
EDITED ANSWER
I have got your code to work but I had to make some modifications to your Checkbox class and ViewController
Checkbox.swift
class CheckBoxButton: UIButton {
// Images
let checkedImage = UIImage(named: "CheckBoxChecked")! as UIImage
let uncheckedImage = UIImage(named: "CheckBoxUnChecked")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(uncheckedImage, forState: .Normal)
} else {
self.setImage(checkedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.userInteractionEnabled = false
// self.addTarget(self, action: #selector(CheckBoxButton.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
// self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isChecked == true {
isChecked = false
} else {
isChecked = true
}
}
}
}
ViewController.swift
class FavVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var FavTableView: UITableView!
var rowsWhichAreChecked = [NSIndexPath]()
//var FData = [FavouritesData]()
var arrDict :NSMutableArray=[]
let cellSpacingHeight: CGFloat = 5 // cell spacing from each cell in table view
override func viewDidLoad() {
self.FavTableView.delegate = self
self.FavTableView.dataSource = self
super.viewDidLoad()
self.jsonParsingFromURL()
let nib = UINib(nibName:"FavCell", bundle: nil)
FavTableView.registerNib(nib, forCellReuseIdentifier: "FCell")
}
// web services method
func jsonParsingFromURL ()
{
// let token = NSUserDefaults.standardUserDefaults().valueForKey("access_token") as? String
let url = NSURL(string: "some url")
let session = NSURLSession.sharedSession()
let request = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// print("done, error: \(error)")
if error == nil
{
dispatch_async(dispatch_get_main_queue())
{
self.arrDict=(try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSMutableArray
if (self.arrDict.count>0)
{
self.FavTableView.reloadData()
}
}
}
}
dataTask.resume()
//
// let StringUrl = "http"+token!
// let url:NSURL = NSURL(string: StringUrl)!
// if let JSONData = NSData(contentsOfURL: url)
// {
// if let json = (try? NSJSONSerialization.JSONObjectWithData(JSONData, options: [])) as? NSDictionary
// {
// for values in json
// {
// self.FData.append()
// }
// if let reposArray = json["data"] as? [NSDictionary]
// {
//
// for item in reposArray
// {
// let itemObj = item as? Dictionary<String,AnyObject>
//
// let b_type = itemObj!["business_type"]?.valueForKey("type")
//
// //self.Resultcount.text = "\(b_type?.count) Results"
//
// if (b_type as? String == "Taxis")
// {
//
// self.FData.append(FavouritesData(json:item))
//
// }
// }
// }
// }
// }
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
// return self.FData.count
return self.arrDict.count
}
// number of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
// height for each cell
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return cellSpacingHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:FavCell = self.FavTableView.dequeueReusableCellWithIdentifier("FCell") as! FavCell
cell.FLabel1.text=arrDict[indexPath.section] .valueForKey("favourite_name") as? String
cell.FLabel2.text=arrDict[indexPath.section] .valueForKey("favourite_address") as? String
let isRowChecked = rowsWhichAreChecked.contains(indexPath)
if(isRowChecked == true)
{
cell.checkbox.isChecked = true
cell.checkbox.buttonClicked(cell.checkbox)
}else{
cell.checkbox.isChecked = false
cell.checkbox.buttonClicked(cell.checkbox)
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell:FavCell = tableView.cellForRowAtIndexPath(indexPath) as! FavCell
// cross checking for checked rows
if(rowsWhichAreChecked.contains(indexPath) == false){
cell.checkbox.isChecked = true
cell.checkbox.buttonClicked(cell.checkbox)
rowsWhichAreChecked.append(indexPath)
}else{
cell.checkbox.isChecked = false
cell.checkbox.buttonClicked(cell.checkbox)
// remove the indexPath from rowsWhichAreCheckedArray
if let checkedItemIndex = rowsWhichAreChecked.indexOf(indexPath){
rowsWhichAreChecked.removeAtIndex(checkedItemIndex)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
{
let cell:FilterTableViewCell = tableView.dequeueReusableCell(withIdentifier: "filtercell", for: indexPath) as! FilterTableViewCell
// Configure the cell...
cell.lblCategory?.attributedText = FontAttributes.sharedInstance.AttributesString(message: self.filterArray[indexPath.row], color: Textcolor)
cell.BtnIndex?.addTarget(self, action: #selector(checkMarkTapped(_ :)), for: .touchUpInside)
cell.BtnIndex?.tag = indexPath.row
let rowid = indexPath.row
let found = rowsWhichAreChecked.filter{$0.rowId == rowid}.count > 0
if found
{
cell.BtnIndex?.setImage(checkedImage, for: .normal)
}
else
{
cell.BtnIndex?.setImage(uncheckedImage, for: .normal)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell:FilterTableViewCell = tableView.cellForRow(at: indexPath) as! FilterTableViewCell
let rowid = indexPath.row
let found = rowsWhichAreChecked.filter{$0.rowId == rowid}.count > 0
if found
{
tempArrayFordelete = rowsWhichAreChecked
for obj in tempArrayFordelete
{
if let index = rowsWhichAreChecked.index(where: { $0.rowId == obj.rowId }) {
// removing item
rowsWhichAreChecked.remove(at: index)
cell.BtnIndex?.setImage(uncheckedImage, for: .normal)
}
}
}
else
{
cell.BtnIndex?.setImage(checkedImage, for: .normal)
let objrowId = selectedIndex(rowId: indexPath.row)
rowsWhichAreChecked.append(objrowId)
}
}
It gives me a great pleasure to inform you all that solve above issue
Resolving Issue Is
CheckBox Functionality
RadioButton Functionality
ReuseCell(tableView.dequeueReusableCell)//Also solve selected cell position issue.
Tested Code
Swift 5
iOS 12.2
Here is my code
import UIKit
class countrySelection:UITableViewCell{
#IBOutlet weak var imgFlag: UIImageView!
#IBOutlet weak var lblCountryName: UILabel!
#IBOutlet weak var btnSelection: UIButton!
}
class ViewController: UIViewController {
var listingDict=[[String:String]]()
var radioOption:Int?// Only used :: if u are 2. RadioButton Functionality implement
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource=self
tableView.delegate=self
fillCountryData()
// Do any additional setup after loading the view.
}
func fillCountryData(){
self.fillJsonData(imgName: "india_flag", countryName: "India")
self.fillJsonData(imgName: "pakistan_flag", countryName: "Pakistan")
self.fillJsonData(imgName: "israel_flag", countryName: "Israel")
self.fillJsonData(imgName: "albania_flag", countryName: "Albania")
self.fillJsonData(imgName: "america_flag", countryName: "America")
self.fillJsonData(imgName: "belize_flag", countryName: "Belize")
self.fillJsonData(imgName: "brunei_flag", countryName: "Brunei")
self.fillJsonData(imgName: "comoros_flag", countryName: "Comoros")
self.fillJsonData(imgName: "congo_flag", countryName: "Congo")
self.fillJsonData(imgName: "ecuador_flag", countryName: "Ecuador")
self.fillJsonData(imgName: "haiti_flag", countryName: "Haiti")
self.fillJsonData(imgName: "jamaica_flag", countryName: "Jamaica")
self.fillJsonData(imgName: "kenya_flag", countryName: "Kenya")
self.fillJsonData(imgName: "mali_flag", countryName: "Mali")
self.tableView.reloadData()
}
func fillJsonData(imgName:String,countryName:String){
var dictData=[String:String]()
dictData["img"]=imgName
dictData["country"]=countryName
dictData["check"]="false"
listingDict.append(dictData)
}
}
extension ViewController:UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listingDict.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=tableView.dequeueReusableCell(withIdentifier: "countrySelection") as! countrySelection
let dictVal=listingDict[indexPath.row]
cell.lblCountryName.text=dictVal["country"]
cell.imgFlag.image=UIImage(named:dictVal["img"]!)
/*//Check Box Functionality
if dictVal["check"] == "false"{
cell.btnSelection.setImage(UIImage(named: "checkbox_UnSelect"), for: .normal)
} else{
cell.btnSelection.setImage(UIImage(named: "checkbox_Select"), for: .normal)
}*/
//RadioButton Functionality
if radioOption==indexPath.row{
listingDict[indexPath.row]["check"]="true"
cell.btnSelection.setImage(UIImage(named: "radioButton_Select"), for: .normal)
} else{
listingDict[indexPath.row]["check"]="false"
cell.btnSelection.setImage(UIImage(named: "radioButton_UnSelect"), for: .normal)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/*//CheckBox Functionality
if listingDict[indexPath.row]["check"]=="true"{
listingDict[indexPath.row]["check"]="false"
} else{
listingDict[indexPath.row]["check"]="true"
}*/
//RadioButton Functionality
print("RadioButton",listingDict)
if listingDict[indexPath.row]["check"]=="true"{
radioOption=nil
} else{
radioOption=indexPath.row
}
self.tableView.reloadData()
}
}

Resources