here is a table View with two Different Section Inside VC2 to Modify or add Contact into phone . the problem it is text fields become blank in Sections when table view scrolled . i found a way to fix this Problem in section 1 but i cant Handle section 2 .
Model :
class ContactModel : NSObject {
var identifier : String!
var thumbnailImageData : UIImage?
var givenName : String!
var familyName : String!
var phoneNumbers : [String]!
var emailAddresses : [String]!
override init() {
self.phoneNumbers = []
self.emailAddresses = []
super.init()
}
VC2 :
var contactModel = ContactModel()
#IBOutlet weak var tvInsert: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell0 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell0") as! InsertTableCell0
cell0.txtFirstName.text = self.contactModel.givenName
cell0.txtLastName.text = self.contactModel.familyName
return cell0
}else if indexPath.section == 1 {
let cell1 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell1") as! InsertTableCell1
cell1.btnDelete.addTarget(self, action: #selector(deleteRowDate(_:)), for: .touchUpInside)
cell1.txtPhoneNumber.placeholder = "Phone Number"
cell1.txtPhoneNumber.text = contactModel.phoneNumbers[indexPath.row]
cell1.txtPhoneNumber.delegate = self
cell1.txtPhoneNumber.tag = indexPath.row
return cell1
}else {
let cell2 = tableView.dequeueReusableCell(withIdentifier: "InsertTableCell2") as! InsertTableCell2
cell2.btnEmail.addTarget(self, action: #selector(deleteRowDate(_:)), for: .touchUpInside)
cell2.txtEmail.placeholder = "Email"
cell2.txtEmail.text = contactModel.emailAddresses[indexPath.row]
cell2.txtEmail.delegate = self
cell2.txtEmail.tag = indexPath.row
return cell2
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let aText = textField.text {
self.contactModel.phoneNumbers[textField.tag] = aText
}
}
Seems like all the fields delegates are self and textFieldDidEndEditing is setting data for all the fields instead of phone number fields.
you have to check if the textField is phone number or not and also you should update the data in model when textField text is changed instead of end editing.
Easier solution will be :- Remove this line "cell1.txtPhoneNumber.delegate = self"
Replace var phoneNumbers: [String]! in model with following
var phoneNumbers = ["","",""] //for 3 phone numbers
Put this code in cellForRow of the particular cell i.e (indexPath.section == 1) for the above question
//Saved Phone number in model
cell.txtPhoneNumber.text = contactModel.phoneNumbers[indexPath.row]
//Get and save Phone number when changed
cell.txtPhoneNumber.tag = indexPath.row
cell.txtPhoneNumber.addTarget(self, action: #selector(self.phoneNumberChanged(_:)), for: .editingChanged)
In the ViewController
#objc func phoneNumberChanged(_ sender: UITextField){
//Phone number changed
contactModel.phoneNumbers[sender.tag] = sender.text ?? ""
}
In case you want to save the phone number on end Editing replace .editingChanged with .editingDidEnd
Related
I have a UITableView with 2 sections.
Each section has different xib cells (with multiple textfields, radio buttons etc.).
The user can enter those values using the keyboard.
How can I get the value of each text field at submit button using model?
Here is my model class for cell for detail section:
class DataModel {
var name: String?
var gender: String?
init(name: String?, gender: String?)
{
self.name = name
self.gender = gender
}
}
How I use it in my tableview:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier:"DetailCell") as? DetailCell
if cell == nil{
let arrNib:Array = Bundle.main.loadNibNamed("DetailCell",owner: self, options: nil)!
cell = arrNib.first as? DetailCell
}
let model: DataModel
model = DataModel [indexPath.row]
//displaying values
cell?.name_lbl.text = model.name
cell?.gender_lbl.text = model.gender
return cell!
}
First, you name_lbl and gender_lbl shoule be UITextField, not the UILabel.
You have to add target for the name_lbl and gender_lbl:
cell?.name_lbl.text = model.name
cell?.name_lbl.tag = indexPath.section
cell?.name_lbl.addTarget(self, action: #selector(textFieldNameDidChange(_:)), for: UIControl.Event.editingChanged)
cell?.gender_lbl.text = model.gender
cell?.name_lbl.tag = indexPath.section
cell?.name_lbl.addTarget(self, action: #selector(textFieldGenderDidChange(_:)), for: UIControl.Event.editingChanged)
and you add methods textFieldNameDidChange and textFieldGenderDidChange as:
#objc func textFieldGenderDidChange(sender: UITextField) {
var model = DataModel[sender.tag]
model.gender = sender.text
}
You should do the same for the "name".
You can give tag to textfield and use UITextFieldDelegate for this like,
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.tag == 0 {
model.name = textField.text ?? ""
}
}
on you button submit, you can access model value like
#IBAction func submitButtonTapped(_ sender: Any) {
self.view.endEditing(true)
let name = model.name
}
For this u need to set tag to textfield in cell for row. In shouldChageCharacter delegate method set text to data array with respective indexpath and create the object of uitableviewcell and set text to the respective label. for get data you can get data from array. In above answer may be if u scroll table then TableCell will mix with eachother.
In the code below I'm populating my table with some data. The switches are off which they don't have to be. In the storyboard I defined it as On.
Cell:
var switchHandler: ((Bool)->Void)?
#IBAction func switchChanged(_ sender: UISwitch) {
self.switchHandler?(sender.isOn)
}
View controller:
var selectedCells = Set<IndexPath>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell") as? SmsTableViewCell
cell?.PhonNumberLbl.text = data![indexPath.section].contacts[indexPath.row]?.phoneNumber
cell?.NameLbl.text = data![indexPath.section].contacts[indexPath.row]?.name
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
cell?.selectedTF.isOn = self.selectedCells.contains(indexPath)
cell?.switchHandler = { (switchState) in
if switchState {
self.selectedCells.insert(indexPath)
} else {
self.selectedCells.remove(indexPath)
}
}
return cell!
}
Model:
typealias smsModelList = [SmsModel]
struct SmsModel:Codable {
var unitNo:Int?
var unitPlaque:String?
var billText:String?
var contacts:[ContactsModel?]
}
typealias contactlistmodel = [ContactsModel]
struct ContactsModel:Codable
{
var id :Int?
var selected :Bool?
var phoneNumber : String?
var name : String?
}
Does anybody see somthing wrong which turns off the switch?
First of all as you force unwrap the cell anyway do it in the dequeue line to avoid the unnecessary amount of question marks and use the API to return a non-optional cell
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell", for: indexPath) as! SmsTableViewCell
To fix your issue update the selected property of the ContactsModel struct directly and forget the extra selectedCells array. Further declare – at least – selected as non-optional, practically there is no maybe state. And declare also all data source arrays (data / contacts) as non-optional, cellForRow is called only if there is an item at the particular indexPath by default.
struct ContactsModel : Codable {
...
var selected : Bool
...
}
...
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell", for: IndexPath) as! SmsTableViewCell
let contact = data[indexPath.section].contacts[indexPath.row]
cell.PhonNumberLbl.text = contact.phoneNumber
cell.NameLbl.text = contact.name
cell.selectedTF.isOn = contact.selected
cell.switchHandler = { [unowned self] switchState in
// as the structs are value types you have to specify the full reference to the data source array
self.data[indexPath.section].contacts[indexPath.row].selected = switchState
}
Consider to use classes rather than structs in this case then you can shorten the closure
cell.switchHandler = { switchState in
contact.selected = switchState
}
You use both
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
cell?.selectedTF.isOn = self.selectedCells.contains(indexPath)
so isOn property of the switch is controlled from 2 sides , so you have to decide which line that should be commnented , plus don't depend on storyboard prototype cell setup as because of cell reusing it' ll be changed , if you want to make them all on by default then change the var selectedCells to contain all possible indexPaths and comment the other one
I have a table consist of more than 20 rows and each row has 3 text field named Rate, minutes and total. calculation is as rate/60 * minutes = total. I want to get total of all 20 rows in another text field Balance that is not a part of table view cell but of view controller. I want to calculate the value in real time and get the addition of total text field named as Rupees Text field below table view cell enter image description here.I can see on 3 rows on screen and 17 other are down. I get for only 3 text field in row not for all. I h have written this code but can calculate for 3 rows not for all 20. Please do check Thanks in advance. second issue I have used notification to pass string and it send me the last value in string not the updated one . Please do check the image
import UIKit
class calculationTVC: UITableViewCell, UITextFieldDelegate{
var numberOfItems = 0
var myTV : UITableView!
#IBOutlet weak var minutes: UITextField!
#IBOutlet weak var total: UITextField!
#IBOutlet weak var rate: UITextField!
#IBAction func rateChanged(_ sender: UITextField) {
// print(minutes.text, total.text, rate.text)
ratesischanged()
}
public func ratesischanged(){
let first = Double(rate.text!)
let second = Double(minutes.text!)
let third = Double(0)
if minutes.text?.count == 0{
total.text = "\(third)"
}
if minutes.text?.count == 0{
// print("null")
total.text = "\(third)"
// print("got zero")
}else{
let output = Double((first!/60) * second!)
total.text = "\(output)"
let rows = myTV.numberOfRows(inSection: 0)
var add : Double!
var c = [Int]()
for i in 0..<rows{
let path = IndexPath(row: i, section: 0)
let cell = myTV.cellForRow(at: path) as? calculationTVC
add = (cell?.total.text as NSString?)?.doubleValue
// print(Int(add))
//print(add)
if add != nil{
c += [Int(add)]
// print(c)
}
}
var sum = 0
var counter = 0
// Enter your code below
while counter < c.count {
var newValue = c[counter]
sum += newValue
counter += 1
// print(sum, "sum")
}
var myString = String(describing: sum)
NotificationCenter.default.post(name: calculationScreen.notificationName, object: nil, userInfo: ["DataMy": myString ?? ""])
//print(ar, "new ar")
}
}
func numberOfRows(numberInt : Int,tableView : UITableView){
numberOfItems = numberInt
myTV = tableView
}
}
you can define protocol on tableViewCell and fire it when the UITextField on each cell modified, and in your viewController implement of delegate:
protocol TableViewCellDelegate: class {
func textViewValueChanged(minutes: String, rate: String, total: String)
}
class calculationTVC: UITableViewCell { // delete UITextFieldDelegate
weak var delegate: TableViewCellDelegate?
// ... the rest of your code
#IBAction func textFieldEditingChanged(_ sender: UIButton) { //action of ***Editing Changed*** and connected to all *UITextField*
//calculate total
delegate?.textViewValueChanged(minutes: self.minutes, rate: self.rate, total: self.total)
}
}
class viewController: UIViewController, UITableView, UITableViewDelegate, UITableViewdataSource, TableViewCellDelegate { //and what you want to implement
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: IndexPath) as? calculationTVC
cell.delegate = self
// ... the rest of your code
return cell
}
func textViewValueChanged(minutes: String, rate: String, total: String) {
var total = 0
for i in 1...20 {
let cell = tableView.cellForRow(at: i) as? calculationTVC
total += cell.rate/60 * cell.minutes
}
//set your totalLabel
}
}
I have a tableView and my tableViewCell has a isLiked variable and a likeButton. I would like to set my likeButton's tintColor accordingly to the state of isLiked. The way I have done it is that I retrieve a list of posts from Firebase that the user has liked, and then pass it into the tableView. If the postID tallies with the list, I would like to set the isLiked variable to true.
However, despite setting this logic in cellForRow, the isLiked variable in tableViewCell is not set accordingly. How can I get it set accordingly? My code as follows:
// in TableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let post: FIRDataSnapshot = self.posts[indexPath.row]
let postValues = post.value as! [String: AnyObject]
let postID = postValues["postID"] as! String
for likedPosts in usersLikedPostsList {
if likedPosts == postID {
DispatchQueue.main.async {
cell.isLiked = true
}
} else {
cell.isLiked = false
}
}
return cell
}
// in TableViewCell
class TableViewCell: UITableViewCell {
var isLiked: Bool?
#IBOutlet weak var likeButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
print(isLiked)
if let _ = isLiked {
likeButton.tintColor = .red
} else {
likeButton.tintColor = .darkGray
}
}
}
From the print statement, isLiked variable is nil when the table is loaded. I have attempted with self.tableView.reloadData() but it does not set it accordingly as well.
Note that I would like to manipulate isLiked state in TableViewCell because I wanna add code to toggle its state within TableViewCell, so that users can like and unlike the post, and perform the updates to Firebase accordingly as well.
The cell awakeFromNib() method is executed only once, when the first time the cell is instanciated from storyboard or Nib file, so you need to update your cell every time this value isLiked change, try with his code
// in TableViewCell
class TableViewCell: UITableViewCell {
var isLiked: Bool = false{
didSet{
likeButton.tintColor = .darkGray
if(isLiked)
{
likeButton.tintColor = .red
}
}
}
#IBOutlet weak var likeButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
Hope this helps you
let post: FIRDataSnapshot = self.posts[indexPath.row]
let postValues = post.value as! [String: AnyObject]
let postID = postValues["postID"] as! String
Try your above code in view did load and make postID as global variable.
and print to see if it returns anything or use .count to check it.
If it has count more than 1.Then try to use postID in method :cellForRowAt
Secondly, tableview reload will work only if numberOfRowsInSection have different value from last time.
Check whether this method is called or not when you reload table.
I'm making a very simple app where the user enters the number of people in the first Screen.
In the second screen it generates a number of UITableViewCell based on the number the user entered in the first screen. The UITableViewCell have a UITextField in them and I'm trying to store the data entered in those fields in an array once the user clicks to go to the third screen.
How can I do that? Thanks in advance!
Edit: I'm using the storyboard.
Here is what the code that calls for the custom UITableViewCell looks like for my UIViewController:
func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell: EditingCell = tableView.dequeueReusableCellWithIdentifier("Cell") as EditingCell
if indexPath.row % 2 == 0{
cell.backgroundColor = UIColor.purpleColor()
} else {
cell.backgroundColor = UIColor.orangeColor()
}
let person = arrayOfPeople[indexPath.row]
cell.setCell(person.name)
return cell
}
Here is what the code for the UITableViewCell looks like:
class EditingCell: UITableViewCell{
#IBOutlet weak var nameInput: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCell(name:String){
self.nameInput.placeholder = name
}
}
There is a problem with your approach if the number of rows in your table exceeds the number that can fit on screen. In that case, the cells that scroll off-screen will be re-used, and the contents of the nameInput textField will be lost. If you can be sure that this will never happen, use the following code (in the method that handles button taps) to compose your array:
var arrayOfNames : [String] = [String]()
for var i = 0; i<self.arrayOfPeople.count; i++ {
let indexPath = NSIndexPath(forRow:i, inSection:0)
let cell : EditingCell? = self.tableView.cellForRowAtIndexPath(indexPath) as EditingCell?
if let item = cell?.nameInput.text {
arrayOfNames.append(item)
}
}
println("\(arrayOfNames)")
Alternatively....
However, if it is possible that cells will scroll off-screen, I suggest a different solution. Set the delegate for the nameInput text fields, and then use the delegate methods to grab the names as they are entered.
First, add variables to your view controller, to hold the array and the row number of the text field currently being edited.
var arrayOfNames : [String] = [String]()
var rowBeingEdited : Int? = nil
Then, in your cellForRowAtIndexPath method, add:
cell.nameInput.text = "" // just in case cells are re-used, this clears the old value
cell.nameInput.tag = indexPath.row
cell.nameInput.delegate = self
Then add two new functions, to catch when the text fields begin/end editing:
func textFieldDidEndEditing(textField: UITextField) {
let row = textField.tag
if row >= arrayOfNames.count {
for var addRow = arrayOfNames.count; addRow <= row; addRow++ {
arrayOfNames.append("") // this adds blank rows in case the user skips rows
}
}
arrayOfNames[row] = textField.text
rowBeingEdited = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
rowBeingEdited = textField.tag
}
When the user taps the button, they might still be editing one of the names. To cater for this, add the following to the method that handles the button taps:
if let row = rowBeingEdited {
let indexPath = NSIndexPath(forRow:row, inSection:0)
let cell : EditingTableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as EditingTableViewCell?
cell?.nameTextField.resignFirstResponder()
}
This forces the textField to complete editing, and hence trigger the didEndEditing method, thereby saving the text to the array.
Here for new swift versions of answer
var arrayOfNames : [String] = [String]()
var i = 0
while i < taskArrForRead.count {
let indexPath = IndexPath(row: i, section: 0)
let cell : taslakDuzenlemeCell? = self.tableView.cellForRow(at: indexPath) as! taslakDuzenlemeCell?
if let item = cell?.taslakTextField.text {
arrayOfNames.append(item)
}
i = i + 1
}
print("\(arrayOfNames)")