swift 4 Empty UITableView before UIRefeshControl - swift

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.

Related

Uitest refresh control swift

I am trying to add uitest for refresh control but I could not have done it because of I can't get access to refresh control with accessibility identifier (refreshControl.accessibilityIdentifier = "refreshControlList")
launchAppWithArgs(json: OrdersResultJSON.ok, xcuiApp: app)
let table = app.tables["agendaTable"]
DispatchQueue.main.async {
table.otherElements["sectionHeader0"].press(forDuration: 2, thenDragTo: table.otherElements["sectionHeader2"])
}
if !table.otherElements[""].waitForExistence(timeout: 6) {
print("XXX")
}
Any suggestion to test it?
To perform a pull to refresh, simply get the first cell and drag it down:
let firstCell = app.tables["agendaTable"].cells.firstMatch
let start = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let finish = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 10))
start.press(forDuration: 0, thenDragTo: finish)
Hope this helps!
You could grab the UIRefreshControl's title label and put an identifier on it, like this:
func setSubviewAccessibility(for tableView: UITableView) {
guard let titleLabel = tableView.refreshControl?.subviews.first?.subviews.last as? UILabel else {
return
}
titleLabel.isAccessibilityElement = true
titleLabel.accessibilityIdentifier = "refresh_control_label"
}
Call this method passing the table view that you've set the UIRefreshControl for and you should be good to go.
Then, on the test side:
let refreshControl = XCUIApplication().staticTexts["refresh_control_label"]
guard refreshControl.waitForExistence(timeout: 5) else {
return XCTFail("Refresh control label not found.")
}
The only problem remaining is you'll probably need the list loading to take a little longer than a second so that your tests don't miss the UIRefreshControl. It's always good to use waitForExistence.

scrolltoitematindexpath always returns to the same position

I'm trying to implement adding new portion of information at the top, when user is scrolling to the top (to see chat history) without any jumping like in Telegram, Whatsup and so on
Here is my method
private var toItem: NSIndexPath?
private func addMoreCells () {
if collectionView.contentOffset.y <= 0 {
guard let userChatUid = user?.chatUid else { return }
guard let ownChatUid = UsersManager.sharedInstance.currentChatUID() else { return }
guard let offset = conversation?.messages.count else { return }
ConversationsManager.sharedInstance.getConversationsWithMoreMessagesFor(ownChatUid, recipient: userChatUid, offset: offset - 1, successHandler: { [weak weakSelf = self] (conversations) in
dispatch_async(dispatch_get_main_queue()) {
if let messagesFromServer = conversations.first?.messages, var messages = weakSelf?.conversation?.messages {
for message in messagesFromServer {
if !messages.contains(message) {
messages.append(message)
}
}
messages = messages.sort({$0.time?.compare($1.time!) == .OrderedAscending})
weakSelf?.conversation?.messages = messages
weakSelf?.collectionView.reloadData()
weakSelf?.toItem = NSIndexPath(forItem: (messages.count - messagesFromServer.count), inSection: 0)
if let item = weakSelf?.toItem{
weakSelf?.collectionView.scrollToItemAtIndexPath(item, atScrollPosition: .Top, animated: false)
}
}
}
}, failHandler: { (error) in
print(error)
})
}
}
which is launched from
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
addMoreCells()
}
First time when I'm scrolling to the top It works as I expect (new portion is added, no jumping. I can see new data and can continue scrolling to the top), but when I continue scrolling to the top and new data are added, it always returns to the position, when first info was added.
But I expect It should return to value messages.count - messagesFromServer.count. In xCode I see numbers are changed every time when I'am scrolling, but scrollToItemAtIndexPath doesn't work properly
And Every time when I scroll to the top and new data are added, It always returns to first position when first info was added.
Can Anyone know what's problem? Cause I cant find solutions to fix it.
I've solved this problem like this:
After reloadData() I save contentSize of collection view, after that I refresh layout, get new contentSize and subtract them to get needed offset to scroll to this place without jumping.
weakSelf?.collectionView.reloadData()
guard let previousContentSize = weakSelf?.collectionView.contentSize else { return }
weakSelf?.collectionView.collectionViewLayout.invalidateLayout()
weakSelf?.collectionView.collectionViewLayout.prepareLayout()
weakSelf?.collectionView.layoutIfNeeded()
guard let newContentSize = weakSelf?.collectionView.contentSize else { return }
print("previous Content Size \(previousContentSize) and new Content Size \(newContentSize)")
let contentOffset = newContentSize.height - previousContentSize.height
weakSelf?.collectionView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false)
I works great and as I would expect.

Quickest way to check multiple bools and changing image depending on that bool

I have multiple achievements that the user can achieve during the game. When the user goes to the achievement VC, he should see an image when the achievement is accomplished.
I am now using this code in the viewDidLoad function:
if unlockedAchievement1 == true
{
achievement1Text.textColor = UIColor.greenColor()
achievement1Image.image = UIImage(named: "achievement1Unlocked")
}
if unlockedAchievement2 == true
{
achievement2Text.textColor = UIColor.greenColor()
achievement2Image.image = UIImage(named: "achievement2Unlocked")
}
This would work, but it takes a lot of time to copy paste this overtime. How can I shorten this? Should I make a class? I read about for in loops, but I did not quite understood that. Any help is welcome!
I feel like I have seen this before. But anyway...
//use array of bools. First declare as empty array.
//Use Core data to achieve saving the Booleans if you need to.
let achievements:[Bool] = []
let achievementsImage:[UIImage] = []
let achievementsText:[UITextView] = []
func viewDidLoad() {
//Say you have 5 achievements.
for index in 0 ..< 5 {
achievements.append(false)
achievementsImage.append(UIImage(named: "achievementLocked"))
achievementText.append(UITextView())
//Then you can edit it, such as.
achievementText[index].frame = CGRect(0, 0, 100, 100)
achievementText[index].text = "AwesomeSauce"
self.view.addSubview(achievementText[index])
achievementText[index].textColor = UIColor.blackColor()
}
}
//Call this function and it will run through your achievements and determine what needs to be changed
func someFunction() {
for index in 0 ..< achievements.count {
if(achievements[index]) {
achievements[index] = true
achievementsImage[index] = UIImage(named: String(format: "achievement%iUnlocked", index))
achievementsText[index].textColor = UIColor.greenColor
}
}
}
I'd recommend making your unlockedAchievement1, unlockedAchievement2, etc. objects tuples, with the first value being the original Bool, and the second value being the image name. You'd use this code:
// First create your data array
var achievements = [(Bool, String)]()
// Then you can make your achievements.
// Initially, they're all unearned, so the Bool is false.
let achievement1 = (false, "achievement1UnlockedImageName")
achievements.append(achievement1)
let achievement2 = (false, "achievement2UnlockedImageName")
achievements.append(achievement2)
// Now that you have your finished data array, you can use a simple for-loop to iterate.
// Create a new array to hold the images you'll want to show.
var imagesToShow = [String]()
for achievement in achievements {
if achievement.0 == true {
imagesToShow.append(achievement.1)
}
}

UICollectionView Custom layout reloading issue in 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.

Change Swipe Button Title After Completion

I have the following code:
cell.rightButtons = [MGSwipeButton(title: "Save", backgroundColor:UIColor.redColor(),callback: {
(sender: MGSwipeTableCell!) -> Bool in
print("Saved")
return true
}))]
cell.rightSwipeSettings.transition = MGSwipeTransition.Rotate3D
return cell
This works fine but I want to change the title from "Save" to remove afterward. Tried it many different ways but doesn't seem to be working.
As explained in the official project: MGSwipeTableCell
you can obtain the button pressed by calling the delegate method:
-(BOOL) swipeTableCell:(MGSwipeTableCell*) cell tappedButtonAtIndex:(NSInteger) index direction:(MGSwipeDirection)direction fromExpansion:(BOOL) fromExpansion;
/**
* Delegate method to setup the swipe buttons and swipe/expansion settings
* Buttons can be any kind of UIView but it's recommended to use the convenience MGSwipeButton class
* Setting up buttons with this delegate instead of using cell properties improves memory usage because buttons are only created in demand
* #param swipeTableCell the UITableVieCel to configure. You can get the indexPath using [tableView indexPathForCell:cell]
* #param direction The swipe direction (left to right or right to left)
* #param swipeSettings instance to configure the swipe transition and setting (optional)
* #param expansionSettings instance to configure button expansions (optional)
* #return Buttons array
**/
In Swift you can write:
func swipeTableCell(cell: MGSwipeTableCell!, tappedButtonAtIndex index: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool {
if let button = cell.rightbuttons[index] {
print(button.titleLabel.text)
button.setTitle("Button Title", forState: UIControlState.Normal)
... (do whatever you want with your tapped button..)
}
}
To help you can launch the MGSwipeDemo project included in the bundle source, and add these lines in objective c like in this screenshots:
-(BOOL) swipeTableCell:(MGSwipeTableCell*) cell tappedButtonAtIndex:(NSInteger) index direction:(MGSwipeDirection)direction fromExpansion:(BOOL) fromExpansion
{
...
if (direction == MGSwipeDirectionRightToLeft && index == 1) {
NSLog(#"more pressed");
id button = cell.rightButtons[index];
UIButton *pressedBtn = button;
[pressedBtn setTitle:#"Less" forState:UIControlStateNormal];
NSLog(#"pressedBtn title: %#",pressedBtn.titleLabel.text);
}
...
}
P.S.: update: this part was added to help during comments:
About your personal project you must add to your datasource any boolean var, i make an example (in Swift code):
Class rowData{
var title: String! = String()
var subTitle:String! = String()
var button1Title : String! = String()
var button2Title : String! = String()
var isSaved : Bool! = false
}
var myDataSource : [rowData]! = [rowData]()
So, when you had populate your datasource you can use the delegate:
func swipeTableCell(cell: MGSwipeTableCell!, tappedButtonAtIndex index: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool {
...
if (direction == MGSwipeDirection.RightToLeft && index == 2){
if let _= cell?.rightButtons[index]{
let cellData:rowData = myDataSource[index]
if cellData.isSaved {
cellData.buttonTitle1 = "Just saved"
cellData.isSaved = true
} else {
cellData.buttonTitle1 = "Save"
cellData.isSaved = false
}
myDataSource[index] = cellData
// datasource is modified so launch reload data to refresh layout and call cellForRow..
// There are other softly methods than reloadData to update tableView layout but I use it just for example..
self.tableView.reloadData()
}
}
...
}