After deleting/removing an array it goes back when refresh - ios

I have two buttons in my tableviewCell which is confirm and ignore these two button should remove the cell when click tho it successfully removing the cell but when i refresh it goes back. How can i permanently removed the data in the array when clicking ignore or confirm.
Firstly the array was solely in activityArray but i need to filter it in order to get the pending activity.
if activities.count > 0 {
let filtered = activities.filter { $0.activityTypeAction == .FriendRequest || $0.activityTypeAction == .EventRSVPInvite }
if self.kCurrentPage == 0 {
self.activityArray.removeAll()
self.pendingActivityArray.removeAll()
}
self.activityArray.append(contentsOf: activities)
self.pendingActivityArray.append(contentsOf: filtered)
}
This is how i removing the cell.
cell.onClinkedIgnoreCallback = {
self.pendingActivityArray.remove(at: indexPath.row)
self.NotificationTableView.deleteRows(at: [indexPath], with: .left)
self.NotificationTableView.reloadData()
}
cell.onClickedConfirmCallback = {
self.pendingActivityArray.remove(at: indexPath.row)
self.NotificationTableView.deleteRows(at: [indexPath], with: .left)
self.NotificationTableView.reloadData()
}

you don't need to call that function to refresh because it again fillups the data in the respected arrays so if you want to refresh it just call tableview.reloadData()

Related

Updating selected collectionview indexes after responding to PHPhotoLibraryChangeObserver event

I am making a multiple selection feature for my collection view which shows photos from the user's library. I keep track of the selected indexPaths in an array and I want to update them in case a photo library change observer event happens in the middle of selecting cells. for example, if a user has selected indexes 3 and 4 and a change observer event removes indexes 1 and 2 from the collection view, selected indexes should change to 1 and 2.
I am trying to do it manually using these functions:
fileprivate func removeIndicesFromSelections(indicesToRemove:IndexSet){
var itemToRemove: Int?
for (_, removeableIndex) in indicesToRemove.map({$0}).enumerated() {
itemToRemove = nil
for (itemIndex,indexPath) in selectedIndices.enumerated() {
//deduct 1 from indices after the deletion index
if (indexPath.item > removeableIndex) && (indexPath.item > 0) {
selectedIndices[itemIndex] = IndexPath(item: indexPath.item - 1, section: 0)
} else if indexPath.item == removeableIndex {
itemToRemove = itemIndex
}
}
if let remove = itemToRemove {
selectedIndices.remove(at: remove)
disableDeleteButtonIfNeeded()
}
}
}
fileprivate func moveSelectedIndicesAfterInsertion (insertedIndices:IndexSet){
for (_, insertedIndex) in insertedIndices.map({$0}).enumerated() {
for (itemIndex,indexPath) in selectedIndices.enumerated() {
//add 1 to indices after the insertion index
if (indexPath.item >= insertedIndex) {
selectedIndices[itemIndex] = IndexPath(item: indexPath.item + 1, section: 0)
}
}
}
}
However, these are getting more complicated than I expected and I keep finding bugs in them. Is there any better way to handle this situation (such as any built in collection view capabilities) or I just have to come up with my own functions like above?
You're on the right path, but you should store a reference to what object the user actually selected, not where they selected it (since that can change).
In this case, you should keep a reference to the selected photos' identifiers (see docs) and then you can determine what cell/index-path should be selected. You can compare your selection array against your image datasource to determine what the most up-to-date index path is.
There is a solution provided by Apple. You can find more information in official documentation page:
Bacically you want to adopt PHPhotoLibraryChangeObserver and implement the following function:
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let collectionView = self.collectionView else { return }
// Change notifications may be made on a background queue.
// Re-dispatch to the main queue to update the UI.
DispatchQueue.main.sync {
// Check for changes to the displayed album itself
// (its existence and metadata, not its member assets).
if let albumChanges = changeInstance.changeDetails(for: assetCollection) {
// Fetch the new album and update the UI accordingly.
assetCollection = albumChanges.objectAfterChanges! as! PHAssetCollection
navigationController?.navigationItem.title = assetCollection.localizedTitle
}
// Check for changes to the list of assets (insertions, deletions, moves, or updates).
if let changes = changeInstance.changeDetails(for: fetchResult) {
// Keep the new fetch result for future use.
fetchResult = changes.fetchResultAfterChanges
if changes.hasIncrementalChanges {
// If there are incremental diffs, animate them in the collection view.
collectionView.performBatchUpdates({
// For indexes to make sense, updates must be in this order:
// delete, insert, reload, move
if let removed = changes.removedIndexes where removed.count > 0 {
collectionView.deleteItems(at: removed.map { IndexPath(item: $0, section:0) })
}
if let inserted = changes.insertedIndexes where inserted.count > 0 {
collectionView.insertItems(at: inserted.map { IndexPath(item: $0, section:0) })
}
if let changed = changes.changedIndexes where changed.count > 0 {
collectionView.reloadItems(at: changed.map { IndexPath(item: $0, section:0) })
}
changes.enumerateMoves { fromIndex, toIndex in
collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
to: IndexPath(item: toIndex, section: 0))
}
})
} else {
// Reload the collection view if incremental diffs are not available.
collectionView.reloadData()
}
}
}
}

how to break and continue after every iteration using for loop in swift

I am using the following code to break and continue in for loop. I want to break and continue the for loop after every iteration. For example when i=0 loop complete the iteration and and break the loop and again continue with i=1 and complete the iteration and break and then continue the iteration with i=2 so on.
outer: for i in 0..<micSources.count {
if let cell = self.micTestFaliureTableView.cellForRow(at: IndexPath(row: i, section: 0)) as? MicFaliureTableViewCell {
//let micLocation = cell.micFaliureTestview.micLocation
//let indexpath = micTestFaliureTableView.indexPath(for: cell)
if !cell.micFaliureTestview.success {
cell.micFaliureTestview.startRecording()
break outer
}
}
continue outer
}
for i in 0..<micSources.count {
// use custom class for your data array and control over that list, then reload/update your tableview.
if let cell = self.micTestFaliureTableView.cellForRow(at: IndexPath(row: i, section: 0)) as? MicFaliureTableViewCell {
if !cell.micFaliureTestview.success {
cell.micFaliureTestview.startRecording()
}
}
}
If you want to test and display microphone status in a tableView,
I recommend you call a updateData() function in viewDidLoad() of your viewController.
var microphoneStatus: [Bool]?
func viewDidLoad() {
super.viewDidLoad()
// test the microphone status only once,
// when the view is loaded for the first time.
updateData()
}
private func updateData() {
for microphoneSource in micSources {
let isMicrophoneRunning = microphoneSource.success
microphoneStatus.append(isMicrophoneRunning)
if !isMicrophoneRunning { startMicrophone }
}
}
And then, use the tableview data source delegate to feed your tableview.
And use the microphoneStatus array to show their status.
You can even create an array of struct instead of a [Bool] and that struct will contain the name of the input, it's number and it's status.
Then you can use this array of struct to simply display your data in the tableView.
It seems your problem is another problem:
when i use this inside the loop... if !cell.micFaliureTestview.success
{ cell.micFaliureTestview.startRecording() } all fail mics start
together but i want with sequence – Umair Shams
I recommend looking into the startRecording() function signature and see if there is a handler that is called only once the startRecording is finished.
Then you can chain the startRecording() calls properly.

.reloadData() without reCreating visible cells or those who are in memory?

When I have instantiated the third cell, I will add more to my items to my model array and then I will update the collection view data with:
DispatchQueue.main.async(execute: {
self.collectionView.reloadData()
})
Everything works as expected. However, when I reload the data for my collectionView, it will instantiate cells that are currently visible or hold in memory (2,3). Unfortunately, I have some expensive server requests which consume a lot of time.
Instaniated 0
Instaniated 2
Instaniated 3
******polluting more data: size of cells 10
Instaniated 2
Instaniated 3
Instaniated 4
How can I reload the data without reCreating visible cells or those who are in memory?
Thanks a lot.
Instead of reloading the cells, try inserting or reloading the once that have actually changed. You can use UIColletionViews performBatchUpdates(_:) for this: Link
An example:
collectionView.performBatchUpdates {
self.collectionView.insertItems(at: [IndexPath(row: 1, section: 1)])
}
This ensures that only the new cells are loaded. You can also move around cells and sections in this method and even delete cells. The linked page contains documentation for all of this.
Why can't you go with below approach
1) I hope you have declared dataSource and collectionView objects as global to the class
let collectionView = UICollectionView()
var dataSource = [Any]()
2) Have one function to get the initial results from the API response
func getInitialPosts(){
// call api
// store your initial response in the local array object and reload collectionview
let results:[Any] = {response from the server call}
self.dataSource.append(contentsOf: results)
DispatchQueue.main.async(execute: {
self.collectionView.reloadData()
})
}
3) For the next call, you can have another function
func getPostsForPage(page:Int){
// call api with page number
let newResults = {response from the server call}
self.dataSource.append(contentsOf: newResults)
var indexPathsToReload = [IndexPath]()
let section = 0
var row = self.dataSource.count - 1
//add new data from server response
for _ in newResults {
let indexPath = IndexPath(row: row, section: section)
row+=1
indexPathsToReload.append(indexPath)
}
// perform reload action
DispatchQueue.main.async(execute: {
self.collectionView.insertItems(at: indexPathsToReload)
})
}
Suppose from your network adapter you are calling delegate function fetchData with new data. Here you have to check if your data is empty or not to check if you need to add new data, or reload entire CollectionView.
Then you create all indexPaths that you need to fetch more, in order to let already fetched cell stay as they are. And finally use insertItems(at: IndexPaths).
I use page in order to paginate new data with page number in the future. Strictly for my use case. Good luck!
func fetchData(with videos: [VideoModel], page: Int) {
guard self.data.count == 0 else{
self.addNewData(with: videos, page: page)
return
}
self.data = videos
DispatchQueue.main.async {
self.isPaging = false
self.collectionView?.reloadData()
self.page = page
}
}
func addNewData(with videos: [VideoModel], page: Int){
var indexPathsToReload = [IndexPath]()
let section = 0
var row = self.data.count - 1
self.data += videos
for _ in videos {
print(row)
let indexPath = IndexPath(row: row, section: section)
row+=1
indexPathsToReload.append(indexPath)
}
DispatchQueue.main.async{
self.isPaging = false
self.collectionView!.insertItems(at: indexPathsToReload)
self.page = page
}
}

Swift / TableView reusable Cell loads with different content every time it gets displayed

I'm having a UITableViewController. Inside section 1 there is a Cell displaying a JTAppleCalendar easily populated with:
if indexPath.section == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier("calendarViewCell") as! CalendarViewCell
print(eventDatesAsNSDates)
cell.calendarView.selectDates(eventDatesAsNSDates)
return cell
}
eventDatesAsNSDates gets populated within viewDidAppear.
In theory everything is working as I want it. But if I scroll the TableView down and up, following completely annoying behavior happens.
The print statement within cellForRowAtIndexPath print(eventDatesAsNSDates) proves that eventDatesAsNSDates does not change and yet the Calendar keeps being populated one time and not the other time and then being populated again...
Neither cell.calendarView.selectDates nor eventDatesAsNSDates gets set or called on another time in the App.
What am I missing? Help is very appreciated.
As requested, the selectDates function:
public func selectDates(dates: [NSDate], triggerSelectionDelegate: Bool = true, keepSelectionIfMultiSelectionAllowed: Bool = false) {
var allIndexPathsToReload: [NSIndexPath] = []
var validDatesToSelect = dates
// If user is trying to select multiple dates with multiselection disabled, then only select the last object
if !calendarView.allowsMultipleSelection && dates.count > 0 { validDatesToSelect = [dates.last!] }
let addToIndexSetToReload = {(indexPath: NSIndexPath)->Void in
if !allIndexPathsToReload.contains(indexPath) { allIndexPathsToReload.append(indexPath) } // To avoid adding the same indexPath twice.
}
let selectTheDate = {(indexPath: NSIndexPath, date: NSDate) -> Void in
self.calendarView.selectItemAtIndexPath(indexPath, animated: false, scrollPosition: .None)
addToIndexSetToReload(indexPath)
// If triggereing is enabled, then let their delegate handle the reloading of view, else we will reload the data
if triggerSelectionDelegate {
self.internalCollectionView(self.calendarView, didSelectItemAtIndexPath: indexPath)
} else { // Although we do not want the delegate triggered, we still want counterpart cells to be selected
// Because there is no triggering of the delegate, the cell will not be added to selection and it will not be reloaded. We need to do this here
self.addCellToSelectedSetIfUnselected(indexPath, date: date)
let cellState = self.cellStateFromIndexPath(indexPath, withDate: date)
if let aSelectedCounterPartIndexPath = self.selectCounterPartCellIndexPathIfExists(indexPath, date: date, dateOwner: cellState.dateBelongsTo) {
// If there was a counterpart cell then it will also need to be reloaded
addToIndexSetToReload(aSelectedCounterPartIndexPath)
}
}
}
let deSelectTheDate = { (oldIndexPath: NSIndexPath) -> Void in
addToIndexSetToReload(oldIndexPath)
if let index = self.theSelectedIndexPaths.indexOf(oldIndexPath) {
let oldDate = self.theSelectedDates[index]
self.calendarView.deselectItemAtIndexPath(oldIndexPath, animated: false)
self.theSelectedIndexPaths.removeAtIndex(index)
self.theSelectedDates.removeAtIndex(index)
// If delegate triggering is enabled, let the delegate function handle the cell
if triggerSelectionDelegate {
self.internalCollectionView(self.calendarView, didDeselectItemAtIndexPath: oldIndexPath)
} else { // Although we do not want the delegate triggered, we still want counterpart cells to be deselected
let cellState = self.cellStateFromIndexPath(oldIndexPath, withDate: oldDate)
if let anUnselectedCounterPartIndexPath = self.deselectCounterPartCellIndexPath(oldIndexPath, date: oldDate, dateOwner: cellState.dateBelongsTo) {
// If there was a counterpart cell then it will also need to be reloaded
addToIndexSetToReload(anUnselectedCounterPartIndexPath)
}
}
}
}
for date in validDatesToSelect {
let components = self.calendar.components([.Year, .Month, .Day], fromDate: date)
let firstDayOfDate = self.calendar.dateFromComponents(components)!
// If the date is not within valid boundaries, then exit
if !(firstDayOfDate >= self.startOfMonthCache && firstDayOfDate <= self.endOfMonthCache) { continue }
let pathFromDates = self.pathsFromDates([date])
// If the date path youre searching for, doesnt exist, then return
if pathFromDates.count < 0 { continue }
let sectionIndexPath = pathFromDates[0]
// Remove old selections
if self.calendarView.allowsMultipleSelection == false { // If single selection is ON
let selectedIndexPaths = self.theSelectedIndexPaths // made a copy because the array is about to be mutated
for indexPath in selectedIndexPaths {
if indexPath != sectionIndexPath { deSelectTheDate(indexPath) }
}
// Add new selections
// Must be added here. If added in delegate didSelectItemAtIndexPath
selectTheDate(sectionIndexPath, date)
} else { // If multiple selection is on. Multiple selection behaves differently to singleselection. It behaves like a toggle. unless keepSelectionIfMultiSelectionAllowed is true.
// If user wants to force selection if multiselection is enabled, then removed the selected dates from generated dates
if keepSelectionIfMultiSelectionAllowed {
if selectedDates.contains(calendar.startOfDayForDate(date)) {
addToIndexSetToReload(sectionIndexPath)
continue // Do not deselect or select the cell. Just add it to be reloaded
}
}
if self.theSelectedIndexPaths.contains(sectionIndexPath) { // If this cell is already selected, then deselect it
deSelectTheDate(sectionIndexPath)
} else {
// Add new selections
// Must be added here. If added in delegate didSelectItemAtIndexPath
selectTheDate(sectionIndexPath, date)
}
}
}
// If triggering was false, although the selectDelegates weren't called, we do want the cell refreshed. Reload to call itemAtIndexPath
if /*triggerSelectionDelegate == false &&*/ allIndexPathsToReload.count > 0 {
delayRunOnMainThread(0.0) {
self.batchReloadIndexPaths(allIndexPathsToReload)
}
}
}
Well the cellForRow delegate method is attempting to update tableView rows/section(s) each time your cell is visible again.
I would definitely prepare the data (for calendar) somewhere else and I'd prefer to render data only. Reusability of UITableViewCell will handle content properly.
Don't call that method in the cellForRow method. Try to place it e.g. into viewDidAppear() method. If selectDates() method does all the work of updating your tableView, it could still work.

call deleteRowsAtIndexPaths multiple times

There are several questions about this problem. I have tried all of the suggested answers but nothing has worked yet.
So I have this func which decline offers that a user receives. I can decline the offers and delete the cell rows but when there is only one cell left I get fatal error: Index out of range
func declineButtonTapped(sender: AnyObject) {
self.view.userInteractionEnabled = false
let buttonRow = sender.tag // this is the tag from my custom cell button
let offer = offers[buttonRow] // I get the error here
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "declining offer...."
let myUrl = NSURL(string: "\(ipAddress)/api/v1.0/offers.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "id=\(offer.id!)&action=decline&offer_id=\(offer.offer_id!)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{ data, response, error in
if error != nil {
let messageToDisplay = error
self.view.userInteractionEnabled = true
return
}
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = json{
let resultValue = parseJSON["status"] as? String
if resultValue == "Success"{
dispatch_async(dispatch_get_main_queue()) {
print("before count is \(self.offers.count)") // before the error the count is 2 here
self.offers.removeAtIndex(buttonRow) //update my model
self.tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: buttonRow, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
print("after count is \(self.offers.count)") //then the count is 1 here
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
self.view.userInteractionEnabled = true
}
}else{
//no success
}
}
} catch{
}
}
task.resume()
}
inside cellForRowAtIndexPath I assign the tag value of the button
offerCell.declineButton.tag = indexPath.row
offerCell.declineButton.addTarget(self, action: #selector(OpenDealsDetailsViewController.declineButtonTapped(_:)), forControlEvents: UIControlEvents.TouchUpInside)
******UPDATE*****
I think I found the error. When I print
print("button row is\(buttonRow)") the number don't get updated. So the first time it calls the correct row but the second times it keep the indexPath.row it had when declineButtonTapped was called the first time
button row is0
before count is 2
after count is 1
button row is1 // this of course should be 0 as there is only one cell left
fatal error: Index out of range
if I try to do
self.offers.removeAtIndex(buttonRow)
self.tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: buttonRow, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.reloadData()
I get the following error:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
You either shouldn't use tags or you should reload the table view fully after each change. Alternately you could iterate the visible cells and update their tag values.
If you were to always and only delete the last row from the table it'd be fine. As soon as you delete an earlier row all the rows after that now have an incorrect tag value. So, when you go to the last row it doesn't actually exist at that tag, and any other invalid rows will result in you deleting the wrong item from the server.
A better approach is to pass the cell an instance of a class which can action the deletion and call back to the view controller with details of the update made. The view controller can then update its data source and the table view. In this way you separate the table index path from the action you're going to take on the data.

Resources