Can not validate if textfield is empty - ios

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

Related

When I change the text color of UITextField inside a UITableView, the color has applied to another UITextfield when I scroll

I really don't understand why this happen. I'm changing the text color of a UITextField that is inside a UITableView if the text has changed but when I scroll down or up the tableView other UITextFields change the text color.
How can solve this problem?
Here is my code and some screenshot of the problem:
import Foundation
import UIKit
private let reuseIdentifier = "ApcGenericCell"
private let portIdentifier = "ApcPortCell"
private let addressIdentifier = "ApcAddressCell"
class MyViewController: UIViewController {
private var tableView: UITableView!
private var bottomConstraint: NSLayoutConstraint!
private var newBottomConstraint: NSLayoutConstraint!
var apc = [Apc]()
override func viewDidLoad() {
super.viewDidLoad()
// configure the table view for the left menu
configureTableView()
}
private func configureTableView() {
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 60
tableView.register(UINib(nibName: "ApcGeneric", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
tableView.register(UINib(nibName: "ApcPortAt", bundle: nil), forCellReuseIdentifier: portIdentifier)
tableView.register(UINib(nibName: "ApcIpAddress", bundle: nil), forCellReuseIdentifier: addressIdentifier)
self.view.addSubview(tableView)
tableView.backgroundColor = .clear
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: view.frame.height / 8).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: view.frame.width / 8).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -(view.frame.width / 8)).isActive = true
bottomConstraint = tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -(view.frame.height / 8))
bottomConstraint.isActive = true
tableView.alwaysBounceVertical = false
tableView.tableFooterView = UIView(frame: .zero)
// register for notifications when the keyboard appears and disappears:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(note:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(note:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(receivedMessage), name: Notification.Name("ReceivedMessage"), object: nil)
apc = ApcSection.emptySection()
self.tableView.reloadData()
}
#objc func receivedMessage() {
DispatchQueue.global(qos: .default).async {
ApcSection.fetchData(map: self.apcConfig, { (apcResult) in
self.apc = apcResult
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
}
// Handle keyboard frame changes here.
// Use the CGRect stored in the notification to determine what part of the screen the keyboard will cover.
// Adjust our table view's bottomAnchor so that the table view content avoids the part of the screen covered by the keyboard
#objc func keyboardWillShow(note: NSNotification) {
// read the CGRect from the notification (if any)
if let newFrame = (note.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if bottomConstraint.isActive {
bottomConstraint.isActive = false
newBottomConstraint = tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -newFrame.height)
newBottomConstraint.isActive = true
tableView.updateConstraints()
}
}
}
// User dismiss the keyboard
#objc func keyboardWillHide(note: NSNotification) {
newBottomConstraint.isActive = false
bottomConstraint.isActive = true
tableView.updateConstraints()
}
#objc func textHasChanged(sender: UITextField) {
let cell = sender.superview?.superview as! UITableViewCell
let indexPath = tableView.indexPath(for: cell)
if let index = indexPath?.row {
if let _ = apc[index] {
// change textColor if the value has been changed
if sender.text != apc[index]!) {
sender.textColor = .systemRed
} else {
sender.textColor = .myBlue
}
}
}
}
}
extension MyViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return apc.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: portIdentifier, for: indexPath) as! ApcPortAt
cell.selectionStyle = .none
return cell
} else if indexPath.row == 7 || indexPath.row == 9 {
let cell = tableView.dequeueReusableCell(withIdentifier: addressIdentifier, for: indexPath) as! ApcIpAddress
cell.selectionStyle = .none
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ApcGeneric
cell.value.text = apc[indexPath.row]
cell.value.delegate = self
cell.value.addTarget(self, action: #selector(textHasChanged(sender:)), for: .editingChanged)
cell.selectionStyle = .none
return cell
}
}
}
Normal View
Editing
Scroll down after editing
Return to the top
You have 2 possible solutions
1. Apply the changes in cellForRowAt of UITableViewDataSource delegate.
2. Subclass UITableViewCell and override prepareForReuse() which within it you can make your updates. And don't forget to register the cell subclass with your table view.
I consider solution #1 much easier
In UITableView dequeueReusableCell- Each UITableViewCell will be reused several times with different data(image).
In your case, When you scrolled, cell at IndexPath(row: x, section: 0) was reused by another cell that is displaying at the top. Ex: cell with red text -> this cell x have red text, because you did not reset the text color to it
Solution:
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ApcGeneric
cell.value.text = apc[indexPath.row]
if index == selectedIndex { // condition to red text
cell.value.textColor = .systemRed
} else {
cell.value.textColor = .myBlue
}
cell.value.delegate = self
cell.value.addTarget(self, action: #selector(textHasChanged(sender:)), for: .editingChanged)
cell.selectionStyle = .none
return cell

Dynamically resize TableViewController Cell

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.

Swift 4: Button triggers the same action in another UITableViewCell

I have a cell with 2 buttons (for listening and speech recognition), label, and textfield. What I am trying to achieve is when the speech recognition button is selected the user speaks the content that is displayed in a label.
My issue with this is that listening button works fine according to the indexPath.row, but the speaking button doesn't. As when it is active, the button in another cell is becoming active too. And it records the same in those cells.
You can see the picture of what I am talking about here
The methods for listening (which is audio synthesizer) and speech recognition are in the UITableViewCell. I have tried all the solutions I could find online, none of them did the trick. Have tried
protocol RepeatCellDelegate: class {
func buttonTapped(cell: RepeatCell)
}
but the problem remains the same. Also, have created another project and instead of using the button to do speech recognition I just used direct textField input, still the same problem occurs.
Button in TableViewCell class:
#IBAction func speakButtonPressed(_ sender: Any) {
self.delegate?.buttonTapped(cell: self)
}
My cellForRowAt indexPath:
let cell = tableView.dequeueReusableCell(withIdentifier: "RepeatCell") as! RepeatCell
cell.delegate = self
cell.conditionlabel.text = repeatTask[indexPath.row].conditionLabel
return cell
The buttonTapped function which detects the cell index and record speech input. It prints right cell index after the button is tapped, but the action gets triggered in another cell too.
func buttonTapped(cell: RepeatCell) {
guard let indexPath = self.repeatTV.indexPath(for: cell) else {
return
}
cell.speakButton.isSelected = !cell.speakButton.isSelected
if (cell.speakButton.isSelected){
self.recordAndRecognizeSpeech()
} else {
audioEngine.inputNode.removeTap(onBus: 0)
recognitionTask?.cancel()
}
print("Button tapped on row \(indexPath.row)")
}
// the speech input recognizer function:
// variables for speech recognizer
let audioEngine = AVAudioEngine()
let speechRecognizer: SFSpeechRecognizer? = SFSpeechRecognizer(locale: Locale.init(identifier: "en-US"))
let request = SFSpeechAudioBufferRecognitionRequest()
var recognitionTask: SFSpeechRecognitionTask?
// speech function
func recordAndRecognizeSpeech(){
let node = audioEngine.inputNode
let recordingFormat = node.outputFormat(forBus: 0)
node.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, _ in
self.request.append(buffer)
}
audioEngine.prepare()
do {
try audioEngine.start()
} catch {
return print(error)
}
guard let myRecognizer = SFSpeechRecognizer() else {
return
}
if !myRecognizer.isAvailable {
return
}
recognitionTask = speechRecognizer?.recognitionTask(with: request, resultHandler: { (result, error) in
if result != nil { //
if let result = result{
let cell = self.repeatTV.dequeueReusableCell(withIdentifier: "RepeatCell") as! RepeatCell
let bestString = result.bestTranscription.formattedString
if cell.speakButton.isSelected == true {
cell.userInput.text = bestString
}
}else if let error = error{
print(error)
}
}
})
}
I get data from a local JSON file and this is a model:
struct RepeatTask: Codable {
let name: String
let label: String
let conditionWord: String
}
Perhaps someone could help me with this?
There isn't enough code here to re-create your issue, in the future please provide a Minimal, Complete, and Verifiable example. Unfortunately, no one can offer you a effective solution to help you resolve the issue if they cannot re-create the problem.
However I believe I understand what you are trying to accomplish:
A Model Object i.e. a struct.
A Protocol with a default implementation that is the same for all cells.
A TableViewCell class conforming to the protocol that calls the protocol's methods.
A TableViewDelegate and Datasource to manage the objects from 1.
Consider the following:
import UIKit
/// 1.
/// Data model for "Repeat Cell Objects"
struct RepeaterModel {
var outputText:String?
var inputAudio:Data?
}
/// 2.
/// Allows a cell to delegate listening and repeating (speaking)
protocol RepeatableCell {
func listen()
func speak()
}
// Extend your protocol to add a default implementation,
// that way you can just confrom to the protocol
// without implementing it every time, in every cell class.
extension RepeatableCell {
func listen() {
print("default implementation for listen")
}
func speak(){
print("default implementation for speak")
}
}
/// 3.
final class RepeatCell: UITableViewCell, RepeatableCell {
// MARK: - Properties
var model:RepeaterModel? {
didSet {
DispatchQueue.main.async {
self.titleLabel.text = self.model?.outputText
}
}
}
// MARK: - Views
lazy var listenButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Listen", for: .normal)
btn.addTarget(self, action: #selector(activateListen), for: .touchUpInside)
btn.setTitleColor(.white, for: .normal)
btn.backgroundColor = .blue
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
lazy var speakButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Speak", for: .normal)
btn.addTarget(self, action: #selector(activateSpeak), for: .touchUpInside)
btn.setTitleColor(.white, for: .normal)
btn.backgroundColor = .green
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
let titleLabel: UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.textColor = .black
l.textAlignment = .center
l.text = "No Text"
return l
}()
//MARK: - Initializers
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Class Methods
func setup() {
self.contentView.addSubview(listenButton)
self.contentView.addSubview(speakButton)
self.contentView.addSubview(titleLabel)
let spacing: CGFloat = 25.0
//Listen top left
listenButton.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: spacing).isActive = true
listenButton.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: spacing).isActive = true
listenButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
listenButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
// title label, center top.
titleLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: spacing).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: self.listenButton.trailingAnchor, constant: spacing).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: self.speakButton.leadingAnchor, constant: -spacing).isActive = true
titleLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
//Speak top right
speakButton.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: spacing).isActive = true
speakButton.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -spacing).isActive = true
speakButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
speakButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
#objc func activateListen() {
print("listen was pressed! on cell \(self.model?.outputText ?? "No Text")")
/// The user wants to listen
// call the delegate method..
listen()
// use self.model?.outputText
}
#objc func activateSpeak() {
print("Speak was pressed! on cell \(self.model?.outputText ?? "No Text")")
/// The user is speaking, record audio
// call the delegate method..
speak()
//self.model?.inputAudio = somedata
}
}
/// 4.
class ViewController: UITableViewController {
// Array of your model objects
var objects:[RepeaterModel] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(RepeatCell.self, forCellReuseIdentifier: "Repeat")
// create or fetch model objects
let items = [
RepeaterModel(outputText: "1st Cell", inputAudio: nil),
RepeaterModel(outputText: "2nd Cell", inputAudio: nil),
RepeaterModel(outputText: "3rd Cell", inputAudio: nil),
RepeaterModel(outputText: "4th Cell", inputAudio: nil),
RepeaterModel(outputText: "5th Cell", inputAudio: nil),
RepeaterModel(outputText: "6th Cell", inputAudio: nil),
RepeaterModel(outputText: "8th Cell", inputAudio: nil),
RepeaterModel(outputText: "9th Cell", inputAudio: nil),
RepeaterModel(outputText: "10th Cell", inputAudio: nil),
RepeaterModel(outputText: "11th Cell", inputAudio: nil),
RepeaterModel(outputText: "12th Cell", inputAudio: nil)
]
self.objects += items
}
//MARK: - TableView Methods
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// 25 top spacing + 50 view element width + 25 bottom spacing
return 100.0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = self.tableView.dequeueReusableCell(withIdentifier: "Repeat") as? RepeatCell {
cell.model = objects[indexPath.row]
// other cell stuff
return cell
}
return UITableViewCell()
}
}
Pressing "Listen" then "Speak" on each cell going downward yields this output:

Inset a new section in UICollectionView with new type of Cell

I am working on a chat app. I am using UICollectionView to display text and different type of text. I have 3 type of cells. In the first section, I am loading the first type of cell. in second, second type and in third, third type of cell loading. I have a button text field. When i click send button in text field i want to create new section of type first cell. I don't know how to create. Please help :)
Here is my Code:
// ChatLogController.swift
// Blubyn
//
// Created by JOGENDRA on 02/02/18.
// Copyright © 2018 Jogendra Singh. All rights reserved.
//
import UIKit
fileprivate enum DefaultConstants {
static let sendButtonImageName: String = "send-icon"
static let voiceButtonImageName: String = "microphone"
}
enum Cells {
case chatCell
case oneWayFlight
case twoWayFlight
}
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate var sideBar = SideBar()
lazy var inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Send a message..."
textField.delegate = self
return textField
}()
let textView: UIView = {
let textFieldview = UIView()
textFieldview.backgroundColor = UIColor.white
textFieldview.translatesAutoresizingMaskIntoConstraints = false
return textFieldview
}()
let cellId = "cellId"
let oneWayCellId = "oneWayFlightCell"
let twoWayCellId = "twoWayFlightCell"
var numberOfSections: Int = 0
var numberOfItemsInASection: Int = 0
var cellType: Cells?
var keyboardHeight: CGFloat = 0.0
var textViewBottomAnchor: NSLayoutConstraint?
var userMessages: [String] = ["Hey there!", "I want to book flight for Pokhara, Nepal", "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s", "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.", "Book a hotel for me near lakeside", "book returning flight"]
var systemMessages: [String] = ["Hello Jogendra!", "Pluto at your service - your guide and concierge to help you with the planning ticket booking and finding new and exotic places.", "Go on! Try me out. Won't take long. I promise."]
lazy var messages: [String] = systemMessages + userMessages
override func viewDidLoad() {
super.viewDidLoad()
setupInputComponents()
sideBarSetup()
collectionView?.contentInset = UIEdgeInsets(top: 8.0, left: 0, bottom: 52.0, right: 0)
collectionView?.backgroundColor = UIColor.chatbackgroundColor
// Register Chat Cell
collectionView?.register(ChatMessagesCell.self, forCellWithReuseIdentifier: cellId)
// Regsiter One Way Flight Cell
let oneWayFlightCellNib = UINib(nibName: "OneWayFlightViewCell", bundle: nil)
collectionView?.register(oneWayFlightCellNib, forCellWithReuseIdentifier: oneWayCellId)
// Register Two Way Flight Cell
let twoWayFlightCellNib = UINib(nibName: "TwoWayFlightViewCell", bundle: nil)
collectionView?.register(twoWayFlightCellNib, forCellWithReuseIdentifier: twoWayCellId)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
// Register Notification, To know When Key Board Appear.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: Notification.Name.UIKeyboardWillShow, object: nil)
// Register Notification, To know When Key Board Hides.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// De register the notifications
NotificationCenter.default.removeObserver(self)
}
fileprivate func setupInputComponents() {
// Text View setups
view.addSubview(textView)
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
textView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
textViewBottomAnchor = textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
textViewBottomAnchor?.isActive = true
let voiceButton = UIButton(type: .system)
let voiceButtonImage = UIImage(named: DefaultConstants.voiceButtonImageName)
voiceButton.setImage(voiceButtonImage, for: .normal)
textView.addSubview(voiceButton)
voiceButton.translatesAutoresizingMaskIntoConstraints = false
voiceButton.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true
voiceButton.trailingAnchor.constraint(equalTo: textView.trailingAnchor).isActive = true
voiceButton.bottomAnchor.constraint(equalTo: textView.bottomAnchor).isActive = true
voiceButton.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
let sendButton = UIButton(type: .system)
let sendButtonImage = UIImage(named: DefaultConstants.sendButtonImageName)
sendButton.setImage(sendButtonImage, for: .normal)
textView.addSubview(sendButton)
sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside)
sendButton.translatesAutoresizingMaskIntoConstraints = false
sendButton.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true
sendButton.trailingAnchor.constraint(equalTo: voiceButton.leadingAnchor).isActive = true
sendButton.bottomAnchor.constraint(equalTo: textView.bottomAnchor).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
textView.addSubview(inputTextField)
inputTextField.translatesAutoresizingMaskIntoConstraints = false
inputTextField.leadingAnchor.constraint(equalTo: textView.leadingAnchor, constant: 4.0).isActive = true
inputTextField.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -4.0).isActive = true
inputTextField.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true
inputTextField.bottomAnchor.constraint(equalTo: textView.safeAreaLayoutGuide.bottomAnchor).isActive = true
let separator = UIView()
separator.backgroundColor = UIColor.black.withAlphaComponent(0.4)
textView.addSubview(separator)
separator.translatesAutoresizingMaskIntoConstraints = false
separator.leadingAnchor.constraint(equalTo: textView.leadingAnchor).isActive = true
separator.trailingAnchor.constraint(equalTo: textView.trailingAnchor).isActive = true
separator.topAnchor.constraint(equalTo: textView.topAnchor).isActive = true
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
}
// MARK: - Keyboard Events
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo![UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
keyboardHeight = keyboardRectangle.height
}
let keyboardDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double
textViewBottomAnchor?.constant = -keyboardHeight
UIView.animate(withDuration: keyboardDuration!, animations: {
self.view.layoutIfNeeded()
})
}
#objc func keyboardWillHide(_ notification: Notification) {
let keyboardDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double
textViewBottomAnchor?.constant = 0
UIView.animate(withDuration: keyboardDuration!, animations: {
self.view.layoutIfNeeded()
})
}
// MARK: - Collection View Methods
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0:
return messages.count
case 1:
numberOfItemsInASection = 5
return numberOfItemsInASection
case 2:
return 5
default:
numberOfItemsInASection = 5
return numberOfItemsInASection
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatMessagesCell
let message = messages[indexPath.item]
cell.chatTextView.text = message
// Align chat cell according
if userMessages.contains(message) {
cell.bubbleView.backgroundColor = UIColor.white
cell.chatTextView.textColor = UIColor.black
cell.bubbleViewRightAnchor?.isActive = true
cell.bubbleViewLeftAnchor?.isActive = false
} else {
cell.bubbleView.backgroundColor = UIColor.chatThemeColor
cell.chatTextView.textColor = UIColor.white
cell.bubbleViewRightAnchor?.isActive = false
cell.bubbleViewLeftAnchor?.isActive = true
}
//Modify the width accordingly
cell.bubbleWidthAnchor?.constant = estimateFrameForText(text: message).width + 32.0
if indexPath.section == 0 {
cellType = Cells.chatCell
return cell
} else if indexPath.section == 1 {
let oneWayFlightCell = collectionView.dequeueReusableCell(withReuseIdentifier: oneWayCellId, for: indexPath) as! OneWayFlightViewCell
cellType = Cells.oneWayFlight
return oneWayFlightCell
} else {
let twoWayFlightCell = collectionView.dequeueReusableCell(withReuseIdentifier: twoWayCellId, for: indexPath) as! TwoWayFlightViewCell
cellType = Cells.twoWayFlight
return twoWayFlightCell
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
numberOfSections = 3
return numberOfSections
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var cellHeight: CGFloat = 80.0
let text = messages[indexPath.item]
cellHeight = estimateFrameForText(text: text).height + 20.0
if indexPath.section == 1 {
cellHeight = 112
} else if indexPath.section == 2 {
cellHeight = 201
}
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
inputTextField.resignFirstResponder()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
//{top, left, bottom, right}
return UIEdgeInsetsMake(10, 0, 10, 0)
}
private func estimateFrameForText(text: String) -> CGRect {
let size = CGSize(width: 300.0, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17.0)], context: nil)
}
#objc fileprivate func didTapSend() {
if let enteredText = inputTextField.text, !enteredText.isEmpty {
messages.append(enteredText)
userMessages.append(enteredText)
}
collectionView?.reloadData()
inputTextField.text = nil
}
fileprivate func sideBarSetup() {
sideBar = SideBar(sourceView: self.view, menuItems: ["Chat", "Hot Deals", "My Trips", "Experiences", "Settings", "Profile"])
sideBar.delegate = self
}
#IBAction func didTapMenu(_ sender: Any) {
sideBar.showSideBar(!sideBar.isSideBarOpen)
}
#IBAction func didTapHelp(_ sender: Any) {
}
}
extension ChatLogController: SideBarDelegate {
func SideBarDidSelectButtonAtIndex(_ index: Int) {
switch index {
case 0:
sideBar.showSideBar(!sideBar.isSideBarOpen)
case 1:
sideBar.showSideBar(!sideBar.isSideBarOpen)
case 2:
sideBar.showSideBar(!sideBar.isSideBarOpen)
case 3:
sideBar.showSideBar(!sideBar.isSideBarOpen)
case 4:
let settingStoryboard = UIStoryboard(name: "Main", bundle: nil)
let settingsViewController = settingStoryboard.instantiateViewController(withIdentifier: "settingvc")
self.navigationController?.pushViewController(settingsViewController, animated: true)
case 5:
let profileStoryboard = UIStoryboard(name: "Main", bundle: nil)
let profileViewController = profileStoryboard.instantiateViewController(withIdentifier: "profilevc")
self.navigationController?.pushViewController(profileViewController, animated: true)
default:
break
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
inputTextField.resignFirstResponder()
inputTextField.endEditing(true)
}
}
extension ChatLogController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
inputTextField.endEditing(true)
return true
}
}
Designs:
See Screens Section of [this link][1].
Achieved Designs:
[![screenshot1][2]][2]
[![screenshot2][3]][3]
[![screenshot3][4]][4]
[![screenshot4][5]][5]
[1]: https://blubyn.com/
[2]: https://i.stack.imgur.com/S9ZLX.png
[3]: https://i.stack.imgur.com/wLvAu.png
[4]: https://i.stack.imgur.com/nBKbH.png
[5]: https://i.stack.imgur.com/Sota4.png

UICollectionViewCell's UiTextField, overlapping in UICollectionViewController

I am trying to design a multistep sign-up menu. For this purpose I am using UICollectionViewController with screen size cells. In these cells, I have a UITextView to ask questions and a UITextField to collect the answers. I also have a Page object for passing in information from uicollectionviewcontroller upon setting.
The problem I'm having now is that after every 3rd page my textField input from 3 pages ago repeats, instead of showing the placeholder. I have noticed yet another problem, the cells seem to be instantiating just 3 times, and not 6 times for how many pages I have. The instantiation order is very odd too. At first it does it once, then upon button click, twice more, then never again.
How can fix this, I am really struggling with this and I have no idea what's going wrong.
This is my code:
import UIKit
class OnboardingPageViewCell: UICollectionViewCell{
override init(frame: CGRect) {
super.init(frame: frame)
print("made a page")
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var oboardingPage = NewOnboardingPage() {
didSet{
reload()
}
}
private var questionTextField: UITextView = {
var q = UITextView()
q.textColor = UIColor.white
q.textAlignment = .left
q.font = UIFont(name: "Avenir-Black", size: 25)
q.isEditable = true
q.isScrollEnabled = false
q.backgroundColor = UIColor.black
q.translatesAutoresizingMaskIntoConstraints = false
print("made aquestion field")
return q
}()
private var answerField : CustomTextField = {
let tf = CustomTextField.nameField
print("made an answer field")
return tf
}()
private func setupView(){
backgroundColor = UIColor.white
addSubview(questionTextField)
questionTextField.topAnchor.constraint(equalTo: topAnchor, constant: 120).isActive = true
questionTextField.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
questionTextField.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.90).isActive = true
questionTextField.heightAnchor.constraint(equalToConstant: 90).isActive = true
addSubview(answerField)
answerField.topAnchor.constraint(equalTo: questionTextField.bottomAnchor, constant: 20).isActive = true
answerField.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
answerField.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.90).isActive = true
answerField.heightAnchor.constraint(equalToConstant: 90).isActive = true
}
private func reload(){
questionTextField.text = oboardingPage.question
answerField.placeholder = oboardingPage.answerField
}
}
class NewOnboardingPage {
var question : String?
var answerField : String?
}
import UIKit
class SignUpController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
private let cellId = "cellId"
private var pages = [NewOnboardingPage]()
override func viewDidLoad() {
super .viewDidLoad()
setupSignUpControllerView()
addPages()
}
private func addPages(){
let namePage = NewOnboardingPage()
namePage.question = "What's your name?"
namePage.answerField = "What's your name?"
pages.append(namePage)
let birthDayPage = NewOnboardingPage()
birthDayPage.question = "When's your birthdate?"
birthDayPage.answerField = "When's your birthdate?"
pages.append(birthDayPage)
let userNamePage = NewOnboardingPage()
userNamePage.question = "Choose a user name."
userNamePage.answerField = "Choose a user name."
pages.append(userNamePage)
let passWordPage = NewOnboardingPage()
passWordPage.question = "Set a password"
passWordPage.answerField = "Set a password"
pages.append(passWordPage)
let emailAuthPage = NewOnboardingPage()
emailAuthPage.question = "What's your email?"
emailAuthPage.answerField = "What's your email?"
pages.append(emailAuthPage)
let phoneNumberPage = NewOnboardingPage()
phoneNumberPage.question = "What's your phone number?"
phoneNumberPage.answerField = "What's your phone number?"
pages.append(phoneNumberPage)
}
private func setupSignUpControllerView(){
collectionView?.backgroundColor = .white
collectionView?.register(OnboardingPageViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.isPagingEnabled = true
collectionView?.isScrollEnabled = true
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
}
view.addSubview(nextButton)
nextButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 400).isActive = true
nextButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
nextButton.widthAnchor.constraint(equalToConstant: 250).isActive = true
nextButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
}
private let nextButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.RED
button.setTitle("next", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont(name: "Avenir-Black", size: 25)
button.layer.cornerRadius = 30
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(turnNextPage), for: .touchUpInside)
return button
}()
#objc private func turnNextPage() {
let visibleItems: NSArray = collectionView?.indexPathsForVisibleItems as! NSArray
let currentItem: IndexPath = visibleItems.object(at: 0) as! IndexPath
let nextItem: IndexPath = IndexPath(item: currentItem.item + 1, section: 0)
if nextItem.row < pages.count {
collectionView?.scrollToItem(at: nextItem, at: .left, animated: true)
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! OnboardingPageViewCell
cell.oboardingPage = pages[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
import UIKit
class CustomTextField: UITextField, UITextFieldDelegate {
convenience init() {
self.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
self.allowsEditingTextAttributes = false
self.autocorrectionType = .no
self.tintColor = UIColor.RED
self.translatesAutoresizingMaskIntoConstraints = false
}
override func willMove(toSuperview newSuperview: UIView?) {
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
editingChanged(self)
}
#objc func editingChanged(_ textField: UITextField) {
guard let text = textField.text else { return }
textField.text = String(text.prefix(30))
}
override func selectionRects(for range: UITextRange) -> [Any] {
return []
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(paste(_:)) ||
action == #selector(cut(_:)) ||
action == #selector(copy(_:)) ||
action == #selector(select(_:)) ||
action == #selector(selectAll(_:)){
return false
}
return super.canPerformAction(action, withSender: sender)
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! OnboardingPageViewCell
cell.answerField.text = nil
cell.oboardingPage = pages[indexPath.item]
return cell
}
1- textFeild showing same data instead of placeholder bacause of cell dequeuing so you must hook these properties and clear their content in cellForRowAt
2- Instantiation is 3 not 6 aslo cell dequeuing
Solve:
Add two properties to your model NewOnboardingPage name them currentQuestion and currentAnswer and as the user inputs and scroll to next page save them in the modelarray that you should make global to be accessed indside cell and outside set these values to the textfeild and textView as you scroll in cellForRowAt

Resources