UICollectionView Custom layout reloading issue in Swift - swift

This is refrence link:
https://www.raywenderlich.com/107439/uicollectionview-custom-layout-tutorial-pinterest
I have implemented load more in UICollectionView at last cell , the data is getting downloaded, after download complete i want to reload collection
collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.reloadData()
let concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(concurrentQueue) {
DataManager.sharedInstance.homeRequest(Dictionary: params, onSuccess: { (dict) in
self.downloadPageIndex += 1
let response = HomeObject(dictionary: dict)
for (_,e) in (response.dataDict?.enumerate())!{
self.dataArray.append(e)
}
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
}
self.activityIndicatorCell.stopAnimating()
}, onError: { (error) in
self.activityIndicatorCell.stopAnimating()
})
like this collectionview reloadata
Can any one help me out?

Ran into your question and am currently facing the same problem, after some digging i figured it out!
The problem is within the PintrestLayout file
Inside you will find code like (PROBLEM) :
guard cache.isEmpty == true, let collectionView = collectionView else {
return
}
Basically, if the attributes that you are applying to the CollectionView are empty (they are at the start), apply the PintrestLayout to it, and thats perfect! BUT If you are reloading the data and adding to the collectionView, you need to apply the layout again, but the cache isnt empty when you try to apply the layout to the collectionView again, thus it returns and doesn't work at all...
SOLUTION :
replace that code with :
guard cache.isEmpty == true || cache.isEmpty == false, let collectionView = collectionView else {
return
}
Now you are saying "i dont care if its empty or not, just apply the damn layout to my collectionView"
And Done! Now reloadData() will update how many cells you got...
This is pretty expensive though, if you wanna make it faster, and trim some memory etc... Then look into invalidateFlowLayout Documentation.

Related

How can I iterate through an array of imageUrls on Firestore, to then return as images based on their index in UIPickerView?

Currently, I am fetching imageUrls from Firestore using my 'tuwos' delegate (code not included). I then use a forIn loop, where I use a print statement to make sure each individual imageUrl has been fetched successfully (they are). Also in my forIn loop, I use the Kingfisher cocoapod to set an imageView's image based on each imageUrl. Finally, I return imageView.image, but the default image, "girl_0", is being shown each time, meaning images are not being displayed from Firestore.
func pickerView(_ pickerView: AKPickerView, imageForItem item: Int) -> UIImage {
let tuwos = self.delegate?.tuwos
let imageView = UIImageView(image: #imageLiteral(resourceName: "jane2"))
if item == 0 {
for (index, element) in tuwos!.enumerated() {
if index == 0 {
let imageName = "\(element.profileImageUrl)"
print("element image url:", element.profileImageUrl)
let url = URL(string: imageName)
imageView.kf.setImage(with: url)
}
}
}
imageView.image = UIImage(named: "girl_0")!.imageWithSize(CGSize(width: 82, height: 82))
return imageView.image!
}
Other info:
(1) I'm using the AKPickerView cocoapod for a Horizontal Picker View, but the logic seems the same ('item' refers to their index).
(2) The 'tuwos' delegate is referring to where I query Firestore.
(3) I only set the code to execute for 'if item == 0' to see if it would execute at all, then was planning to figure out an abstraction to execute for each item in my pickerView. I suspect my problem is to do with my forIn loop, and that nothing is executing outside of it, hence returning the default for item 0.
This is my first post so apologies if I've missed anything, but happy to provide any info you need. I have looked around quite a lot trying to get the answer on my own (URLSession/kingfisher/sdwebimage/escaping closure/forin loop articles) but struggling on this one.

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.

Binding textfield text to enable button not working

I am new to reactive programming, and Bond framework specifically. I know I may be doing things that are wrong in my basic understanding of this programming technique. This is the situation:
I have a UITextView and an "approve" UIButton.
I want the approve button to be enabled only when the text in the textView is not nil. I have tried adding these lines of code into my viewDidLoad method in my ViewController.
textView.reactive.text.observeNext{(text) in
self.message = text
print(text)
}
textView.reactive.text.map { $0 != nil}.bind(to: approveButtonOutlet.reactive.isEnabled)
The first action works (printing the text is happening successfully on every input change).
The second one does not work, the button is enabled both when the text is not nil and when it is.
Any help appreciated.
You can try like
RAC(self.approveButtonOutlet, enabled) = [self.textView.rac_textSignal map:^id(NSString *text) {
return #(text.length > 0);
}];
I'm not sure how it will be in swift 3 just try like
RAC(self.approveButtonOutlet, enabled) = self.textView.rac_textSignal.map({(text: String) -> void in
return (text.length > 0)
})
I found the issue was that I had a placeholder in my textView, which prevented from the text really being nil. So what eventually I did is this:
textView.reactive.text.map {
if $0 == placeholder {
return false
} else if $0 != nil {
return $0!.characters.count > 0
} else{
return false
}
}.bind(to: approveButtonOutlet.reactive.isEnabled)

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)
}

UITable UITextField not displaying data correctly SWIFT 3.0

My application code is pretty lengthly so I would like to try and get this problem resolved without having to show a bunch of code....but here is what is going on..
I have a tableview with dynamic cells all set up just fine. There is a textfield in the cell. When data is entered into the textfield, it is then sent to a label within that same cell. This happen when a "done" button is pressed. This all works just find until I have about 6 rows added to the table. At that point, when the "done" button is pressed, the data gets all mixed up. Sometimes the data is sent to the wrong cell label and other times it doesn't send at all. This only happens when I get to about 6 rows. I am very confused. The cells are displayed based on a Core Data entity.count...the textfield has the delegate set as itself....I have no clue what could be causing this...only after 6 rows!?
Thank you in advance for your help...like I said, I would like to try and avoid pasting my code here but if I have to provide some I will or I can talk you through what is going on in my code...
EDIT
Here is some of my code in cellForRowAt that deals with the textFields in the cell content
if searchResults.count > 1 {
if showInGrams {
if foods[indexPath.row].units != "g" && foods[indexPath.row].units != "grams" && foods[indexPath.row].units != "Grams" {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", (100 / foods[indexPath.row].coefficient) * searchResults[indexPath.row].doubleValue)
cell.labelUOM.text = "grams"
} else {
if cell.changeServingSegmentedControl.selectedSegmentIndex == 0 {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 0)
//show default values
} else {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 1)
}
}
} else {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", searchResults[indexPath.row].doubleValue * foods[indexPath.row].serving)
cell.labelUOM.text = "grams"
}
}
} else {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", searchResults[indexPath.row].doubleValue * foods[indexPath.row].serving)
cell.labelUOM.text = foods[indexPath.row].units
} else {
if cell.changeServingSegmentedControl.selectedSegmentIndex == 0 {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 0)
} else {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 1)
}
}
}
I hope this helps, thank you
What you are seeing is a symptom of UITableView's reuse strategy. May be what you want to do is store the information entered in your UITextField in a collection and then every time the tableView "reloads", you pick it out from that collection for the current indexPath and load it.
A line like this is never going to work:
if cell.changeServingTextField.text == ""
The problem is that cells are reused. Thus, in its previous life, if the text field had text, it still has text and the test will fail.
Instead, your logic needs to be based entirely on what row this is and what the data corresponding to that row is. And it needs to cover every case, because the cell can be reused.
Things would be clearer if you moved most of this logic into the cells themselves. You can start off by making all your cell's IBOutlets fileprivate and assigning a model object or dictionary as a property of the cell. That way you're separating the logic for creating the cells from their display - and will prevent you using cell's outlets to determine state.
While this might not solve your problem directly, separating the logic might clarify where to look.