XCUITest unexpected behaviour - ios

I have a simply project with a tableView and a detail vc. The tableView displays 20 rows with "cell (n)" text and the detail view shows a label with the cell pressed.
I want to assert given a tap into a cell i get the text found in the tableView in the detail vc label. So for instance if i tap of the cell 3, which contains "cell 3" i want to get this text, instead of hardcoding it, and assert that i can find this text in the detail vc.
func testCanNavigateToDetailVCWithTheTextFromCell() {
let labelInTableView = app.staticTexts["cell 3"]
labelInTableView.tap()
let labelInDetailVC = app.staticTexts[labelInTableView.label]
XCTAssertTrue(labelInDetailVC.exists)
}
This seems working. But i want to do this:
func testCanNavigateToDetailVCWithTheTextFromCellV2() {
let cell = app.tables.element.cells.element(boundBy: 3) //Get the third cell of the unique tableView
cell.tap()
let textFromPreviousCell = cell.staticTexts.element(boundBy: 0).label //Since is a "Basic" cell it only has one label.
//I will also want to set an accessilibtyIdentifier to the label and access it via cell.staticTexts["id"].label
let labelInDetailVC = app.staticTexts[textFromPreviousCell]
XCTAssertTrue(labelInDetailVC.exists)
}
I set up a project with this issue here

The problem is that you are trying to get the text of the cell after tapping it. This means that the cell is no longer on the screen (the new screen has appeared). All you need to do is change the order of the lines cell.tap() and let textFromPreviousCell = cell.staticTexts.element(boundBy: 0).label. See the new function below:
func testCanNavigateToDetailVCWithTheTextFromCellV2() {
let cell = app.tables.element.cells.element(boundBy: 3) //Get the third cell of the unique tableView
let textFromPreviousCell = cell.staticTexts.element(boundBy: 0).label //Since is a "Basic" cell it only has one label.
cell.tap()
let labelInDetailVC = app.staticTexts[textFromPreviousCell]
XCTAssertTrue(labelInDetailVC.exists)
}

Related

UIUISegmentedControls overlap inside UITableView cells after scrolling

I have UITableView with some cells (settings).
Inside the cell I create a UISegmentedControl programmatically:
class SettingsCell: UITableViewCell {
var segDist : UISegmentedControl? = nil ...
var sectionType: SectionType?{
didSet{
guard let sectionType = sectionType else {return}
textLabel?.text = sectionType.description
switch sectionType.containsSegmented{
case .DISTANCE:
if (segDist == nil){
segDist = makeSegmentedControl(items:["yrds","m"], segmentIndex: SettingsManager.shared.getRangeUnitIndex() - 1, action: #selector(handleSegmentedControlActionDistance))}
and so on for every cell. Everything works fine
, but if l scroll down and up several times (5-10), my segmentedControls can overlap:
Seems like UISegmentedControl from second row is created in the first and vice versa. How can I avoid it?
Create segment control in your cell UI or code so that it will get
refresh for each new cell as donMag mention
if you need help in that setup please share your implementation

How to save the text in textfield in swift?

I have a tableview consisting of 2 cells and each cell has a textfield. I'd like to hold the text in textfield in an another variable and append it into an array but it just save the second cell's textfield's text into the variable and doesn't append the array.
Here is cellForRowAt code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == queryTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: queryCellReuseIdentifier,
for: indexPath) as! queryCell
cell.selectionStyle = UITableViewCell.SelectionStyle.gray
queryTextField = UITextField(frame: CGRect(x: 20, y: 0, width: 300, height: 20))
queryTextField.delegate = self
queryTextField.placeholder = "Soruyu buraya giriniz"
queryTextField.font = UIFont.systemFont(ofSize: 15)
cell.addSubview(queryTextField)
return cell
}
Here is my related function :
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = queryTextField.text!
queryArray.append(temp)
print(temp)
}
if your project is non-storyboard approach, then I would say, it would be more easier and flexible to take control of each type and data and action since sometimes things not quite flexible with storyboard only.. . as you create, even a single UIElement, UI conforms to those properties, methods and actions which you define, means fully customized code - custom defined code base..
now to your question, according to what you did, probably, your textfield text is overwritten to blank, losing the previously entered value every time the cell is dequeued, cell is generated fresh again for use every time " dequeue cell " method is executed, if you want to persist your previous value, I would say, " remind the dequeued cell that it had some value before being re- dequeued again " means:
declare your global array or dict.
dequeue your cell and setup your cell properties
3 after cell is dequeued, do something like this, and your cells previous value is saved even after the cell is dequeued.
example, common approach:
struct DefaultInfo {
var string: String?
var type: SomeData? //optional
}
let array: [DefaultInfo] = []
var array = self.array[indexPath.row]
// after cell is dequeued:
switch array.type {
case .someCase:
let string = cell.textField.text
array.string = string
cell.textField.text = array.string
//print(array.string!)
// now you should be able to see your input after cell is
//renewed.. . try it.. .
default: break
}
It seems like you have an independant variable queryTextField which is overriden when you create the second cell.
Also, in textFieldDidEndEditing try accessing textField instead of queryTextField, like this:
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = textField.text!
queryArray.append(temp)
print(temp)
}
Responding to this:
I need every cell has its textfield and I need to be able to save
their texts into an array. After that I am going to send them to web
service. To sum up, they will be my parameters.
You don't need to have global queryTextField just for that. You can remove this variable, if that's its only goal.
Do you need to send web request on some trigger? Like button tap. I assume, yes.
Since theoretically not all your cells are visible at the same time (e.g. when thay do not fit the screen), it's bad idea to try and track texts in text fields. Instead, you need some kind of model to store all texts (paired with indeces, maybe). The simplest would be dictionary where key is cell identifier (e.g. indexPath) and value is the text.
In textFieldDidEndEditing you can report the changes to the view controller. For this you need to assign the cell as the delegate for its text field. And view controller - as the delegate for the cell. In textFieldDidEndEditing cell would be calling view controller delegate method to report the text change, passing (for example) self and text as parameters. View controller would then be able to find its index path and store the text in the model (dictionary).
On trigger (button click?) view controller will be able to build parameters from the model (dictionary).
Consider this pseudocode as a direction:
cellForRow {
...
cell = ...
let textField = ...
textField.delegate = cell
cell.addSubview(textField)
cell.delegate = self
}
In cell:
textFieldDidEndEditing(textField: UITextField) {
delegate?.report(text: textField.text ?? "", in: self)
}
In view controller:
report(text: String, in cell: UITableViewCell) {
let indexPath = tableView.indexPath(of: cell)
model[indexPath] = text
}

Proper way to get indexes of a UITableViewCell in response to a button click which sits in a tableview within tableview?

Screenshot of the app:
I have an 'x' button to delete a TableViewCell which is in a table within a table. On click of the button, I would like to remove the cell, so I need to know the 2 indexes of the button click, the row if the first table view, and then within that tableview the row of the cell which the button was clicked. All I have is the sender.
So to be a bit clearer, in the screenshot, if someone clicks the x under the ford fiesta, I need to get indexpath 0 for the "subtableview" and 1 for the tableview, and that way I know to delete this element from the table datasource.
I do it successfully by doing:
var cell = sender.superview
while (cell != nil) && !((cell?.isKind(of: CustomCell.self))!) {
cell = cell?.superview
}
let tbl = cell?.superview as! UITableView
let indexPath = tbl.indexPath(for: (cell as? UITableViewCell)!
)
The stupid thing is I have to do it twice, once to find the index of the cell within the "sub"tableview, and then again to find the index of the "subtableview" within the tableview.
Is there a better way to do this? Isnt there a way to get the buttonClick to get the didSelectRowAt to fire and add the sender object to it (so I know that a button was clicked as opposed to the cell being selected)?
EDIT I forgot to mention that the first tableview opens and closes on click, so the main tableview has 2 different cell types, one closed (so no nested tableview) and then onselect of a row from that tableview, the cell is replaced with a detailed cell which has another tableview inside it, thats why sectioned tableview isnt a solution (to the best of my knowledge, I'm new here)
One way to do it is to use closures. You set up your cell with a closure and then call it. Pretty much like this:
class CellWithClosure: UITableViewCell {
var button: UIButton = UIButton()
var closureForButton: (Void) -> Void
func setupCell(closureForButton: #escaping (Void) -> Void) {
self.closureForButton = closureForButton
button.addTarget(self, action: #selector(buttonAction), for: UIControlEvents.touchUpInside)
}
#objc func buttonAction() {
closureForButton()
}
}

Is it possible to derive the indexPath if you know the row?

Okay, I'm going to try to break this down as simply as I am able. I have a tableView in a ViewController. I have two prototype cells for the table. I am reusing the cells multiple times to populate the table.
In one of the cells, I've added gesture recognizer to the label through which I'm making a textField visible on place of the label and hiding the label. Now I want the labels text to change to what I've entered in the textField when I'm done using the textField and hit the return key. So i implemented the UITextFieldDelegate protocol in the viewController. I've also added tags to each of the textFields in the cell so that I know what textField is returning and what row the textField is in.
Basically, what I want to know is if there is any way to get the indexPath if I already know the indexPath.row?
For the gesture recognizers, i was able to work around this issue by getting the indexPath from the tapped location:
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
let indexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(indexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
I need the indexPath so that I can refer to the elements contained within a cell. Can anyone offer any insights? Has anybody tried a similar approach? Is there any way I can access the cell by just using the row?
If you are able to get the indexPath inside the GestureMethod then you can create one instance property of type NSIndexPath store its value inside that Gesture's method and later used the indexPath inside textFieldShouldReturn delegate method, something like this.
var selectedIndexPath: NSIndexPath?
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
self.selectedIndexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(self.selectedIndexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
Now use this self.selectedIndexPath inside UITextFieldDelegate method.
Edit: From your question's comment you have told that you have just one Section so you can also create indexPath from that textField's tag this way.
func textFieldShouldReturn(textField: UITextField) -> Bool {
let indexPath = NSIndexPath(forRow: textField.tag, inSection: 0)
//Or You can use self.selectedIndexPath also
}
In case of single or multiple sections, the below code will work
In your cellForRowAtIndexPath, set the tag as below:-
let tag = indexPath.section*100 + indexPath.row
cell.savedUserInput.tag = tag
cell.userDetailTextfield.tag = tag
In your textfield delegate method, get the indexPath as follows:-
func genderTapped(sender: UITapGestureRecognizer) {
let textfieldObject = sender as! UITextField
let sectionTag = textfieldObject.tag % 100
let rowTag = textfieldObject.tag / 100
let indexPath = NSIndexPath(forRow: rowTag.tag, inSection: sectionTag)
}
Disclaimer: This is not an answer to the literal question asked here, but it might provide an simpler solution to OP's goal.
Unless you need to do something in addition to what you described in your question it seems to me that a much easier solution would be not to use labels at all but in stead just use an UITextField and set it's enabled property to false when you want it to act like an label.
You can subclass the UITextField if you need the styling to change when the mode changes.
If you know the row number which you are accessing and the section in which the row is, then use this code
let indexPath = NSIndexPath(forRow: row, inSection: section)
For accessing the cell corresponding to this indexPath, use
let cell = tableView.cellForRowAtIndexPath(indexPath) as! tableViewCell

IBOutlet is nil, but it is connected in storyboard (Swift 2)

I am trying to show some content in the cell of the UITableView. The program does reach the cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: EventTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("eventCell") as! EventTableViewCell
let date = self.eventArray[indexPath.row].startTime
let calendar = NSCalendar.currentCalendar()
let minutes = calendar.component(NSCalendarUnit.Minute, fromDate: date)
var minutesString: String
if (minutes == 0) {
minutesString = "00"
} else {
minutesString = String(calendar.component(NSCalendarUnit.Minute, fromDate: date))
}
let hours = calendar.component(NSCalendarUnit.Hour, fromDate: date)
//this next line of code works, i see cell text:
// cell.textLabel?.text = self.eventArray[indexPath.row].title + " - \(hours):\(minutesString)"
//these lines do not work, see empty cells:
cell.cellLable?.text = self.eventArray[indexPath.row].title + " - \(hours):\(minutesString)"
cell.textField?.text = self.eventArray[indexPath.row].notes
return cell
}
I have properly connected the outlets:
If I set breakpoint, the cell appears to be the EventTableViewCell, but both cellLabel and textField are nil:
My table view connections look like this:
Connection inspector for the eventCell here:
I have also made Content View background color blue, but it seems like I don't see the whole Content view in my cell.
My custom cell class looks like this:
Check that you have the correct identifier for your cell in IB. Let cell... is returning nil so that appears to be your problem.
let cell: EventTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("eventCell") as! EventTableViewCell
cell.textLabel works since you inherit the UITabelViewCell Class. You are getting a UITabelViewCell as reference - that is granted.
Create a strong reference to your label within the scope.
Place a breakpoint and validate what you are actually getting as cell. (indeed the correct subclass of yours or something generic.)
If the cell object is correct, but you don't get the label it must be outlet related.
In some cases in Swift I came upon the need to change the OUTLET for an UIElement from weak to strong.
If yes, only the properties of the label remain as cause. Give the label a background color. It's layout is actually visible? (eg.: hidden = NO, alpha = 1, frame/constrains render it visible and so on.)

Resources