How to remove duplicate object in array? - ios

I have a VC where I fetch data for only the 1st object in an array of objects. so I only fetch arrayOfObjects[0] then when I enter a second VC I need to fetch all the other data associated with the other objects in that same array. But then I have a problem where I end up fetching that first bit of data I already had again. So my array would look like, data1, data1, data2, data3 ... which is not what I want of course.
Currently what I had tried to fix this issue, was to do the following: MainObject?.arrayOfSubObjects.remove(at: 0), this however means that on the first go it works well, but every time I go back to the preceding VC and then back I subtract one of the objects that I want to be there. So I end up with: data2, data3 ...
So my question is how can I remove that extra object from the beginning, but not delete anything after its been deleted?
Some things i have tried:
if selectedPost?.media[0].videoURL != nil {
if selectedPost?.media[0].videoURL == selectedPost?.media[1].videoURL {
selectedPost?.media.remove(at: 0)
} else {
print("NO!!!!!! they are not the same ")
}
} else if selectedPost?.media[0].image != nil {
if selectedPost?.media[0].image == selectedPost?.media[1].image {
selectedPost?.media.remove(at: 0)
} else {
print("NO!!! they are not the same ")
}
}
This however does not do anything, it always ends up going into the else. I have also tried stuff like setting number schemes, but this failed because the VC kept reloading

You can try to do it like this:
guard let selectedPost = selectedPost else { return }
if selectedPost.media.contains(where: {$0.image == image}) { return } // Here the $0.image should be replaced with $0.mediaURL == mediaURL for the videos or however you videoURL is called in Media
selectedPost.media.append(Media(image: image, timeStamp: Double(timeStamp)))
self.tableView.reloadData()
Try conforming Hashable in you Media class:
class Media : Hashable {
var hashValue : Int {
return image.hashValue
}
var image = UIImage()
// .....
static func == (lhs: Media, rhs: Media) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
Then you can compare two media objects with ==, So you can do something similar to the top part of this answer. Good luck

Related

Randomly Rearrange items in a LazyRow - Jetpack Compose

I have a LazyRow. Everything works fine. I just want the items to be randomly rearranged every time this LazyRow is drawn on the screen. Here is my code:
LazyRow(
reverseLayout = true,
contentPadding = PaddingValues(top = twelveDp, end = eightDp)
) {
itemsIndexed(
items = featureUsersLazyPagingItems,
key = { _, featuredPerson ->
featuredPerson.uid
}
) { _, featuredUser ->
featuredUser?.let {
//Daw the age suggested People
DrawSuggestedPerson(featuredUser.toPersonUser(),) {
homeViewModel.deleteFeaturedUserFromLocalDb(featuredUser.uid)
}
}
}
featureUsersLazyPagingItems.apply {
when {
loadState.refresh is LoadState.Loading -> {
item {
ShowLazyColumnIsLoadingProgressBar()
}
}
loadState.append is LoadState.Loading -> {
item {
ShowLazyColumnIsLoadingProgressBar()
}
}
loadState.refresh is LoadState.Error -> {
val e = featureUsersLazyPagingItems.loadState.refresh as LoadState.Error
item {
LazyColumnErrorView(
message = e.error.localizedMessage!!,
onClickRetry = { retry() }
)
}
}
loadState.append is LoadState.Error -> {
val e = featureUsersLazyPagingItems.loadState.append as
LoadState.Error
item {
LazyColumnErrorView(
message = e.error.localizedMessage!!,
onClickRetry = { retry() }
)
}
}
}
}
So the LazyRow displays the same set of 30 or so items but only 3- 4 items are visible on screen, for a bit of variety, I would like the items to be re-arranged so that the user can see different items on the screen. Is there a way to achieve this?
You can shuffle your list inside remember, by doing this you're sure that during one view appearance your order will be the same, and it'll be shuffled on the next view appearance. I'm passing featureUsersLazyPagingItems as a key, so if featureUsersLazyPagingItems changes shuffledItems will be recalculated.
val shuffledItems = remember(featureUsersLazyPagingItems) {
featureUsersLazyPagingItems.shuffled()
}
The only problem of remember is that it'll be reset on screen rotation. Not sure if you need that, and if you wanna save state after rotation, you need to use rememberSaveable. As it can only store simple types, which your class isn't, you can store indices instead, like this:
val shuffledItemIndices = rememberSaveable(featureUsersLazyPagingItems) {
featureUsersLazyPagingItems.indices.shuffled()
}
val shuffledItems = remember(featureUsersLazyPagingItems, shuffledItemIndices) {
featureUsersLazyPagingItems.indices
.map(featureUsersLazyPagingItems::get)
}
I suggest you checkout out documentation for details of how state works in compose.

Add elements to array each time a particular function is called

I am working on an audio-recording application. Each time I stop the recording, I need to add the recording filename to an array.
Currently, only one element gets added and when I add the next element, I am not able to see the previous element that had been added.
Am I missing something?
func pushDummyUploadCell(pendingUploadModel: String) {
var pendingUploadModels: [String] = []
if !queueAllFailedRecordings {
pendingUploadModels.append(pendingUploadModel)
print("Queue:",pendingUploadModels.count,"elements:",[pendingUploadModels]) //each time returns 1
}
else {
pendingUploadModels = [pendingUploadModel]
}
}
I am calling this function once I stop the recording.
You just need to define your array as globle in your controller or class.
var pendingUploadModels: [String] = []
func pushDummyUploadCell(pendingUploadModel: String) {
if !queueAllFailedRecordings {
pendingUploadModels.append(pendingUploadModel)
print("Queue:",pendingUploadModels.count,"elements:",[pendingUploadModels]) //each time returns 1
}
else {
pendingUploadModels = [pendingUploadModel]
}
}

Cannot convert value of type 'inout NSNumber?' to expected argument type 'AutoreleasingUnsafeMutablePointer<AnyObject?>' error

I have this script use to check whether the *downloaded file from iCloud is available or not. But unfortunately I encountered error Cannot convert value of type 'inout NSNumber?' to expected argument type 'AutoreleasingUnsafeMutablePointer<AnyObject?>' in some lines of code. Please help me to solve this issue because it is my first time to create a code to check whether the downloaded file is available in the icloud or not.
Please refer to the image below as sample of the error and also codes are available below for your reference. Hope you could help me. Thank you.
Sample screenshot of error
//-------------------------------------------------------------------
// ダウンロードできるか判定 Judgment or can be downloaded
//-------------------------------------------------------------------
func downloadFileIfNotAvailable(_ file: URL?) -> Bool {
var isIniCloud: NSNumber? = nil
do {
try (file as NSURL?)?.getResourceValue(&isIniCloud, forKey: .isUbiquitousItemKey)
if try (file as NSURL?)?.getResourceValue(&isIniCloud, forKey: .isUbiquitousItemKey) != nil {
if isIniCloud?.boolValue ?? false {
var isDownloaded: NSNumber? = nil
if try (file as NSURL?)?.getResourceValue(&isDownloaded, forKey: .ubiquitousItemIsDownloadedKey) != nil {
if isDownloaded?.boolValue ?? false {
return true
}
performSelector(inBackground: #selector(startDownLoad(_:)), with: file)
return false
}
}
}
} catch {
}
return true
}
It looks like you copied and pasted some really old code. Besides, this is Swift, not Objective-C. Do not use NSURL or getResourceValue. Your code should look more like this:
if let rv = try file?.resourceValues(forKeys: [.isUbiquitousItemKey]) {
if let isInCloud = rv.isUbiquitousItem {
// and so on
}
}
And so on; the same pattern applied to other keys. Note that there is no .ubiquitousItemIsDownloadKey either. You can condense things like this:
if let rv = try file?.resourceValues(
forKeys: [.isUbiquitousItemKey, .ubiquitousItemDownloadingStatusKey]) {
if let isInCloud = rv.isUbiquitousItem {
if let status = rv.ubiquitousItemDownloadingStatus {
if status == .downloaded {
}
}
}
}

How to identify object that is deleted from a collection (array)?

I have following code to handle pull to refresh response
let copyData = data.reversed() // it is pull to refresh response (load page 1)
for (_,element) in copyData.enumerated() {
let foundElement = allObjects.filter{$0.id == element.id} // Find element in main array
if let firstElement = foundElement.first, let index = allObjects.index(of: firstElement) {
allObjects[index] = element // Replace if found
} else {
allObjects.insert(element, at: 0) // Insert if not found
}
}
self.arrayPosts = allObjects
Where data is codable class which is API response of pull to refresh. allObjects is preloaded data with pagination
Question : Suppose In allObjects i have 50 Object (5 Pages of 10 ID is (1 to 50))
User pull to refresh And I load first Page from API (ID 1,2,3,4,5,6,7,10,11) then how to identify which object is deleted (8,9) ?
Should I compare allObjects 's 10th index with data's 10th index object's ID ?
Is it better way to handle this ? Please suggest
Don't compare pages (i.e. 10 items at a time) - if an item is added/deleted the pages will get out of sync, and you'll end up with missing / duplicate objects.
Presumably your objects are sorted by some key / date, etc.
Take the value of the key in your last downloaded object.
Copy all the existing objects with keys <= that last key into a new array.
Compare your downloaded array against this sub-array.
Objects in the downloaded array that are not in the sub-array should be removed.
Here How I handle this. Code is quite complex for first time read but added comments to understand it
func handleResponse(page:Int,isForRefersh:Bool = false, data:Array<InspirePost>) {
guard data.count != 0 else {
// Check if we are requesting data from pull to referesh and First page is empty then we don't have data to show change state to empty
if self.arrayPosts.count == 0 || (isForRefersh && page == 1) {
self.state = .empty
self.tableView.reloadData()
} else {
self.state = .populated(posts: self.arrayPosts)
}
return
}
// Now we need to check if data called by referesh control then
//1) Replace object in array other wise just append it.
var allObjects = self.state.currentPost
if isForRefersh {
// If both array Has same number of element i.e both has page one loaded
if data.count >= allObjects.count {
allObjects = data
} else {
let copyData = data.reversed()
for (_,element) in copyData.enumerated() {
if let index = allObjects.firstIndex(where: {$0.id == element.id}) {
allObjects[index] = element // Replace if found
} else {
allObjects.insert(element, at: 0) // Insert if not
}
}
let minID = data.min(by: {$0.id ?? 0 < $1.id ?? 0})?.id
// DELETE item
let copyAllObject = allObjects
for (_,element) in copyAllObject.enumerated() {
guard let id = element.id, id >= minID ?? 0 else {
continue
}
if !data.contains(element) {
if let indexInMainArray = allObjects.index(where: {$0.id == id}) {
allObjects.remove(at: indexInMainArray)
}
}
}
}
//When we pull to refersh check the curent state
switch self.state {
case .empty,.populated : // if empty or populated set it as populated (empty if no record was avaiable then pull to refersh )
self.state = .populated(posts: allObjects)
case .error(_, let lastState) : // If there was error before pull to referesh handle this
switch lastState {
case .empty ,.populated: // Before the error tableview was empty or popluated with data
self.state = .populated(posts: allObjects)
case .loading,.error: // Before error there was loading data (There might more pages if it was in loading so we doing paging state ) or error
self.state = .paging(posts: allObjects, nextPage: page + 1)
case .paging(_,let nextPage): // Before error there was paging then we again change it to paging
self.state = .paging(posts: allObjects, nextPage: nextPage)
}
case .loading: // Current state was loading (this might not been true but for safety we are adding this)
self.state = .paging(posts: allObjects, nextPage: page + 1)
case .paging(_,let nextPage): // if there was paging on going don't break anything
self.state = .paging(posts: allObjects, nextPage: nextPage)
}
self.arrayPosts = allObjects
} else {
allObjects.append(contentsOf: data)
self.isMoreDataAvailable = data.count >= self.pageLimit
if self.isMoreDataAvailable {
self.state = .paging(posts: allObjects, nextPage: page + 1)
} else {
self.state = .populated(posts: allObjects)
}
self.arrayPosts = self.state.currentPost
}
self.tableView.reloadData()
}
Where I have
indirect enum PostListStatus {
case loading
case paging(posts:[InspirePost],nextPage:Int)
case populated(posts:[InspirePost])
case error (error:String,lastState:PostListStatus) // keep last state for future if we need to know about data or state
case empty
var currentPost:[InspirePost] {
switch self {
case .paging(let posts , _):
return posts
case .populated( let posts):
return posts
case .error( _, let oldPost) :
switch oldPost {
case .paging(let posts , _):
return posts
case .populated( let posts):
return posts
default:
return []
}
default:
return []
}
}
var nextPage : Int {
switch self {
case .paging(_, let page):
return page
default:
return 1
}
}
}
You may make set for both allObjects and data. Then use subtracting(_:) method in set to find the missing one.
Remove those missing ones from the main array and use it. Once you have correct main array elements, page them while displaying.

Speed up UISearchController with threads

I have a Greek dictionary app that searches tens of thousands of word entries from Sqlite to fill a UITableView. I'm encountering many of the same problems outlined here (keyboard lag as search text changes & needing to abort search and rerun if search text changes), and the solution provided works great.
My code:
func updateSearchResultsForSearchController(searchController: UISearchController) {
if (self.resultSearchController.searchBar.text == "") {
theResultsArray.removeAll(keepCapacity: true)
searchTable.reloadData()
return
}
self.theResultsArray = filter(self.wordArray) {
$0[self.betaNoSymbols].rangeOfString(self.resultSearchController.searchBar.text) != nil
// ($0[self.betaCode] as NSString).containsString(self.resultSearchController.searchBar.text)
}
searchTable.reloadData()
}
In the current example, I'm searching only one field but I'd like to search multiple, the Greek word with and without diacritics, and beta code (transliterated english) with and without symbols. I'm wondering if it'd be faster to query Sqlite or first place the word list in an Array, and if there's another solution with Swift and iOS 8. If I should adapt the code, help would be appreciated.
Update:
I've put the search in an async queue and it seems to be working. Using an array filter, the search for some reason stopped working after a bunch of edits to the UISearchBar, so it's now using Sqlite.
let searchQueue = dispatch_queue_create("searchQueue", DISPATCH_QUEUE_SERIAL)
func updateSearchResultsForSearchController(searchController: UISearchController) {
if (self.resultSearchController.searchBar.text == "") {
theResultsArray.removeAll(keepCapacity: true)
searchTable.reloadData()
println("Search field empty.")
return
}
let searchText = self.resultSearchController.searchBar.text
var tempArray = [Row]()
dispatch_async(searchQueue) {
// abort if text changed
if searchText != self.resultSearchController.searchBar.text {
println("Search changed, aborted.")
return
}
// do search
if resultSearchController.searchBar.selectedScopeButtonIndex == 0 {
tempArray = Array(self.liddellQuery.select(self._id, self.greekFullWord).filter(
like("%\(self.resultSearchController.searchBar.text!)%", self.betaNoSymbols)
|| like("%\(self.resultSearchController.searchBar.text!)%", self.betaSymbols)
|| like("%\(self.resultSearchController.searchBar.text!)%", self.greekFullWord)
|| like("%\(self.resultSearchController.searchBar.text!)%", self.greekLowercase)
|| like("%\(self.resultSearchController.searchBar.text!)%", self.greekNoSymbols) println("Searched: \(self.resultSearchController.searchBar.text), results: \(tempArray.count)")
} else {
...
}
// update if search still current
if searchText == self.resultSearchController.searchBar.text {
dispatch_async(dispatch_get_main_queue()) {
self.theResultsArray = tempArray
self.searchTable.reloadData()
}
}
}
}
I'm not sure if as is is the best way. There is also a long delay between searches and an UIIndicatorView to replace the search icon would be helpful.

Resources