I put UITextField inside UITableViewCell and want to make highlight tableViewCell and unselected tableViewCell goes original color if user key-in inside each UITextField. So, I did like that.
func textFieldDidBeginEditing(_ textField: UITextField) {
defaultIndex = textField.tag
dynamicFormTable.reloadData()
}
But problem is Keyboard is not showing when I've added dynamicFormTable.reloadData(). Please let me know how to resolve it. Thanks.
Following code will give good result, to avoid reloads
var cellBGColr = [Int : UIColor]()
var previouselectedRow = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<70 // numberOfRows
{
cellBGColr[i] = UIColor.white
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 70
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "table", for: indexPath) as! TblTableViewCell
cell.backgroundColor = cellBGColr[indexPath.row]
cell.selectionStyle = .none
return cell
}
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
let cellPosition = textView.superview?.convert(CGPoint.zero, to: tblView)
let indPath : IndexPath = tblView.indexPathForRow(at: cellPosition!)!
let cell = tblView.cellForRow(at: indPath) as! TblTableViewCell
var previousSelectedCellRow : Int = -1 // FOR VALIDATION
if previouselectedRow.count == 0 // FIRST EDIT
{
previouselectedRow.append(indPath.row)
}
else
{
previousSelectedCellRow = previouselectedRow[0]
if previousSelectedCellRow == indPath.row // SAME ROW EDITING AGAIN
{
}
else // NEW ROW
{
let previousIndPath : IndexPath = IndexPath(row: previousSelectedCellRow, section: 0)
if (tblView.indexPathsForVisibleRows?.contains(previousIndPath))!
{
let previousCell = tblView.cellForRow(at: previousIndPath) as! TblTableViewCell
previousCell.backgroundColor = UIColor.white
cellBGColr[previousSelectedCellRow] = UIColor.white
}
else
{
cellBGColr[previousSelectedCellRow] = UIColor.white
}
previouselectedRow.remove(at: 0)
previouselectedRow.append(indPath.row)
}
}
cell.backgroundColor = UIColor.red // HERE YOU CAN CHANGE UR CELL COLOR
cellBGColr[indPath.row] = UIColor.red // HERE STORED IN DICT
return true
}
On scrolling your tableview, or somewhere you try to reload, cell background color will not change / reuse.
When reloadData is called it resigns first responder. But you can use beginUpdates/endUpdates methods:
dynamicFormTable.beginUpdates()
dynamicFormTable.reloadRows(at: [IndexPath(row: defaultIndex, section: 0)], with .none)
dynamicFormTable.endUpdates()
Related
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
I am new to Swift and I am using Swift 4.2 . I have a TableView with a label and button . When I press a button I would like to add a new row directly below the row in which the button was clicked . Right now when I click a button the new row gets added to the bottom of the TableView every time. I have been looking at posts on here but haven't been able to get it working this is my code base . I have a method called RowClick I get the indexpath of the row that was clicked but do not know how to use that to get the new row to appear directly below the clicked row .
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
InsertVideoTitle(indexPath: indexPath)
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
This is how my table looks I clicked the Facebook Rows button and it appended the string SnapChat . The Snapchat label should appear in a row below Facebook instead . Any suggestions would be great !
I think the easiest solution without re-writing this whole thing would be adding 1 to the current row of the IndexPath you captured from the action.
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
I did this from memory because I am not near an IDE, so take a look at the change and apply that change if needed in any other location.
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
Your current code calls append to add the new item at the end of the array. What you want to do is insert a new row at indexPath.row+1. Array has an insert(element,at:) function.
You have to handle the case where the user has tapped the last row and not add 1 to avoid an array bounds error:
func InsertVideoTitle(indexPath: IndexPath)
{
let targetRow = indexPath.row < videos.endIndex ? indexPath.row+1 : indexPath.row
videos.insert("Snapchat" at:targetRow)
let newIndexPath = IndexPath(row: targetRow, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [newIndexPath], with: .automatic)
TableSource.endUpdates()
}
I'm doing a form through a UITableView, using custom UITableViewCells which contain a UITextField each.
I'm using textFieldShouldReturn to jump to the next textField but I cannot understand why what I typed in one textField appears randomly (actually, there is weird pattern) into another textField.
Here the custom UITableViewCell
class RPTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textField: DictionaryTextField!
#IBOutlet weak var errorLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
titleLabel.textColor = Constants.secondaryColor
textField.textColor = Constants.secondaryColor
contentView.isUserInteractionEnabled = false
errorLabel.isHidden = true
}
func setTag(tag: Int) {
textField.tag = 100 + tag
}
}
Then in my FormViewController I do
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let field = formFields?[indexPath.row] else { return UITableViewCell() }
if let cell = tableView.dequeueReusableCell(withIdentifier: "cellID") as? RPTableViewCell {
cell.titleLabel.text = field["displayText"]?.uppercased
cell.textField.text = field["userAnswer"] as? String // This was missing, but is key
cell.textField.placeholder = field["placeholder"] as? String
cell.setTag(tag: indexPath.row)
cell.textField.delegate = self
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? RPTableViewCell else { return }
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
cell.textField.becomeFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextTextField = tableView.viewWithTag(textField.tag + 1) as? UITextField {
tableView.deselectRow(at: IndexPath(row: textField.tag - 100, section: 0), animated: false)
tableView.selectRow(at: IndexPath(row: textField.tag - 99, section: 0),
animated: false, scrollPosition: .middle)
nextTextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
EDIT:
In viewDidLoad I load the formFiels like this
// Read from a JSON file
guard let visitorPaths = Constants.configDict()?["visitorPaths"] as? [Dictionary<String, AnyObject>] else {
print("Error: no visitorPaths found in the configuration")
return
}
formFields = visitorPaths.first!["fields"]! as? [[String: AnyObject]]
Your are using the following snippet which does not work:
if let nextTextField = tableView.viewWithTag(textField.tag + 1) as? UITextField
The textfield is not a subview of your tableView. The Textfield is a subview of the TableViewCell.
You can acces the cell in the textFieldShouldReturn(_ textField: UITextField) delegate method like the following:
let nextIndexPath = IndexPath(row: textField.tag + 1, section: 0)
if let cell = tableView.cellForRow(at: nextIndexPath) as? RPTableViewCell {
cell.textField.becomeFirstResponder()
}
Edit for the text jumping:
Add the following textField delegate method to store the new text:
func textFieldDidEndEditing(_ textField: UITextField) {
formFields?[textField.tag - 100]["displayText"] = textField.text
}
I want to change the TableView data according to Textfield. when a user taps on Textfield and starts editing it will change the TableView data accordingly. I saw a lot of examples on but mainly I found about search bar any help would be appreciated. Please note that this is textfield not seacrhbar
You can try
var searchActive : Bool = false
var data = ["San Francisco","New York","San Jose","Chicago","Los Angeles","Austin","Seattle"]
var filtered:[String] = []
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: UIControlEvents.editingChanged)
and handle method:
#objc func textFieldDidChange(_ textField: UITextField) {
// filter tableViewData with textField.text
let searchText = textField.text
filtered = data.filter({ (text) -> Bool in
let tmp: NSString = text as NSString
let range = tmp.range(of: searchText, options: NSString.CompareOptions.caseInsensitive)
return range.location != NSNotFound
})
if(filtered.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:CellIdentifier1) as! generalTableViewCell
if(searchActive){
cell.titlelb.text = filtered[indexPath.row]
} else {
cell.titlelb.text = data[indexPath.row];
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(searchActive){
return filtered.count
} else {
return data.count
}
}
You can implement textFieldDelegate in the view controller and then in the delegate method textFieldDidChange you can change the tableview datasource according to your use and reload the tableview after that.
I have a quite strange behaviour when updating a TableViews cells.
The easiest way to describe is a video:
https://drive.google.com/open?id=0B67InGf2FEPaODFLUEhLZ29LWTg
Here you can see, that the first time i try to expand(or collapse) the View, it sor of does something, but not really. The views that i wanted to show are not there, but it flickers.
Here is my Code:
class StylingTableViewController: UITableViewController {
//MARK: properties
var articles = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
articles.append(contentsOf: [Article(id: "Artikel 1"), Article(id: "Artikel 2")])
//Let table auto layout in height
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
// Use the edit button item provided by the table view controller.
navigationItem.rightBarButtonItem = editButtonItem
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return articles.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "ArticleDetailCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? StylingDetailTableViewCell else {
fatalError("The dequeued cell is not an instance of StylingDetailTableViewCell.")
}
let article = articles[indexPath.row]
cell.articleName.text = article.id
//Scrollview
let width:CGFloat = 90;
var xPos:CGFloat = 0;
var scrollViewContentSize:CGFloat=0;
for _ in 0...10{
let myView:CFPictureView = CFPictureView()
myView.frame.size.width = 80
myView.frame.size.height = 120
myView.frame.origin.x = CGFloat(xPos)
xPos += width
cell.pictures_scrollView.addSubview(myView)
scrollViewContentSize += width
cell.pictures_scrollView.contentSize = CGSize(width: scrollViewContentSize, height: 120)
}
return cell
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
articles.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
//MARK: Actions
#IBAction func favButtonPressed(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
}
#IBAction func expandCell(_ sender: UIButton) {
if let cell = sender.superview?.superview?.superview?.superview?.superview as? StylingDetailTableViewCell {
cell.fieldDescriptorStackView.isHidden = false;
cell.articleBarcode.isHidden = false;
cell.articleCustomerNumber.isHidden = false;
cell.articleStyle.isHidden = false;
cell.expandCellButton.isHidden = true;
cell.collapseCellButton.isHidden = false;
cell.pictures_order.isHidden = false;
cell.pictures_amount.isHidden = false;
cell.pictures_scrollView.isHidden = false;
cell.pictures_title.isHidden = false;
let indexPath = tableView.indexPath(for: cell)
self.tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
(self.parent as? StylingViewController)?.topStackView.isHidden = true;
}
}
#IBAction func collapseCell(_ sender: UIButton) {
if let cell = sender.superview?.superview?.superview as? StylingDetailTableViewCell {
cell.fieldDescriptorStackView.isHidden = true;
cell.articleBarcode.isHidden = true;
cell.articleCustomerNumber.isHidden = true;
cell.articleStyle.isHidden = true;
cell.expandCellButton.isHidden = false;
cell.collapseCellButton.isHidden = true;
cell.pictures_order.isHidden = true;
cell.pictures_amount.isHidden = true;
cell.pictures_scrollView.isHidden = true;
cell.pictures_title.isHidden = true;
let indexPath = tableView.indexPath(for: cell)
self.tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
(self.parent as? StylingViewController)?.topStackView.isHidden = false;
}
}
func undoAction() {
print("undo")
}
}
It seems like something is wrong with tableView.reloadRows.
Help would be appreciated
Could be the issue because the app still didint know the estimated height of the cell at first run.
A fix could be by adding this on viewDidLoad :
tableView.estimatedRowHeight = 60
tableView.rowHeight = UITableViewAutomaticDimension