Single table view through two different NSFetchedResultsControllers with sections - ios

Good morning to everyone. I am using Swift 3.1.1, Xcode 8.3.2. I need to connect a single Table View to two different tables (entities) in Core Data through two different NSFetchedResultsControllers. I have created two NSFetchedResultsControllers, and even fetched data from table but I faced problem how to tell Table View that first controller should response for section one and second controller should be responsible for section two.
I can show you the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tv: UITableView!
#IBAction func pressed(_ sender: UIButton) {
ModelA.read { table1 in
ModelB.read { table2 in
if table1.isEmpty {
ModelA.save(recordToSave: [(a: 1, b: "a"), (a: 2, b: "b"), (a: 3, b: "c")]) {
ModelB.save(recordToSave: [(a: 4, b: 5.0, c: true), (a: 6, b: 7.0, c: false)]) {
self.tvReload()
}
}
} else {
self.tvReload()
}
}
}
}
var fetchedResultsControllerForModelA = CoreDataFunctions.fetchedResultsController
var fetchedResultsControllerForModelB = CoreDataFunctions.fetchedResultsController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tvReload()
}
func modelOfTableA(indexPath: IndexPath) -> (forLabel1: String, forLabel2: String, forLabel3: String)? {
if let fetchedResultsControllerForModelA = CoreDataFunctions.getNSManagedObjectForIndexPathOfTable(fetchedResultsController: fetchedResultsControllerForModelA, indexPath: indexPath) {
if let model = ModelA.read(nsmanagedobject: fetchedResultsControllerForModelA) {
return (forLabel1: "\(model.a)", forLabel2: model.b, forLabel3: "")
}
}
return nil
}
func modelOfTableB(indexPath: IndexPath) -> (forLabel1: String, forLabel2: String, forLabel3: String)? {
if let fetchedResultsControllerForModelB = CoreDataFunctions.getNSManagedObjectForIndexPathOfTable(fetchedResultsController: fetchedResultsControllerForModelB, indexPath: indexPath) {
if let model = ModelB.read(nsmanagedobject: fetchedResultsControllerForModelB) {
return (forLabel1: "\(model.a)", forLabel2: "\(model.b)", forLabel3: "\(model.c)")
}
}
return nil
}
func tvReload() {
fetchedResultsControllerForModelA = CoreDataFunctions(tableName: .a).fetchedResultsController(keyForSort: ModelA.a.rawValue, searchParameters: nil)
fetchedResultsControllerForModelB = CoreDataFunctions(tableName: .b).fetchedResultsController(keyForSort: ModelB.a.rawValue, searchParameters: nil)
do {
try fetchedResultsControllerForModelA?.performFetch()
try fetchedResultsControllerForModelB?.performFetch()
DispatchQueue.main.async {
self.tv.reloadData()
}
} catch {
print("Error")
}
}
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections1 = fetchedResultsControllerForModelA?.sections {
if let sections2 = fetchedResultsControllerForModelB?.sections {
return sections1[section].numberOfObjects + sections2[section].numberOfObjects
}
return sections1[section].numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
if indexPath.section == 0 {
if let modelOfTable = modelOfTableA(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
} else {
if let modelOfTable = modelOfTableB(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
}
return cell
}
}
I could not find any tutorial on this theme, so I am asking question there. I do not want to use inheritance from single entity in Core Data, because, in real life it would be impossible.
Thank you for any help or advice!

OK - I downloaded your code, and there are a couple issues...
1) If you want your data to fill two sections in the table, the table must have two sections - currently, you are just returning 1, so use this (although you may want/need different handling based on data retrieved):
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
if fetchedResultsControllerForModelA == nil || fetchedResultsControllerForModelB == nil {
return 0
}
return 2
}
2) For number of rows in each section, your code was close but not quite right...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if let sections = fetchedResultsControllerForModelA?.sections {
return sections[0].numberOfObjects
}
} else {
if let sections = fetchedResultsControllerForModelB?.sections {
return sections[0].numberOfObjects
}
}
return 0
}
3) For the actual cellForRowAtIndexPath data, again your code was close but you need to keep track of which data to get for each section...
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
if indexPath.section == 0 {
if let modelOfTable = modelOfTableA(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
} else {
// Each "Table data model" has only one section... since this is the 2nd table section, we need
// to change the section of the index path for our call to the data model
let dataIndexPath = IndexPath(row: indexPath.row, section: 0)
if let modelOfTable = modelOfTableB(indexPath: dataIndexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
}
return cell
}
That should get you on your way.

Related

TableView cells becomes inactive

I am using a tableView to take some surveys.
Header I use for a question. Footer for «back» and «next» buttons. And tableView cells for answer options.
Now I started to have a problem, with some user interaction: when you simultaneously click on the “next” button and select an answer, the answer options cease to be active, nothing can be selected. Although the buttons remain active.
Tell me in what direction to look for the problem and how you can debug this problem in order to understand what's wrong.
It all started after fixing bugs, when the application crashed when simultaneously (or almost) pressing the "next" button and choosing an answer. Because the didSelectRowAt method worked after I changed the current array of answer options, and the selected index in the previous question turned out to be larger than the size of the array with the answers to the new question.
class AssessmentVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var footer: FooterTableView?
var header: UIView?
var arrayAssessmnet = [AssessmentDM]()
var assessment: AssessmentDM!
var question: QuestionDM!
var viewSeperationHeader = UIView()
var arrayOptions: [Option]?
var countAssessment = 0
var numberAssessment = 0
var numberQuestion = 0
var countQuestion = 0
var numberQusttionForLabel = 1
var arrayQuestion = [QuestionDM]()
var arrayAnswers = [AnswerDM]()
var arrayEvents = [EventDM]()
override func viewDidLoad() {
super.viewDidLoad()
settingAssessment()
}
//MARK: - settingAssessment()
private func settingAssessment() {
let id = self.assessment.serverId
arrayQuestion = QuestionDM.getQuestions(id: id)
assessmentName.text = assessment.name
countQuestion = arrayQuestion.count
let day = self.assessment.day
arrayAnswers = AnswerDM.getAnswers(idAssessment: id, day: day)
settingQuestion(eventType: .start)
}
//MARK: - settingQuestion()
private func settingQuestion(eventType: EventType? = nil) {
let prevQuestion = question
question = arrayQuestion[numberQuestion]
timeQuestion = 0
footer!.grayNextButton()
//first question
if numberQuestion == 0 && numberAssessment == 0 {
footer!.previousButton.isHidden = true
} else {
footer!.previousButton.isHidden = false
}
arrayOptions = [Option]()
let sortOption = question.options!.sorted {$0.numberOption < $1.numberOption}
for option in sortOption {
arrayOptions?.append(Option(label: option.label, value: option.value))
}
tableView.rowHeight = UITableView.automaticDimension
tableView.reloadData()
heightTableView()
tableView.setContentOffset(.zero, animated: false)
}
//MARK: - heightTableView()
func heightTableView() {
}
//MARK: - UITableViewDataSource
extension AssessmentVC: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewSeperationHeader.isHidden = false
footer?.viewSeperationFooter.isHidden = false
tableView.separatorStyle = .singleLine
return question.options?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath as IndexPath) as AnswerAssessmentCell
cell.initCell(text: arrayOptions![indexPath.row].label, value: arrayOptions![indexPath.row].value, arrayValue: arrayAnswers[numberQuestion].response, isCheckbox: true)
return cell
}
}
//MARK: - UITableViewDelegate
extension AssessmentVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
if question.answerType == "Radio" || question.answerType == "Checkbox"{
selectRadioOrChekbox(indexPath: indexPath)
}
}
}
//MARK: - selectRadioOrChekbox
extension AssessmentVC {
private func selectRadioOrChekbox(indexPath: IndexPath) {
if question.answerType == "Radio" {
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
for cell in cells {
cell.select = false
cell.isSelected = false
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = true
cell.isSelected = true
if arrayOptions?.count ?? 0 > indexPath.row {
arrayAnswers[numberQuestion].response = arrayOptions![indexPath.row].value
footer?.greenNextButton()
}
}
if question.answerType == "Checkbox" {
if arrayOptions?.count ?? 0 > indexPath.row {
//если нажато что-то, что должно сбросить "None"
// question.options![0].isSelect = false
let cells = tableView.visibleCells as! Array<AnswerAssessmentCell>
if cells[0].answerLabel.text == "None" {
cells[0].select = false
cells[0].isSelected = false
}
var array = arrayAnswers[numberQuestion].response?.components(separatedBy: ";")
array?.removeAll { $0 == "0"}
if array?.count == 0 {
arrayAnswers[numberQuestion].response = nil
} else {
arrayAnswers[numberQuestion].response = array?.joined(separator: ";")
}
let cell = tableView.cellForRow(at: indexPath) as! AnswerAssessmentCell
cell.select = !cell.select
cell.isSelected = cell.select
arrayAnswers[numberQuestion].response = array.joined(separator: ";")
if array.count == 0 {
arrayAnswers[numberQuestion].response = nil
footer?.grayNextButton()
} else {
footer?.greenNextButton()
}
}
}
}
}
//MARK: - Navigation between questions
extension AssessmentVC {
func nextQuestion() {
footer!.grayNextButton()
numberQuestion += 1
numberQusttionForLabel += 1
settingQuestion(eventType: .next)
} else {
}
func previousQuestion() {
numberQusttionForLabel -= 1
settingQuestion(eventType: .previous)
}
}
Some snippets that can help you :
// Answer type : use enum . Here the Strong/Codable is if you want to
// save using JSON encoding/decoding
enum AnswerType: String, Codable {
case checkBox = "CheckBox"
case radio = "Radio"
}
Setup of your cell :
class AnswerAssessmentCell: UITableViewCell {
...
// work with Option type
func initCell(option: Option, response: String?, answerType: AnswerType) {
// setup cell contents (labels)
// check for selected status
switch answerType {
case .checkBox:
// check if option is in response
// set isSelected according
break
case .radio:
// check if option is response
// set isSelected according
break
}
}
}
In table view data source :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AnswerAssessmentCell
// Use the option to init the cell
// this will also set the selected state
let optionNumber = indexPath.row
cell.initCell(option: arrayOptions![optionNumber], response: arrayAnswers[numberQuestion].response, answerType: question.answerType)
return cell
}
In Table view delegate :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isChangAnswerInAssessment = true
let optionNumber = indexPath.row
switch question.answerType {
case .radio:
selectRadio(optionNumber: optionNumber)
case .checkBox:
selectCheckBox(optionNumber: optionNumber)
}
// Reload tableview to show changes
tableView.reloadData()
}
// Separate in 2 function for smaller functions
// in this function work only with model data, the reload data will do
// cell update
// only the footer view button cooler may need to be changed
private func selectRadio(optionNumber: Int) {
// Reset current response
// set response to optionNumber
// update footer button cooler if necessary
}
private func selectCheckBox(optionNumber: Int) {
// if option is in response
// remove option from response
// else
// add response to option
// update footer button cooler if necessary
}
Hope this can help you

How to remove row in UITableViewController presented as popover

I have a custom UITableViewController that I present as a popover in my app. In some of the cells there is a delete button (trash can) to remove that item. Everything works as it should except that I the UI is not update when pressing the delete button. That is, the data is cleared and I call self.tableView.reloadData(), but the cell remains visible in the UI. (Pressing the delete button again makes the app crash in my C++ code because of an assert). I have no storyboard or xib as I do not need it. I only want this to be in code.
What am I missing? It might be something simple, but I cannot fathom why. I have tried:
Separate data source implementation.
Calling reloadData() both sync and async.
Setting delegate to self.
Various other hacks.
Here is the UITableViewController implementation:
import Foundation
class IngredientInfoPopoverViewController : UITableViewController
{
var slViewController: ShoppingListViewController?;
var ingredientName: String = "Ingrediens";
#IBOutlet var uniqueIngredients: [Ingredient] = []; // Unique per *recipe* so that we can list all the recipes for the ingredients
var clickedCellIndexPath: IndexPath? = nil;
enum SECTIONS : Int
{
case HEADER = 0;
case RECIPE = 1;
}
static let ROW_HEIGHT = 44;
override func viewDidLoad()
{
super.viewDidLoad();
tableView.register(UINib(nibName: "OpenIngredientInfoCell", bundle: nil), forCellReuseIdentifier: "OpenIngredientInfoCell");
tableView.register(UINib(nibName: "OpenRecipeCell", bundle: nil), forCellReuseIdentifier: "OpenRecipeCell");
tableView.separatorStyle = .singleLine;
tableView.bounces = false; // "Static" table view
updateSize();
}
func updateSize()
{
let totalCount = min(uniqueIngredients.count + 1, 6); // + 1: header row. min: Allow max 5 recipes in list (enables scrolling)
self.preferredContentSize = CGSize(width: 300, height: totalCount * IngredientInfoPopoverViewController.ROW_HEIGHT);
}
func setup(slvc: ShoppingListViewController?, ingredients: [Ingredient], clickedCellIndexPath: IndexPath)
{
self.slViewController = slvc;
self.clickedCellIndexPath = clickedCellIndexPath;
if (ingredients.count > 0)
{
let first = ingredients[0];
for i in ingredients
{
assert(i.getId() == first.getId());
}
ingredientName = first.getName();
var uniqueRecipeNames: Set<String> = [];
for i in ingredients
{
uniqueRecipeNames.insert(i.getRecipeName());
}
let sorted = uniqueRecipeNames.sorted();
uniqueIngredients.removeAll();
for s in sorted
{
for i in ingredients
{
if (i.getRecipeName() == s)
{
uniqueIngredients.append(i);
break;
}
}
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int
{
return 2;
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
switch section
{
case SECTIONS.HEADER.rawValue:
return 1;
case SECTIONS.RECIPE.rawValue:
return uniqueIngredients.count;
default:
assert(false);
return 0;
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
switch (indexPath.section)
{
case SECTIONS.HEADER.rawValue:
assert(indexPath.row == 0);
if (uniqueIngredients.count > 0)
{
let ingredient = uniqueIngredients[0]; // All are the same ingredient
self.dismiss(animated: true, completion: nil);
slViewController?.onIngredientInfoButtonClicked(ingredient);
}
break;
case SECTIONS.RECIPE.rawValue:
if (indexPath.row < uniqueIngredients.count)
{
let ingredient = uniqueIngredients[indexPath.row];
self.dismiss(animated: true, completion: nil);
slViewController?.onRecipeInfoButtonClicked(ingredient);
}
break;
default:
break;
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell();
switch (indexPath.section)
{
case SECTIONS.HEADER.rawValue:
if (uniqueIngredients.count > 0)
{
let ingredient = uniqueIngredients[0];
let cell = tableView.dequeueReusableCell(withIdentifier: "OpenIngredientInfoCell", for: indexPath) as! OpenIngredientInfoCell;
cell.setup(ingredient);
}
break;
case SECTIONS.RECIPE.rawValue:
if (indexPath.row < uniqueIngredients.count)
{
cell.selectionStyle = .none; // Without this the cell contents become gray and disappear when long pressing! FML
let ingredient = uniqueIngredients[indexPath.row];
let cell = tableView.dequeueReusableCell(withIdentifier: "OpenRecipeCell", for: indexPath) as! OpenRecipeCell;
cell.setup(self, ingredient, clickedCellIndexPath);
}
break;
default:
break;
}
return cell;
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return CGFloat(IngredientInfoPopoverViewController.ROW_HEIGHT);
}
func ingredientRemoved(_ ingredient: Ingredient)
{
for i in 0..<uniqueIngredients.count
{
if (uniqueIngredients[i].getRecipeId() == ingredient.getRecipeId())
{
uniqueIngredients.remove(at: i);
// let indexPath = IndexPath(row: i, section: SECTIONS.RECIPE.rawValue);
// self.tableView.deleteRows(at: [indexPath], with: .fade);
DispatchQueue.main.async {
self.tableView.reloadData();
}
break;
}
}
if (uniqueIngredients.count == 0)
{
self.dismiss(animated: true, completion: nil);
}
else
{
DispatchQueue.main.async {
self.tableView.reloadData();
}
}
}
}
Here is how I present the IngredientInfoPopoverViewController:
#objc func ingredientInfoClicked(_ sender: UITapGestureRecognizer)
{
let tapLocation = sender.location(in: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: tapLocation)!
let ingredients = CppInterface.shoppingList.getIngredients(UInt(indexPath.section), position: UInt(indexPath.row));
let controller = IngredientInfoPopoverViewController();
controller.setup(slvc: self, ingredients: ingredients!, clickedCellIndexPath: indexPath);
controller.modalPresentationStyle = .popover;
controller.popoverPresentationController!.delegate = self;
self.present(controller, animated: true, completion: {
self.tableView.reloadData();
});
}
Here is how the view controller looks when presented. If I click the trash can on one of the items, the data is cleared, but the cell is not removed from the UI, which is what I am trying to achieve.
I'm actually surprised your tableView shows any data at all. Because you declare cell as a let in cellForRowAt when you do let cell = UITableViewCell();, that makes it immutable, and the first cell (outside of the switch) is the one that should technically get returned. Hence why no data should be displaying. And probably also the reason why your tableView is not updating correctly.
Anyway, you should only declare cell when you're dequeueing it, and you should as much as possible, avoid force-unwrapping of a variable.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == SECTIONS.HEADER.rawValue, let cell = tableView.dequeueReusableCell(withIdentifier: "OpenIngredientInfoCell", for: indexPath) as? OpenIngredientInfoCell {
// not sure this check is necessary, but I'm adding it because it was in your original code
guard uniqueIngredients.count > 0 else { return UITableViewCell() }
let ingredient = uniqueIngredients[0]
cell.setup(ingredient)
return cell
} else if indexPath.section == SECTIONS.RECIPE.rawValue, let cell = tableView.dequeueReusableCell(withIdentifier: "OpenRecipeCell", for: indexPath) as? OpenRecipeCell {
// it shouldn't be possible for the indexPath to ever be greater than the dataSource items count, but I'll keep the check
guard indexPath.row < uniqueIngredients.count else { return UITableViewCell() }
cell.selectionStyle = .none
let ingredient = uniqueIngredients[indexPath.row]
cell.setup(self, ingredient, clickedCellIndexPath)
return cell
}
return UITableViewCell()
}
I've removed the semi-colons as they're not necessary in Swift.
For specifying the table cells' reuse identifiers, using the class names would probably be better. So you would use "\(OpenRecipeCell.self)" instead of "OpenRecipeCell"
If you are using the defaulted way of editing a UITableView (either swiping or entering edit mode), then here's my delegate code that works fine:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedStep = appState.recipe.steps[sourceIndexPath.row]
appState.recipe.steps.remove(at: sourceIndexPath.row)
appState.recipe.steps.insert(movedStep, at: destinationIndexPath.row)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
appState.recipe.steps.remove(at: indexPath.row)
tblSteps.deleteRows(at: [indexPath], with: .automatic)
}
}
Notes:
I manually place this table view in edit mode through a UIBarButtonItem, and a cell can both be moved or deleted.
My data source is in my model, at appState.recipe.steps. The structure doesn't matter, just handling the array.
I set a Notification anytime this array is changed that triggers a reloadData() in this table view.
I don't see either of these delegate methods listed, so I'm posting this answer. If by chance it doesn't help you, I'll gladly delete this.

Swift 3.0 multiple selection with select all cell

I have added data in table view and I have manually added "select all" option to the list at first position, now when the user selects the first option which is 'select all' then the person manually option "Select all" is not selected. Select all, click then work all person or deselect working but signal selection all the person not working "Select all"
I have tried the code below but it's not working so can any one help me to solve this?
var unchecked:Bool = true
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell = ObjTableview.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SelectUserCell
// set the text from the data model
cell.selectionStyle = UITableViewCellSelectionStyle.none
cell.lblStudentName.text = getStudentName[indexPath.row]
if UnAll == "unselect" {
if indexPath.row == 0 {
cell.btnCheckbox.setImage(UIImage(named: "unSelectedItem"), for: .normal)
}
if indexPath.row == Int(selectedNumber) {
cell.btnCheckbox.setImage(UIImage(named: "unSelectedItem"), for: .normal)
}
if indexPath.row == Int(unSelectNumber) {
//var j = "\(i)"
cell.btnCheckbox.setImage(UIImage(named: "selectedItem"), for: .normal)
}
}else
{
if(unchecked){
cell.btnCheckbox.setImage(UIImage(named: "unSelectedItem"), for: .normal)
}
else{
cell.btnCheckbox.setImage(UIImage(named: "selectedItem"), for: .normal)
}
}
return cell
}
var UnAll = ""
var selectedNumber = ""
var unSelectNumber = ""
var checkselect:Bool = true
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
UnAll.removeAll()
selectedNumber.removeAll()
unSelectNumber.removeAll()
if(indexPath.row == 0){
btnCheckBoxClick(sender: UIButton())
}else
{
UnAll = "unselect"
btnCheckBoxClick(sender: UIButton())
if checkselect {
selectedNumber = "\(indexPath.row)"
checkselect = false
}else
{
unSelectNumber = "\(indexPath.row)"
checkselect = true
}
print("the selected index is : \(indexPath.row)")
}
}
#IBAction func btnCheckBoxClick(_ sender: Any) {
if(unchecked){
unchecked = false
}
else{
unchecked = true
}
ObjTableview.reloadData()
}
Create a struct for model data with a Bool property. You can modify this property by cell selection.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var allCharacters:[Character] = []
override func viewDidLoad() {
super.viewDidLoad()
allCharacters = [Character(name: "All"),Character(name: "Luke Skywalker"),Character(name: "Leia Organa"),Character(name: "Advik Shah"),Character(name: "Aarav Modi")]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allCharacters.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil{
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
}
cell?.textLabel?.text = allCharacters[indexPath.row].name
if allCharacters[indexPath.row].isSelected
{
cell?.accessoryType = .checkmark
}
else
{
cell?.accessoryType = .none
}
cell?.selectionStyle = .none
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0
{
allCharacters[indexPath.row].isSelected = !allCharacters[indexPath.row].isSelected
for index in allCharacters.indices
{
allCharacters[index].isSelected = allCharacters[indexPath.row].isSelected
}
}
else
{
allCharacters[indexPath.row].isSelected = !allCharacters[indexPath.row].isSelected
if allCharacters.dropFirst().filter({ $0.isSelected }).count == allCharacters.dropFirst().count
{
allCharacters[0].isSelected = true
}
else
{
allCharacters[0].isSelected = false
}
}
tableView.reloadData()
}
}
struct Character
{
var name:String
// var otherDetails
var isSelected:Bool! = false
init(name:String) {
self.name = name
}
}
Creating Array of Struct objects from array of dictionary
let SubjectArray = json["students"] as! [[String:Any]]
allCharacters = SubjectArray.map({ Character(name: $0["studentName"] as! String) })
allCharacters.insert(Character(name:"All"), at: 0)
I like #Pranil's suggestion of using a separate section for the "All" row, so I have stolen that.
You can use an NSMutableIndexSet for tracking the selected rows. This is simpler than having to create a new struct or array of booleans or something. The only thing you do need to be aware of is if your tableview allows row reordering then the index set needs to be adjusted accordingly.
Here is my implementation. The "all" state is determined by the number of selected rows being equal to the number of rows in the data source array.
I have just used simple table view accessories for the checkmarks, but I am sure you can see how to adopt your image based approach in cellForRow(at:)
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
let names: [String]? = ["Luke Skywalker","Leia Organa","Advik Shah","Aarav Modi"]
var selectedRows = NSMutableIndexSet()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let names = self.names else {
return 0
}
return 0 == section ? 1 : names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
var text: String
var accessory = UITableViewCellAccessoryType.none
if 0 == indexPath.section {
text = "All"
if self.selectedRows.count == self.names!.count {
accessory = .checkmark
}
} else {
text = names![indexPath.row]
if selectedRows.contains(indexPath.row) {
accessory = .checkmark
}
}
cell.textLabel!.text = text
cell.accessoryType = accessory
return cell
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if indexPath.section == 0 {
if self.selectedRows.count == self.names!.count {
self.selectedRows = NSMutableIndexSet()
} else {
self.selectedRows = NSMutableIndexSet(indexesIn: NSRange(location: 0, length: self.names!.count))
}
tableView.reloadData()
} else {
self.selectedRows.contains(indexPath.row) ? self.selectedRows.remove(indexPath.row) : self.selectedRows.add(indexPath.row)
let rows = [IndexPath(row: 0, section: 0), indexPath]
tableView.reloadRows(at: rows, with: .none)
}
return nil
}
}
I think you are using only one section in the table view. I suggest you use two sections in the table view, so that first section will contain only one row (Select All) and the second section will contain other options. When you click on Select All, that is in the first row of the first section you can make all the rows in the second section as selected while reloading the table view.
// MARK: - struct for cell item
struct CellItem {
var name : String
var isSelected:Bool! = false
init(name: String) {
self.name = name
}
}
class ViewController: UITableViewController {
#IBOutlet var viewTable: UITableView!
// Declare a boolean varaible to toggle the checkbox in the first section of table view
var isSelectAllSelected : Bool = false
var cellData: [CellItem] = []
override func viewDidLoad() {
super.viewDidLoad()
cellData = [CellItem(name: "Luke Skywalker"),CellItem(name: "Leia Organa"),CellItem(name: "Advik Shah"),CellItem(name: "Aarav Modi")]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}
else
{
return cellData.count
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
// MARK: - Table view delegates
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = TableCell()
cell.selectionStyle = .none
if indexPath.section == 0 {
cell.textLabel?.text = "Select All"
if isSelectAllSelected{
cell.accessoryType = .checkmark
}
else{
cell.accessoryType = .none
}
}
else
{
cell.textLabel?.text = cellData[indexPath.row].name
if cellData[indexPath.row].isSelected{
cell.accessoryType = .checkmark
}
else{
cell.accessoryType = .none
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0
{
cellData[indexPath.row].isSelected = !cellData[indexPath.row].isSelected
isSelectAllSelected = cellData[indexPath.row].isSelected
for index in cellData.indices
{
cellData[index].isSelected = cellData[indexPath.row].isSelected
}
}
else
{
cellData[indexPath.row].isSelected = !cellData[indexPath.row].isSelected
if cellData.filter({ $0.isSelected }).count == cellData.count
{
isSelectAllSelected = true
}
else
{
isSelectAllSelected = false
}
}
viewTable.reloadData()
} }
Hello u can take cheboxbutton action method inside view controller with addtarget method and assign tag indexpath.row so u can easily get the indexpath. from below code u can get the idea.
class ViewController:UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var ObjTableview: UITableView!
var arrStudent = ["1","2","3","4","5"]
var arrSelectedStudent :[Int] = []
var selectAll:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrStudent.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell = ObjTableview.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SelectUserCell
// set the text from the data model
cell.selectionStyle = UITableViewCellSelectionStyle.none
// cell.lblStudentName.text = getStudentName[indexPath.row]
cell.lblStudentName.text = arrStudent[indexPath.row]
cell.btnCheckbox.tag = indexPath.row
cell.btnCheckbox.addTarget(self, action:#selector(btnCheckBoxClick(sender:)), for: .touchUpInside)
if selectAll {
cell.btnCheckbox.setImage(UIImage(named: "selectedItem"), for: .normal)
}else{
if arrSelectedStudent.contains(indexPath.row){
cell.btnCheckbox.setImage(UIImage(named: "selectedItem"), for: .normal)
}else{
cell.btnCheckbox.setImage(UIImage(named: "unSelectedItem"), for: .normal)
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func btnCheckBoxClick(sender: UIButton) {
if sender.tag == 0{
selectAll = true
}else
{
selectAll = false
if let index = arrSelectedStudent.index(of: sender.tag) {
arrSelectedStudent.remove(at: index)
}else{
arrSelectedStudent.append(sender.tag)
}
}
ObjTableview.reloadData()
}}

Loading an Array to TableView based on Segment-Control

I am relatively new to Swift. I tried to search and google the problem but i can't find any answers. It shouldn't be that hard. Hope you guys can help me out. I‘ve been struggling with this Issue over days now:
I created a Tableview which loads an array of tuples from another .swift file. That is working fine! Now I want the tableview to choose the .swift based on a "segment control". So if the Segment-Control is switched to "A" I want it to show the Array of "PSSCBOOKMac.Swift", for B it would be the Array of "PSSCBOOKWin.swift".
The Action ist written properly, I guess (print-statements are working). But the change of the segment-control doesn't effect the Tableview. My guess: The segment-control doesn't effect the Tableview because it has been loaded before and I can't change the value. How can I achieve that?
Cheers for any answers!
Here is the Code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var PSSCSegmentControl: UISegmentedControl!
//LOAD ARRAY FROM PSSCBOOK.SWIFT
var PSSCBook = PSSCBOOKMac()
#IBAction func PSSCSegmentControlChoose(sender: AnyObject) {
if PSSCSegmentControl.selectedSegmentIndex == 0 {
var PSSCBook = PSSCBOOKMac()
println("im mac")
} else {
var PSSCBook = PSSCBOOKWin()
println("im win")
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return PSSCBook.PSSCTools.count
} else {
return PSSCBook.PSSCFile.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("PSCell", forIndexPath: indexPath) as! UITableViewCell
if indexPath.section == 0 {
let (shortCutTitle,shortCutKey) = PSSCBook.PSSCTools[indexPath.row]
cell.textLabel?.text = shortCutTitle
cell.detailTextLabel?.text = shortCutKey
} else {
let (shortCutTitle,shortCutKey) = PSSCBook.PSSCFile[indexPath.row]
cell.textLabel?.text = shortCutTitle
cell.detailTextLabel?.text = shortCutKey
}
/* var PSIcon = UIImage(named: "PSIcon")
cell.imageView?.image = PSIcon */
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Tools"
} else {
return "File"
}
}
}
When you update the datasource of your table view, it won't magically update itself.
You have to reload the table view for the changes to take place:
#IBAction func PSSCSegmentControlChoose(sender: AnyObject) {
if PSSCSegmentControl.selectedSegmentIndex == 0 {
var PSSCBook = PSSCBOOKMac()
println("im mac")
} else {
var PSSCBook = PSSCBOOKWin()
println("im win")
}
self.tableView.reloadData();
}
I spend the last two days trying to figure out what was wrong with my code. I implemented the suggested reloadData() without having errors. The println Values in the console change.. But the tableview just won't refresh. I really don’t know where else to look, is searched for hours. Could somebody please tell me what type of silly mistake I am doing? Thanks guys!
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
//LOAD ARRAY FROM PSSHORTCUTSBOOK.SWIFT
var PSSCBook = PSShortCutsBook()
#IBOutlet weak var SCOutlet: UISegmentedControl!
#IBOutlet weak var SCtableviewOutlet: UITableView!
#IBAction func SCAction(sender: AnyObject) {
if SCOutlet.selectedSegmentIndex == 1 {
var PSSCBook = PSShortCutsBook()
println("There are \(PSSCBook.shortCutsPS.count) items in this Array")
self.SCtableviewOutlet.reloadData();
} else {
var PSSCBook = PSShortCutsBook2()
println("There are \(PSSCBook.shortCutsPS.count) items in this Array")
self.SCtableviewOutlet.reloadData();
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return PSSCBook.shortCutsPS.count
} else {
return PSSCBook.shortCutsPS2.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("PSCell", forIndexPath: indexPath) as! UITableViewCell
if indexPath.section == 0 {
let (shortCutTitle,shortCutKey) = PSSCBook.shortCutsPS[indexPath.row]
cell.textLabel?.text = shortCutTitle
cell.detailTextLabel?.text = shortCutKey
} else {
let (shortCutTitle,shortCutKey) = PSSCBook.shortCutsPS2[indexPath.row]
cell.textLabel?.text = shortCutTitle
cell.detailTextLabel?.text = shortCutKey
}
/* var PSIcon = UIImage(named: "PSIcon")
cell.imageView?.image = PSIcon */
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Tools"
} else {
return "Help"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

iOS - Swift - Custom TableView - Returning Multiple Prototypes

This is for a custom UITableView. I'd like to return a prototype cell based on what's on the stack, represented by the array MentionItemsStack. Is there anything conceptually wrong with this piece of code? Nothing appears on the custom UITableView.
class MentionsTableViewController: UITableViewController {
var mentions: Tweet? {
didSet {
placeMentionItemsInStack()
}
}
var MentionItemsStack = [String?]()
private struct Storyboard {
static let imagesCellReuseIdentifier = "imagesCell"
static let urlsCellReuseIdentifier = "urlsCell"
static let hashtagsCellResuseIdentifier = "hashtagsCell"
static let usersCellReuseIdentifier = "userMentionsCell"
static let emptyCellReuseIdentifier = "emptyCell"
}
override func viewDidLoad() {
super.viewDidLoad()
}
func placeMentionItemsInStack() {
if !mentions!.userMentions.isEmpty {
MentionItemsStack.append("Users")
}
if !mentions!.hashtags.isEmpty {
MentionItemsStack.append("Hashtags")
}
if !mentions!.urls.isEmpty {
MentionItemsStack.append("URLs")
}
if !mentions!.media.isEmpty {
MentionItemsStack.append("Images")
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return MentionItemsStack.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 // returned 1 for now?
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let emptyCell = tableView.dequeueReusableCellWithIdentifier(Storyboard.emptyCellReuseIdentifier, forIndexPath: indexPath) as EmptyTableViewCell
if !MentionItemsStack.isEmpty {
if let identifier = MentionItemsStack.removeLast() {
switch identifier {
case "Users":
let userMentionCell = tableView.dequeueReusableCellWithIdentifier(Storyboard.usersCellReuseIdentifier, forIndexPath: indexPath) as UsersTableViewCell
userMentionCell.tweet = mentions
return userMentionCell
case "Hashtags":
let hashtagCell = tableView.dequeueReusableCellWithIdentifier(Storyboard.hashtagsCellResuseIdentifier, forIndexPath: indexPath) as HashtagsTableViewCell
hashtagCell.tweet = mentions
return hashtagCell
case "URLs":
let urlCell = tableView.dequeueReusableCellWithIdentifier(Storyboard.urlsCellReuseIdentifier, forIndexPath: indexPath) as URLsTableViewCell
urlCell.tweet = mentions
return urlCell
case "Images":
let imageCell = tableView.dequeueReusableCellWithIdentifier(Storyboard.imagesCellReuseIdentifier, forIndexPath: indexPath) as ImagesTableViewCell
imageCell.tweet = mentions
return imageCell
default: break
}
}
}
return emptyCell // arbitrary return statement
}
}

Resources