In my project I have a SignUpViewController which looks like this:
All the textFields are custom-cells within a tableViewController.
TableView:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 1st cell -> email textfield
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpEmailCell", for: indexPath) as! SignUpEmailCell
return cell
// 2nd cell -> anzeigename
}else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpAnzeigeName", for: indexPath) as! SignUpAnzeigeName
return cell
// 3rd cell -> Wishlist-Handle
}else if indexPath.row == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpHandleCell", for: indexPath) as! SignUpHandleCell
return cell
// 4th cell -> passwort textfield
}else if indexPath.row == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpPasswordCell", for: indexPath) as! SignUpPasswordCell
return cell
// 5th cell -> repeat password textfield
}else if indexPath.row == 4 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpPasswordRepeatCell", for: indexPath) as! SignUpPasswordRepeatCell
return cell
// 6th cell -> document label
}else if indexPath.row == 5 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpDocumentCell", for: indexPath) as! SignUpDocumentCell
return cell
}
// last cell -> signUpButton
let cell = tableView.dequeueReusableCell(withIdentifier: "SignUpButtonCell", for: indexPath) as! SignUpButtonCell
return cell
}
Password-Cell: (basic structure is the same for every cell)
class SignUpPasswordCell: UITableViewCell, UITextFieldDelegate {
public static let reuseID = "SignUpPasswordCell"
lazy var eyeButton: UIButton = {
let v = UIButton()
v.addTarget(self, action: #selector(eyeButtonTapped), for: .touchUpInside)
v.setImage(UIImage(named: "eyeOpen"), for: .normal)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
lazy var passwordTextField: CustomTextField = {
let v = CustomTextField()
v.borderActiveColor = .white
v.borderInactiveColor = .white
v.textColor = .white
v.font = UIFont(name: "AvenirNext-Regular", size: 17)
v.placeholder = "Passwort"
v.placeholderColor = .white
v.placeholderFontScale = 0.8
v.minimumFontSize = 13
v.borderStyle = .line
v.addTarget(self, action: #selector(SignUpPasswordCell.passwordTextFieldDidChange(_:)),for: .editingChanged)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = .clear
passwordTextField.delegate = self
eyeButton.isHidden = true
passwordTextField.textContentType = .newPassword
passwordTextField.isSecureTextEntry.toggle()
setupViews()
}
func setupViews(){
contentView.addSubview(passwordTextField)
contentView.addSubview(eyeButton)
passwordTextField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
passwordTextField.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
passwordTextField.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
passwordTextField.heightAnchor.constraint(equalToConstant: 60).isActive = true
eyeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
eyeButton.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 10).isActive = true
}
var check = true
#objc func eyeButtonTapped(_ sender: Any) {
check = !check
if check == true {
eyeButton.setImage(UIImage(named: "eyeOpen"), for: .normal)
} else {
eyeButton.setImage(UIImage(named: "eyeClosed"), for: .normal)
}
passwordTextField.isSecureTextEntry.toggle()
if let existingText = passwordTextField.text, passwordTextField.isSecureTextEntry {
/* When toggling to secure text, all text will be purged if the user
continues typing unless we intervene. This is prevented by first
deleting the existing text and then recovering the original text. */
passwordTextField.deleteBackward()
if let textRange = passwordTextField.textRange(from: passwordTextField.beginningOfDocument, to: passwordTextField.endOfDocument) {
passwordTextField.replace(textRange, withText: existingText)
}
}
/* Reset the selected text range since the cursor can end up in the wrong
position after a toggle because the text might vary in width */
if let existingSelectedTextRange = passwordTextField.selectedTextRange {
passwordTextField.selectedTextRange = nil
passwordTextField.selectedTextRange = existingSelectedTextRange
}
}
#objc func passwordTextFieldDidChange(_ textField: UITextField) {
if textField.text == "" {
self.eyeButton.isHidden = true
}else {
self.eyeButton.isHidden = false
}
}
}
Problem:
I would like to be able to show some extra information on some textFields when selected.
For example: When passwordTextField is editing I would like to show the password requirements right below the textfield. But the extra information should only be displayed while editing or after editing. When the ViewController is being displayed at first it should still look like the picture above.
I hope my problem is clear and I am grateful for every help.
Related
I have a detail ViewController whoose cells are defined by a custom uitableviewdiffabledata like this:
{ (_, indexPath, item) -> UITableViewCell? in
let color = UIColor(named: "blue")!
if let _ = item as? TextFieldItem, let cell = tableView.dequeueReusableCell(withIdentifier: "textField", for: indexPath) as? TextFieldTableViewCell {
cell.textField.text = recipe.wrappedValue.name
cell.textField.placeholder = NSLocalizedString("name", comment: "")
cell.selectionStyle = .none
cell.textChanged = nameChanged
cell.backgroundColor = color
return cell
} else if let imageItem = item as? ImageItem, let imageCell = tableView.dequeueReusableCell(withIdentifier: "image", for: indexPath) as? ImageTableViewCell {
imageCell.setup(imageData: imageItem.imageData)
return imageCell
} else if let _ = item as? AmountItem, let amountCell = tableView.dequeueReusableCell(withIdentifier: "times", for: indexPath) as? AmountTableViewCell{
amountCell.setUp(with: recipe.wrappedValue.timesText, format: formatAmount)
amountCell.backgroundColor = color
return amountCell
} else if item is InfoItem {
return InfoTableViewCell(infoText: Binding(get: {
return recipe.wrappedValue.info
}, set: updateInfo), reuseIdentifier: "info")
} else if let stripItem = item as? InfoStripItem, let infoStripCell = tableView.dequeueReusableCell(withIdentifier: "infoStrip", for: indexPath) as? InfoStripTableViewCell {
infoStripCell.setUpCell(for: stripItem)
return infoStripCell
} else if let stepItem = item as? StepItem {
let stepCell = StepTableViewCell(style: .default, reuseIdentifier: "step")
stepCell.setUpCell(for: stepItem.step)
return stepCell
} else if let detailItem = item as? DetailItem, let cell = tableView.dequeueReusableCell(withIdentifier: "detail", for: indexPath) as? DetailTableViewCell {
let title = NSAttributedString(string: detailItem.text, attributes: [.foregroundColor : UIColor.label])
cell.textLabel?.attributedText = title
cell.accessoryType = .disclosureIndicator
cell.backgroundColor = color
return cell
}
return UITableViewCell()
}
and i want to make the InfoTableViewCell which is defined like this:
class InfoTableViewCell: UITableViewCell {
#Binding private var infoText: String
private var textView = UITextView()
init(infoText: Binding<String>, reuseIdentifier: String?) {
self._infoText = infoText
super.init(style: .default, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
self.addSubview(textView)
textView.fillSuperview()
textView.text = infoText
textView.delegate = self
textView.backgroundColor = UIColor(named: "blue")!
textView.font = UIFont.preferredFont(forTextStyle: .body)
}
}
extension InfoTableViewCell: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
self.infoText = textView.text
}
}
and i want to make this cell to resize based on the text thats in the textField. Any tips on how to do that ?
P. S. I am using the LBTA tools for filling the contents of the cell with the textField.
In my experience, just omit the tableView(_:heightForRowAt:) tableview delegate. Then when creating your cells, create constraints that bind the top and bottom of your cell to the top and bottom of your content. The cell will then grow or shrink to meet the constraints.
In the init of your custom table view cell class:
textView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8).isActive = true
textView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -8).isActive = true
Not sure how the LBTA tools will affect the behavior of this, so this might only work with vanilla UIKit.
I am posting this after having tried all the .isEmpty solutions i found.
I am unable to detect a value in textField. I have cells which are set to cell.selectionStyle = .none. These cells have a label and textFields.
I have given the cells identifiers:
let cell = addRestaurant.dequeueReusableCell(withIdentifier: String(describing: RestaurantAddViewCells.self), for: indexPath) as! RestaurantAddViewCells
My goal is to have a button which checks if any field is empty onclick.
let saveButton = UIBarButtonItem(image: UIImage(named: "save"), style: .plain, target: self, action: #selector(saveRestaurant))
Here is how i try to check if the textFields are empty or not:
#objc func saveRestaurant() {
if let indexPath = addRestaurant.indexPathForSelectedRow {
let cell = addRestaurant.cellForRow(at: indexPath) as! RestaurantAddViewCells
if (cell.nameTextField.text == "" || cell.typeTextField.text == "" || cell.locationTextField.text == "" || cell.hotelPhoneText.text == "") {
let saveAlertController = UIAlertController(title: "Fields Empty", message: "fill all fields", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
saveAlertController.addAction(saveAction)
self.present(saveAlertController, animated: false, completion: nil)
}
else { }
}
}
However nothing happens
I have tried and comment cell.selectionStyle = .none, still no effect
Here addRestaurant is tableView in same file while RestaurantAddViewCells is a class containing properties for labels , textFields
UPDATE - here is what i do in my cellForRowAt, sample for first two cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = addRestaurant.dequeueReusableCell(withIdentifier: String(describing: RestaurantAddViewCells.self), for: indexPath) as! RestaurantAddViewCells
view.addSubview(cell.contentView)
view.addSubview(cell.hotelImage)
view.addSubview(cell.imageButton)
view.addSubview(cell)
cell.imageButton.translatesAutoresizingMaskIntoConstraints = false
cell.hotelImage.translatesAutoresizingMaskIntoConstraints = false
//set the cell height
cell.heightAnchor.constraint(greaterThanOrEqualToConstant: 200).isActive = true
//set the hotelImage
cell.hotelImage.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
cell.hotelImage.heightAnchor.constraint(equalToConstant: 200).isActive = true
cell.hotelImage.leadingAnchor.constraint(equalTo: cell.leadingAnchor).isActive = true
cell.hotelImage.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
// pin it
//No need to pin it as width is already pinned to to lead and trail of screen
cell.imageButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
cell.imageButton.widthAnchor.constraint(equalToConstant: 30).isActive = true
cell.imageButton.centerXAnchor.constraint(equalTo: cell.centerXAnchor).isActive = true
cell.imageButton.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
cell.imageButton.image = UIImage(named: "photo")
return cell
case 1:
let cell = addRestaurant.dequeueReusableCell(withIdentifier: String(describing: RestaurantAddViewCells.self), for: indexPath) as! RestaurantAddViewCells
cell.nameTextFiled.tag = 1
cell.nameTextFiled.delegate = self
cell.nameTextFiled.becomeFirstResponder()
cell.selectionStyle = .none
cell.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
view.addSubview(cell.nameLabel)
view.addSubview(cell.nameTextFiled)
view.addSubview(cell)
view.addSubview(cell.contentView)
cell.nameLabel.translatesAutoresizingMaskIntoConstraints = false
cell.nameTextFiled.translatesAutoresizingMaskIntoConstraints = false
cell.nameTextFiled.heightAnchor.constraint(equalToConstant: 50).isActive = true
cell.nameLabel.text = "Name:"
//Define custom fonts
let font = UIFont(name: "Rubik-Medium", size: 18)
let dynamicFonts = UIFontMetrics(forTextStyle: .body)
cell.nameLabel.font = dynamicFonts.scaledFont(for: font!)
cell.nameTextFiled.font = dynamicFonts.scaledFont(for: font!)
cell.nameTextFiled.borderStyle = .roundedRect
cell.nameTextFiled.placeholder = "Enter Your Name"
let stackName = UIStackView()
view.addSubview(stackName)
stackName.alignment = .top
stackName.axis = .vertical
stackName.spacing = 5.0
stackName.distribution = .fill
stackName.translatesAutoresizingMaskIntoConstraints = false
stackName.addArrangedSubview(cell.nameLabel)
stackName.addArrangedSubview(cell.nameTextFiled)
stackName.topAnchor.constraint(equalTo: cell.topAnchor, constant: 10).isActive = true
stackName.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 10).isActive = true
stackName.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -10).isActive = true
cell.nameTextFiled.trailingAnchor.constraint(equalTo: stackName.trailingAnchor).isActive = true
return cell
This is what my updated RestaurantAddViewCells lookslike with input from one member here
class RestaurantAddViewCells: UITableViewCell, UITextFieldDelegate, UITextViewDelegate {
var nameLabel: UILabel = UILabel()
var nameTextFiled: RoundedTextFields = RoundedTextFields()
var typeLabel: UILabel = UILabel()
var typeTextField: RoundedTextFields = RoundedTextFields()
var locationLabel: UILabel = UILabel()
var locationTextField: RoundedTextFields = RoundedTextFields()
var imageButton: UIImageView = UIImageView()
var hotelImage: UIImageView = UIImageView()
var hotelDescriptionLabel: UILabel = UILabel()
var hotelTextDescription: UITextView = UITextView()
var hotelPhonelabel: UILabel = UILabel()
var hotelPhoneText: RoundedTextFields = RoundedTextFields()
var isEmptyTextFields: Bool {
return nameTextFiled.text!.isEmpty || typeTextField.text!.isEmpty || locationTextField.text!.isEmpty || hotelTextDescription.text!.isEmpty || hotelPhoneText.text!.isEmpty
}
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
}
}
UPDATE 2 - so here is my update while trying to ask the question again, i have a tableview where each cell has some textfields, when i press a button before i do any thing with the data, i want to check if the fields are filled or not, how can i do this, now as per my design i use a class to define the view properties and call them and give then values where i use the tableView, i use cellForRowAt to define constraints and provide values to labels etc, but how can i successfully access them outside of cellForRowAt to check current state , with out totally changing the design of project
UPDATE 3 - it seems if i were to do the same thing in story board and add the outlets to same function it is capable of detecting a change in state of textbox, if it is empty or not, in case of trying to do it programatically, its not able to detect a change in state of textbox from empty to not empty, but i still have no way to check like in javascript if the textboxes are empty or not on button click
Update 4 - I am now using below code on button tap, but for some strange reason , i am not able to detect the text entered, it always keeps returning empty even if there is text there
#objc func saveRestaurant(sender: AnyObject) {
let cell = addRestaurant.dequeueReusableCell(withIdentifier: String(describing: RestaurantAddViewCells.self)) as! RestaurantAddViewCells
if cell.nameTextFiled.text == "" || cell.typeTextField.text == "" || cell.locationTextField.text == "" || cell.hotelPhoneText.text == "" || cell.hotelTextDescription.text == "" {
let alertController = UIAlertController(title: "Oops", message: "We can't proceed because one of the fields is blank. Please note that all fields are required.", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(alertAction)
present(alertController, animated: true, completion: nil)
// addRestaurant.reloadData()
return
}
else {
print("Name: \(cell.nameTextFiled.text ?? "")")
print("Type: \(cell.typeTextField.text ?? "")")
print("Location: \(cell.locationTextField.text ?? "")")
print("Phone: \(cell.hotelPhoneText.text ?? "")")
print("Description: \(cell.hotelTextDescription.text ?? "")")
dismiss(animated: true, completion: nil)
}
}
Update 5 - this is what has finally worked , but is very long and not so good, can any one suggest a loop for going through all the indexPath
#objc func saveRestaurant(sender: AnyObject) {
let index = IndexPath(row: 1, section: 0)
let cell: RestaurantAddViewCells = self.addRestaurant.cellForRow(at: index) as! RestaurantAddViewCells
let nameVal = cell.nameTextFiled.text!
let index1 = IndexPath(row: 2, section: 0)
let cell2: RestaurantAddViewCells = self.addRestaurant.cellForRow(at: index1) as! RestaurantAddViewCells
let typeVal = cell2.typeTextField.text!
let index2 = IndexPath(row: 3, section: 0)
let cell3: RestaurantAddViewCells = self.addRestaurant.cellForRow(at: index2) as! RestaurantAddViewCells
let locationVal = cell3.locationTextField.text!
let index3 = IndexPath(row: 4, section: 0)
let cell4: RestaurantAddViewCells = self.addRestaurant.cellForRow(at: index3) as! RestaurantAddViewCells
let phoneVal = cell4.hotelPhoneText.text!
let index4 = IndexPath(row: 5, section: 0)
let cell5: RestaurantAddViewCells = self.addRestaurant.cellForRow(at: index4) as! RestaurantAddViewCells
let descVal = cell5.hotelTextDescription.text!
if(nameVal == "" || typeVal == "" || locationVal == "" || phoneVal == "" || descVal == "") {
let saveAlertController = UIAlertController(title: "Fields Empty", message: "fill all fields", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
saveAlertController.addAction(saveAction)
self.present(saveAlertController, animated: false, completion: nil)
// return
}
else {
print("Name: \(nameVal)")
print("Type: \(typeVal)")
print("Location: \(locationVal)")
print("Phone: \(phoneVal)")
print("Description: \(descVal)")
self.navigationController?.popViewController(animated: false)
}
}
First of all, add a computed property in RestaurantAddViewCells that'll return if any of the textFields in the cell is empty, i.e.
class RestaurantAddViewCells: UITableViewCell {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var typeTextField: UITextField!
var isEmptyTextFields: Bool {
return nameTextField.text!.isEmpty || typeTextField.text!.isEmpty
}
}
Now, your saveRestaurant() method will loop through the numberOfCells. Get the cell for each row and check if isEmptyTextFields returns true. In that case you need to show the alert.
#objc func saveRestaurant() {
let numberOfCells = 6
var isAnyTextFieldEmpty = false
(0..<numberOfCells).forEach {
let cell = tableView.cellForRow(at: IndexPath(row: $0, section: 0)) as! RestaurantAddViewCells
isAnyTextFieldEmpty = isAnyTextFieldEmpty || cell.isEmptyTextFields
}
if isAnyTextFieldEmpty {
//Show alert...
} else {
//....
}
}
I'll try to give you an answer as easy as I can make possible.
Problem
You're using a table view and it's cell which contains a textfield where user may enter some input, also the cell may get reused when scrolled and its possible we may lose input from user.
Solution
We have a textfield in our cells, and we need all the inputs from user to be stored somewhere lets say a dictionary of IndexPath as key and String as value. Something like var inputs:[IndexPath: String] = [:]. Whenever user enters something and as soon as leaves the textfield we'll store that input in our dictionary against it's cell indexPath. when user clicks on button, we'll loop through and check which textfield is empty
A very simple example is here
import UIKit
class TextFieldTableViewCell: UITableViewCell {
lazy var textField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.placeholder = "Enter your text here"
return textField
} ()
lazy var label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.required, for: .horizontal)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension TextFieldTableViewCell {
//MARK: Private
private func setupView() {
selectionStyle = .none
contentView.addSubview(label)
contentView.addSubview(textField)
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8).isActive = true
label.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8).isActive = true
label.bottomAnchor.constraint(greaterThanOrEqualTo: contentView.bottomAnchor, constant: -8).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
textField.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 8).isActive = true
textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8).isActive = true
textField.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8).isActive = true
textField.bottomAnchor.constraint(greaterThanOrEqualTo: contentView.bottomAnchor, constant: -8).isActive = true
textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
}
class ViewController: UIViewController {
private lazy var tableView: UITableView = {
let tableView = UITableView.init(frame: .zero, style: .grouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
private var inputs: [IndexPath: String] = [:]
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.dataSource = self
tableView.register(TextFieldTableViewCell.self, forCellReuseIdentifier: "TextFieldTableViewCell")
title = "Some random title"
let barbutton = UIBarButtonItem.init(barButtonSystemItem: .done, target: self, action: #selector(saveAction(_:)))
navigationItem.rightBarButtonItem = barbutton
}
#objc
func saveAction(_ sender: UIBarButtonItem) {
view.endEditing(true)
for i in 0 ..< tableView.numberOfSections {
for j in 0 ..< tableView.numberOfRows(inSection: i) {
let indexPath = IndexPath.init(row: j, section: i)
print("Input at indexPath: Row: \(indexPath.row), Section: \(indexPath.section)")
if let input = inputs[indexPath], input.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
print(input)
}
else {
print("user has not input any value or kept it empty")
}
print("__________")
}
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
30
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TextFieldTableViewCell", for: indexPath) as? TextFieldTableViewCell ?? TextFieldTableViewCell.init(style: .default, reuseIdentifier: "TextFieldTableViewCell")
cell.label.text = "Row: \(indexPath.row)"
cell.textField.delegate = self
cell.textField.text = inputs[indexPath]
return cell
}
}
extension ViewController: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
if let cell = textField.superview?.superview as? TextFieldTableViewCell, let indexPath = tableView.indexPath(for: cell) {
inputs[indexPath] = textField.text
}
}
}
EDIT
After OPs comments, here's the solution that will work for OP
Solution:
As OP has only 6 cells, we can cache then in a dictionary and return from the dictionary whenever needed. (We're doing this only because of small number of table cells and OP's structure. I do not recommend this solution)
Add a new dictionary to your viewcontroller
private var cachedCell: [IndexPath: TextFieldTableViewCell] = [:]
Change Cell for row to this (Note that you should not reuse tableview cell as we're caching them in our cachedCell dictionary otherwise it may result in unknown. It is never recommended to cache cells by me as well as other developers)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = cachedCell[indexPath] {
return cell
}
let cell = TextFieldTableViewCell.init(style: .default, reuseIdentifier: "TextFieldTableViewCell")
cell.label.text = "Row: \(indexPath.row)"
cell.textField.delegate = self
cell.textField.text = inputs[indexPath]
cachedCell[indexPath] = cell
return cell
}
Change Save Action to
#objc
func saveAction(_ sender: UIBarButtonItem) {
view.endEditing(true)
for (indexPath, cell) in cachedCell {
print("Input at indexPath: Row: \(indexPath.row), Section: \(indexPath.section)")
if let input = cell.textField.text, input.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
print(input)
}
else {
print("user has not input any value or kept it empty")
}
print("__________")
}
}
If you have any other questions, you can ask it in comment section.
Happy Coding
I'm a newbie, I have the following problem. There is a screen like this picture. I use UICollectionViewCell for header and I can't addTarget for the blue button in this.
I can’t manage. Could you help me?
class UserProfileVC: UICollectionViewController, UICollectionViewDelegateFlowLayout,UserProfileHeaderDelegate{
var currentUser: User?
var userToLoadFromSearchVC: User?
override func viewDidLoad() {
super.viewDidLoad()
// Register cell classes
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
// resgiter header class before use
self.collectionView!.register(UserProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader , withReuseIdentifier: headerIdentifier)
// back ground color
self.collectionView?.backgroundColor = .white
//fetch user data
if userToLoadFromSearchVC == nil{
fetchCurrentUserData()
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 0
}
// config size for header
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 200)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// Declare header
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! UserProfileHeader
// set delegate
header.delegate = self
if let user = self.currentUser {
header.user = user
} else if let userToLoadFromSearchVC = self.userToLoadFromSearchVC {
header.user = userToLoadFromSearchVC
self.navigationItem.title = userToLoadFromSearchVC.username
}
// Return header
return header
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
This is my code in header file.
class UserProfileHeader: UICollectionViewCell {
var delegate: UserProfileHeaderDelegate?
var user: User? {
didSet {
configuredEditProfileFollowButton()
setUserStats(for: user)
let fullName = user?.name
nameLabel.text = fullName
guard let profileImageUrl = user?.profileImage else {return}
profileImageView.loadImage(with:profileImageUrl)
}
}
let profileImageView : UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = .lightGray
return iv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 12)
return label
}()
let postLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
let attributedText = NSMutableAttributedString(string:"5\n",attributes:[NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string:"posts",attributes: [NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14), NSAttributedString.Key.foregroundColor:UIColor.lightGray]))
label.attributedText = attributedText
return label
}()
let followersLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
let followingLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
let attributedText = NSMutableAttributedString(string:"5\n",attributes:[NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string:"following",attributes:[NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14), NSAttributedString.Key.foregroundColor:UIColor.lightGray]))
label.attributedText = attributedText
return label
}()
let editProfileFollowButton : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Loading", for: .normal)
button.layer.cornerRadius = 5
button.layer.borderColor = UIColor.lightGray.cgColor
button.layer.borderWidth = 0.5
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(handleEditProfileFollow), for: .touchUpOutside)
return button
}()
let gridButton : UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "grid"), for: .normal)
return button
}()
let listButton : UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named:"list"), for: .normal)
button.tintColor = UIColor(white: 0, alpha: 0.2)
return button
}()
let bookmarkButton : UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named:"ribbon"), for: .normal)
button.tintColor = UIColor(white: 0, alpha: 0.2)
button.addTarget(self, action: #selector(testFunc(_:)), for: UIControl.Event.touchUpInside)
return button
}()
#objc func testFunc(_ sender : UIButton){
print("Pressed ")
}
#objc func handleFollowersTapped() {
delegate?.handleFollowersTapped(for: self)
}
#objc func handleFollowingTapped() {
delegate?.handleFollowingTapped(for: self)
}
#objc func handleEditProfileFollow() {
guard let user = self.user else {return}
if editProfileFollowButton.titleLabel?.text == "Edit Profile"{
print("Handler edit profile ")
}
else{
if editProfileFollowButton.titleLabel?.text == "Follow"{
editProfileFollowButton.setTitle("Following", for: .normal)
user.follow()
}else{
editProfileFollowButton.setTitle("Follow", for: .normal)
user.unfollow()
}
}
}
This is because you are creating the button target before the UserProfileHeader initialized. So you need to create the button using lazy var (lazily).
private lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(handleButtonTapped), for: .touchUpInside)
return button
}()
#objc private func handleButtonTapped() {
print("Button tapped")
}
Try this, i think this will help you.
class customCell: UICollectionViewCell {
#IBOutlet weak var btn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! customCell
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: #selector(self.btnpressed(sender:)), for: .touchUpInside)
return cell
}
#objc func btnpressed(sender: UIButton!) {
print(sender.tag)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: customCell, for: indexPath) as! customCell
view.pagePluginButtonAction = {
self.TapedBtn()
}
return view
}
func TapedBtn(){
print("click")
}
You need to add the target in 'viewForSupplementaryElementOfKind' where you set up your header. See example below, just after you set the header delegate.
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// Declare header
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! UserProfileHeader
// set delegate
header.delegate = self
// Add the target here
header.yourButton.addTarget(self, action: #selector(handleYourButton), for: .touchUpInside)
if let user = self.currentUser {
header.user = user
} else if let userToLoadFromSearchVC = self.userToLoadFromSearchVC {
header.user = userToLoadFromSearchVC
self.navigationItem.title = userToLoadFromSearchVC.username
}
// Return header
return header
}
in my project I have a UITableView. The cells inside of that contains among other things a UIButton which acts like a "check-box", so the user can tick of a task.
My question: How can I delete a cell after the user presses the UIButton inside of it?
This is my customCell :
import UIKit
class WhishCell: UITableViewCell {
let label: UILabel = {
let v = UILabel()
v.font = UIFont(name: "AvenirNext", size: 23)
v.textColor = .white
v.font = v.font.withSize(23)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let checkButton: UIButton = {
let v = UIButton()
v.backgroundColor = .darkGray
// v.layer.borderColor = UIColor.red.cgColor
// v.layer.borderWidth = 2.0
v.translatesAutoresizingMaskIntoConstraints = false
v.setBackgroundImage(UIImage(named: "boxUnchecked"), for: .normal)
return v
}()
public static let reuseID = "WhishCell"
required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = .clear
// add checkButton
self.contentView.addSubview(checkButton)
self.checkButton.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
self.checkButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
self.checkButton.widthAnchor.constraint(equalToConstant: 40).isActive = true
self.checkButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
self.checkButton.addTarget(self, action: #selector(checkButtonTapped), for: .touchUpInside)
// add label
self.contentView.addSubview(label)
self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 70).isActive = true
self.label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
#objc func checkButtonTapped(){
self.checkButton.setBackgroundImage(UIImage(named: "boxChecked"), for: .normal)
self.checkButton.alpha = 0
self.checkButton.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
UIView.animate(withDuration: 0.3) {
self.checkButton.alpha = 1
self.checkButton.transform = CGAffineTransform.identity
}
}
}
You can access the tableView and the indexPath within the cell with this extension:
extension UITableViewCell {
var tableView: UITableView? {
return (next as? UITableView) ?? (parentViewController as? UITableViewController)?.tableView
}
var indexPath: IndexPath? {
return tableView?.indexPath(for: self)
}
}
With the help of this other extension:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
So then you can delete the cell as usual:
tableView!.deleteRows(at: [indexPath!], with: .automatic)
Note that the tableView should be responsible for managing cells.
When you are creating the cell and setting the button selector all you need to set button tag and selector like this:
self.checkButton.tag = indexPath.row
self.checkButton.addTarget(self, action: #selector(checkButtonTapped(sender:)), for: .touchUpInside)
and make change this in selector:
#objc func checkButtonTapped(sender : UIButton){
let index = sender.tag
self.tableView.deleteRows(at: [index], with: .automatic)
//Add all your code here..
}
Thanks
Change your checkButtonTapped whit this
#objc func checkButtonTapped() {
self.checkButton.setBackgroundImage(UIImage(named: "boxChecked"), for: .normal)
self.checkButton.alpha = 0
self.checkButton.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
UIView.animate(withDuration: 0.3) {
self.checkButton.alpha = 1
self.checkButton.transform = CGAffineTransform.identity
}
if let tableView = self.superview as? UITableView {
let indexPath = tableView.indexPath(for: self)
tableView.dataSource?.tableView!(tableView, commit: .delete, forRowAt: indexPath!)
}
}
And add the next method to your UITableViewDelegate implementation
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Deleted")
self.texts.remove(at: indexPath.row)
self.tableview.deleteRows(at: [indexPath], with: .automatic)
}
}
A swifty way is a callback closure.
In the cell add a property
var callback : ((UITableViewCell) -> Void)?
In the action call the callback and pass the cell
#objc func checkButtonTapped(){
...
callback?(self)
}
In the controller in cellForRowAt assign a closure to the callback property
cell.callback = { cell in
let indexPath = tableView.indexPath(for: cell)!
// insert here a line to remove the item from the data source array.
tableView.deleteRows(at: [indexPath], with: .automatic)
}
This native Swift solution is much more efficient than assigning tags, objective-c-ish target/action or cumbersome view hierarchy math.
I have checkbox and label inside a tableview and when we click checkbox the price present in label in each cell of tableview should add to another label which is present in another view
#IBAction func checkUncheckButtonAction(_ sender: UIButton) {
if let cell = sender.superview?.superview as? PrepaidPageTableViewCell
{
let indexPath = tableviewOutlet.indexPath(for: cell)
if cell.checkUncheckButtonOutlet.isSelected == false
{
cell.checkUncheckButtonOutlet.setImage(#imageLiteral(resourceName: "checked_blue"), for: .normal)
cell.checkUncheckButtonOutlet.isSelected = true
viewHeightConstraint.constant = 65
cell.amountOutlet.text = "₹ "+amount_receivable_from_customerArray[indexPath!.row]
isPrepaidOrder = false
tableviewOutlet.reloadData()
} else {
cell.checkUncheckButtonOutlet.setImage(#imageLiteral(resourceName: "unchecked_blue"), for: .normal)
cell.checkUncheckButtonOutlet.isSelected = false
self.viewHeightConstraint.constant = 0
tableviewOutlet.reloadData()
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PrepaidPageTableViewCell") as! PrepaidPageTableViewCell
cell.customerNameOutlet.text = buyer_nameArray[indexPath.row]
cell.deliverydateOutlet.text = "Delivery Date:\(dispatch_dateArray[indexPath.row])"
cell.amountOutlet.text = "₹\(amount_receivable_from_customerArray[indexPath.row])"
cell.dispatchidoutlet.text = "Dispatch ID: \(id_dispatch_summaryArray[indexPath.row])"
cell.dispatchdateOutlet.text = "Dispatch Date:\(dispatch_dateArray[indexPath.row])"
cell.checkUncheckButtonOutlet.setImage(#imageLiteral(resourceName: "unchecked_blue"), for: .normal)
cell.selectionStyle = .none
return cell
}