Insert new row into a section of UITableView based on selected row in UIPickerView - Swift - swift

I have a custom inline UIPickerView (essentially just a expanding/collapsing UIPickerView) and there are 3 options (rows) in the picker view: "Weekly", "Monthly" and "Enter Manually". What I am trying to accomplish is that if a user chooses the row "Enter Manually", a new row is inserted below the row containing the custom UIPickerView (which would contain a custom TextInputCell I have created - but thats aside from the matter). The UITableView represents a form where the user can create an "event" so to speak. There are 4 sections and each section has a different amount of rows, some rows have expanding/collapsing UIPickerViews and others have custom UITextFields, and others have expanding/collapsing UIDatePickers. I only want this to happen for 1 particular UIPickerView, but I cannot seem to get it working. I had tried something like this in the didSelectRowAtIndexPath for when the UIPickerView is collapsed from selecting it's row:
tableView.beginUpdates()
if(cell.isKindOfClass(PickerCell)) {
let pickerTableViewCell = cell as! PickerCell
if(!pickerTableViewCell.isExpanded() && pickerTableViewCell.rightLabelText() == numClasses[2]) {
alertOptions.insert("Number of Weeks", atIndex: 1)
numberOfRowsAtSection[2] = 5
tableView.insertRowsAtIndexPaths([
NSIndexPath(forRow: alertOptions.count-3, inSection: 2)
], withRowAnimation: .Automatic)
}
}
tableView.endUpdates()
The numClasses is an array with the UIPickerView row data:
let numClasses = ["Weekly", "Monthly", "Enter Manually"]
All the UITableView data is in separate arrays for each section like follows:
var numberOfRowsAtSection: [Int] = [3, 4, 4, 3]
let mainOptions = ["Client Name", "Invoicing Email", "Invoicing Address"]
let dateOptions = ["All Day", "Starts", "Ends", "Time Zone"]
var alertOptions = ["How Many Classes", "Shown on Calendar As", "Alert by Email", "Alert by Text"]
let billingOptions = ["Amount Charged", "Tax Included", "Billing Date"]
And then I suppose I would just put a condition for everything between tableBeginUpdates() and tableEndUpdates() to test if it was the right UIPickerView like:
if(pickerViewTableCell.pickerView == numClassesPicker.pickerView) { ... }
Is what I'm trying to do possible or am I on the right track? Help!
Here are some images for a better visual:
Prior to selecting "Enter Manually":
During selecting "Enter Manually", UIPickerView is expanded:
And after, with it now collapsed and "Enter Manually" Selected,
What I'm going for here is to now have a new row between "How Many Classes" and "Show on Calendar As":

In alertOptions, just add a new object and reload the tableView after you are finished with the action in the pickerView.

One of the main issues was that I was not using tableView.reloadData() but aside from that, the implementation that I ended up coding was:
tableView.beginUpdates()
if(cell.isKindOfClass(PickerCell)) {
let pickerTableViewCell = cell as! PickerCell
if(pickerTableViewCell.pickerView == numClassesPicker.pickerView && numberOfRowsAtSection[2] < 5) {
let newRowIndex = alertOptions.count - 4
let newNumRows = 5
if(!pickerTableViewCell.isExpanded() && pickerTableViewCell.rightLabelText() == numClasses[2]) {
alertOptions.insert("Number of Weeks", atIndex: 1)
numberOfRowsAtSection[2] = newNumRows
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: newRowIndex, inSection: 2)],
withRowAnimation: .Automatic)
tableView.reloadData()
}
} else if(numberOfRowsAtSection[2] == 5) {
let oldRowIndex = alertOptions.count - 5
let numRows = 4
alertOptions.removeAtIndex(1)
numberOfRowsAtSection[2] = numRows
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: oldRowIndex, inSection: 2)],
withRowAnimation: .Automatic)
tableView.reloadData()
}
}
tableView.endUpdates()
And then in didSelectRowAtIndexPath tableView delegate I have a condition to test how many rows are in numberOfRowsAtSection[2] and then return the type of cell I want accordingly

Related

How to display a set number of rows in small Table View with self-sizing cells

I have a custom table view class, which is configured to set the table view height to display only 3 rows. This means if there are 20 rows, table view will be sized to display first 3 rows, and allowing user to scroll.
This code of mine works only if I set the static rowHeight
class CannedRepliesTableView: UITableView {
/// The max visible rows visible in the autocomplete table before the user has to scroll throught them
let maxVisibleRows = 3
open override var intrinsicContentSize: CGSize {
let rows = numberOfRows(inSection: 0) < maxVisibleRows ? numberOfRows(inSection: 0) : maxVisibleRows
return CGSize(width: super.intrinsicContentSize.width, height: (CGFloat(rows) * rowHeight))
}
}
If I set UITableViewAutomaticDimension to the rowHeight, table view is not properly resized. Is there a solution to this?
This would be one way to improve on what you currently have. I don't have experience accessing intrinsicContentSize for this calculations and didn't test this locally (other than syntax), but if previously it worked, this should as well.
Basically you're creating an array with maxVisibleRows number of indexPaths. If you have less, then fetchedIndexesCount prevents an indexOutOfBounds crash. Once you have the array, you iterate for each corresponding cell and fetching its size, finally summing it up.
class CannedRepliesTableView: UITableView {
var focussedSection = 0
let maxVisibleRows = 3
open override var intrinsicContentSize: CGSize {
return CGSize(width: super.intrinsicContentSize.width, height: calculateHeight())
}
private func calculateHeight() -> CGFloat {
guard let indexPaths = startIndexes(firstCount: 3) else {
// Your table view doesn't have any rows. Feel free to return a non optional and remove this check if confident there's always at least a row
return 0
}
return indexPaths.compactMap({ cellForRow(at: $0)?.intrinsicContentSize.height }).reduce(0, +)
}
private func startIndexes(firstCount x: Int) -> [IndexPath]? {
let rowsCount = numberOfRows(inSection: focussedSection)
let fetchedIndexesCount = min(x, rowsCount)
guard fetchedIndexesCount > 0 else {
return nil
}
var result = [IndexPath]()
for i in 0..<fetchedIndexesCount {
result.append(IndexPath(row: i, section: focussedSection))
}
return result
}
}

swift 4 Empty UITableView before UIRefeshControl

Good day, guys!
I have a little problem with updating data.
I'm getting information from backend with JSON-RPC and populating my table view cells.
I implemented UIRefreshContol to my TableView, so now when I pull to refresh it gives me the new information on top of old one.
So I have a list of products and when I add some products and refresh tableView to get updated information, tableView gives me old info AND new one on top of the old one
Are there any ways to empty table before I will get updated information from JSON?
// That's my JSON
func getJSONresponse() {
getResponse(o_method: "search_read", o_model: "res.users", o_domain:[["id", "=", jkID]], o_fields: ["name","partner_id"]) { (result) -> () in
//print(result)
if result != JSON.null {
let partner_id = result[0]["partner_id"][0].int!
getResponse(o_method: "search_read", o_model: "res.partner", o_domain: [["id", "=", partner_id]], o_fields: ["name", "kato"], completion: { (result)->() in
//print(result)
let id = result[0]["kato"][0].int!
katoID = id
print("adiasodjasoid",katoID)
getResponse(o_method: "search_read", o_model: "property.building", o_domain: [], o_fields: ["name","kato_area"], completion: { (result)->() in
result.forEach({ (temp) in
var area:String = "", name:String = "", totID: Int = 0
if (temp.1["kato_area"].string != nil) {
area = temp.1["kato_area"].string!
}
if (temp.1["name"].string != nil) {
name = temp.1["name"].string!
}
if (temp.1["id"].int != nil) {
totID = temp.1["id"].int!
}
let newModel = resUser(area: area, name: name, id: totID)
self.data.append(newModel)
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
// print(self.data)
})
})
}
}
}
That's my Pull to refresh Function
#objc func refreshData() {
tableView.reloadData()
getJSONresponse()
self.tableView.contentInset = UIEdgeInsets(top: 80, left: 0, bottom: 0, right: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.refresher.endRefreshing()
self.tableView.contentInset = UIEdgeInsets.zero
self.tableView.setContentOffset(.zero, animated: true)
}
}
Well you will have to bind the UITableView with to an array, some may also refer to it to as datasource.
So basically heres the flow:
1) Create a mutable array/datasource (This will hold all the JSON).
2) Correspond UITableView number of rows method to return the count this array/datasource.
3) When you press the refresh. remove all the contents of the array/datasource (removeAll)
4) Once you receive the response from backend, add them to the array/datasource and call reloadData on UITableView object.
Hope That Helps. Code and Prosper!!
Please bind UITableView with an Array. Then update this Array first from the data that you are getting from JSON (Empty it and put in the new data), and then call UITableView reload.

How to tap on a button inside a CollectionView cell which is not visible on screen

I am working on UITests using XCode. I have multiple CollectionView cells.
When I perform Count in the collectionView it shows the certain count.
I can able to access first two cells but coming to the 3rd cell as 3(depends on device size). It says that specific button I am looking for in 3rd Cell as exists.
But isHittable is false.
Is there any way I can tap on the button on the 3rd Cell.
I have tried using the extension for forceTapElement() which is available online, it didn’t help.
Extension Used:
extension XCUIElement{
func forceTapElement(){
if self.isHittable{
self.tap()
}else{
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: .zero)
coordinate.tap()
}
}
}
Tried to perform swipeUp() and access the button. it still shows isHittable as false
The only way I've found is to swipe up untile the isHittable will be true.
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
for _ in 0..<100 {
guard !tester.isHittable else {
break;
}
collectionView.swipeUp()
}
}
Please note that the swipeUp() method will only move few pixels. If you want to use more comprehensive methods you can get AutoMate library and try swipe(to:untilVisible:times:avoid:from:):
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
collectionView.swipe(to: .down, untilVisible: tester)
// or swipe max 100 times in case 10 times is not enough
// collectionView.swipe(to: .down, untilVisible: tester, times: 100)
}

Is there a way to get the number of cells in a table view?

In case if we are to take in all the cells in a TableView into an array and iterate through it to click on elements. I am looking for a solution in swift.
If each cell have a differentiating factor, then you get those in an array.
To use the text strings, I have to get hold of the cells first. So that leads to - how to get count count of cells and after getting hold of the cells, drilling down to see if this is the text, then open the contextual menu, else do something.
Here's what you can do: Keep using 'atIndex:' on the cells. Use selectElementWithMatcher::withError. Loop through until you find an indexOutOfBoundsError and then you should have the text.
But with the looping using atIndex:, you should have the cells that you want. And to do the same, see below:
for (int i = 0; i < someLargeValue; i++) {
EarlGrey.selectElementWithMatcher(grey_accessibilityID("abc")).atIndex(i)
}
-> Ok and for getting the value of “someLargeValue”, use selectElementWithMatcher::withError. Loop through until you find an indexOutOfBoundsError and then you should have the text.
This could be achieved by implementing a custom GREYAssertionBlock for the UITableView:
func assertTableView(_ accessibilityID: String, hasRowCount rowCount: Int, inSection section: Int) {
let cellCountAssert = GREYAssertionBlock(name: "cell count") { (element, error) -> Bool in
guard let tableView = element as? UITableView, tableView.numberOfSections > section else {
return false
}
let numberOfCells = tableView.numberOfRows(inSection: section)
return numberOfCells == rowCount
}
EarlGrey.selectElement(with: grey_accessibilityID(accessibilityID)).assert(cellCountAssert)
}

Get RowHeight of each row in UITableView Swift

is there a way to get the row height for each row in an UITableView in swift ? Please help. Thanks in advance.
Swift 4:
var height: CGFloat = 0
for cell in tableView.visibleCells {
height += cell.bounds.height
}
I think this is what you are looking for. This assumes that "Cell" is the identifier of the given row, and indexPath is the index of the row in question.
let row = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)as! UITableViewCell
let height = row.bounds.height
Cells only exist when they are visible, and you have access to them through the table view's visibleCells() method.
for obj in tableView.visibleCells() {
if let cell = obj as? UITableViewCell {
let height = CGRectGetHeight( cell.bounds )
}
}
The functional way:
let sum = tableView.visibleCells.map( { $0.bounds.height } ).reduce(0,+)
print("Height:\(sum)")
You need the cell for a certain IndexPath to calculate its bounds.
You can do it this way in any of the delegate functions of the UITableView :
let row = tableView.cellForRow(at: indexPath)
let cellHeight = (row?.bounds.height)!
let cellWidth = (row?.bounds.width)!