Safe and elegant way to delete element in custom array, Swift [duplicate] - ios

This question already has answers here:
Swift: Better way to remove a specific Object from an array?
(5 answers)
Closed 2 years ago.
I have this custom array:
var customArray = [CustomItem]()
Custom item is:
class CustomItem {
let firstItem: Enumeration
let data: Any
// ...
}
Enumeration contains an enumaration with different case. For example case example1, case example2 etc.
I add element with append with all the info like firstItem (enumeration etc).
What I need is to check if in my customArray I have a given item in my enumeration. Let's say I have to check if in customArray the enumeration .example1 exist (because I append it before) and in case delete it.
What is the safest and most elegant way to perform this?
Extra: what If i would like to add one element at the end of this custom arrray?

if you want find a specific item index you can use firstIndex(of: )
like this:
let index = customArray.firstIndex(of: CustomItem)
customArray.remove(at: index)
EDIT:
You have to make your object Equatable with an extension:
enter codextension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
} }
Now you can compare the objects with each other.
this is my code from playground:
enum Enumeration {
case opt1
case op2
}
struct CustomItem {
var firstItem: Enumeration
let data: Any
}
extension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
}
}
class ViewController: UIViewController {
var customArray = [CustomItem]()
func searchAndDestory() {
let index = customArray.firstIndex(of: CustomItem.init(firstItem: .op2, data: 0)) ?? 0
customArray.remove(at: index)
}
}

Related

Swift workaround for protocols comparison

I know comparing protocols doesn't make any sense but my situation is dictated by choices and decisions taken before me.
A table view's data source is an array of RowViewModel.
protocol RowViewModel {}
there's nothing in there (yet) to make it even conform to Equatable.
Then my table has different cells, all of which implement that protocol:
func getCells() -> [RowViewModel] {
var rows = [RowViewModel]()
rows.append(Cell1ViewModel())
rows.append(Cell2ViewModel())
rows.append(Cell3ViewModel())
return rows
}
Cell's view model:
class Cell1ViewModel: RowViewModel {
var cellTitle: String
...
}
This structure is convenient but it now shoots me in the back because I now need to calculate delta to send specific tableView indexes to insert / delete rows. To calculate delta I need RowViewModel to conform to Equatable, which is possible but seems like a workaround that defies the initial point of using this approach. I'd like to do something like this:
let oldList = rows
let newList = getCells()
let deltaAdded = newList.filter { !oldList.contains($0) }.compactMap { newList.firstIndex(of: $0) }
let deltaRemoved = oldList.filter { !newList.contains($0) }.compactMap { oldList.firstIndex(of: $0) }
What is the best practice here? Is there a way to write a comparison function for concrete types conforming to the RowViewModel?
As I told in comment you would have something like:
class CellViewModel1: Equatable {
// classes need explicit equatable conformance.
static func == (lhs: CellViewModel1, rhs: CellViewModel1) -> Bool {
// your implementation
return true
}
}
enum RowViewModel: Equatable {
// enum automatically is Equatable as long as all cases have Equatable associated types
case cell1(CellViewModel1)
}
func test() {
let oldList = [RowViewModel]()
let newList = [RowViewModel]()
let deltaAdded = newList.filter { !oldList.contains($0) }.compactMap { newList.firstIndex(of: $0) }
let deltaRemoved = oldList.filter { !newList.contains($0) }.compactMap { oldList.firstIndex(of: $0) }
}
Notice that both enum and ViewModels must conform to Equatable.
Still not 100% sure if this fits your necessities.

Find if user is already displayed in TableView using Swift [duplicate]

This question already has answers here:
How to throttle search (based on typing speed) in iOS UISearchBar?
(12 answers)
Closed 5 years ago.
I'm coding a viewController which permit to search specifics users. I have a searchBar and a listener:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
doSearch()
}
func doSearch() {
if let searchText = searchBar.text?.lowercased() {
self.users.removeAll()
self.tableView.reloadData()
Api.User.queryUsers(withText: searchText, completion: { (user) in
self.users.append(user)
self.tableView.reloadData()
})
}
}
The queryUser func:
func queryUsers(withText text: String, completion: #escaping (Userm) -> Void) {
REF_USERS.queryOrdered(byChild: "username_lowercase").queryStarting(atValue: text).queryEnding(atValue: text+"\u{f8ff}").queryLimited(toLast: 5).observeSingleEvent(of: .value) { (snapshot) in
snapshot.children.forEach({ (s) in
let child = s as! DataSnapshot
if let dict = child.value as? [String: Any] {
let user = Userm.transformUser(dict: dict, key: child.key)
completion(user)
}
})
}
}
The problem is that, with Firebase, when I write too fast I can have the same user displayed several times. I would like to have a function which can do something like this:
// if user is not already contained in the array
-> Display it
I tried to use the .contains() function but I have not arrived at a good result, I'm still a beginner in Swift.
Do you have any idea please?
You can choose to filter the array the TableView uses to display data. This can be done either by casting it to a Set (since Sets only contain unique values) and then back to an Array again, or with an extension to the User class so it conforms to the Equatable protocol. Using the set option can be done like this:
let array = ["one", "one", "two", "two", "three", "three"]
let unique = Array(Set(array))
// ["one", "two", "three"]
Using the protocol would look something like this:
extension User: Equatable {
static func == (lhs: User, rhs: User) -> Bool {
//make sure the User class has something that can uniquely identify them.
return lhs.identifier == rhs.identifier
}
}
I think you can use filters to get a valid array.
something like this:
var someObject:SomeObject = receivedObjectFromMars
var arrayOfObjects:Array<SomeObject> = [object1, object2, object3, object4]
// imagine that 'someObject' has the same email of object1 and object 4.
var filteredArray:Array<String> = [];
filteredArray = arrayOfObjects.filter({ (obj) -> Bool in
//so when you filter the array, you will compare a specific
// attribute (e.g: email)
return obj.email != someObject.email
})
// current filteredArray = [object2, object3]
So, after that, if you want to add the specific element, you just add.
filteredArray.append(someObject);
You can use it with other types, like strings, int, whatever.
get help here, too: Swift 3 filter array of objects with elements of array
JLU.

How to alphabetize an array of objects within Swift 3?

I try to list an array of objects in alphabetic order. I create this simple test, but doesn't know how to achieve this with an array of objects:
let names = ["Martin", "Nick", "Alex", "Ewa", "Barry", "Daniella", "Chris", "Robert", "Andrew"]
func alphabetizeArray(_ s1: String, _ s2: String) -> Bool {
return s1 < s2
}
let alphabeticNames = names.sorted(by: names)
print(reversedNames)
When I try this for an array of objects I came up with something like this:
func sorterForIDASC(this:People, that:People) -> Bool {
return this.name > that.name
}
peoples.sort(by: sorterForIDASC)
But this will give me an error of: Binary operator '>' cannot be applied to two 'String?' operands
Anyone suggestions how to solve this. I would examine the names of each object that is from the type of String. I use Swift 3/Xcode8.
If you only need > then implementing > for your optional property is sufficient:
func >(lhs: People, rhs: People) -> Bool {
if let left = lhs.name, let right = rhs.name {
return left > right
}
return false
}
Now you can use > on an array of your objects:
let result = arrayOfObjects.sorted(by: >)
You could also have your object conform to Equatable and implement at least == and > for the optional property:
struct People: Equatable {
var name: String?
}
func ==(lhs: People, rhs: People) -> Bool {
if let left = lhs.name, let right = rhs.name {
return left == right
}
return false
}
func >(lhs: People, rhs: People) -> Bool {
if let left = lhs.name, let right = rhs.name {
return left > right
}
return false
}
This opens even more possibilities.
In Swift 3.0
you can do that simply like this.
peoples.sort(by: { (this: People, that: People) -> Bool in
this. name < that. name
})

Custom collection in swift: is it a right way?

I am learning swift. I would like to use a custom class to be loopable [able to a for...in loop] like Array. Below is the given sample code that so far, I have tried. The class in question is "GuestManager" which is holding a private collection of guests [objects of class Guest]
import Foundation
class Guest{
var guestId: String
var guestName: String
init(gId: String, name: String){
self.guestId = gId
self.guestName = name
}
}
class GuestManager: GeneratorType, SequenceType{
private var guests = [Guest]?()
private var nextIndex: Int
init(guests: [Guest]){
self.guests = guests
self.nextIndex = 0
}
func next() -> Guest? {
if self.nextIndex > (self.guests?.count)! - 1 {
self.nextIndex = 0
return nil
}
let currentGuest = self.guests![self.nextIndex]
self.nextIndex += 1
return currentGuest
}
subscript(aGuestId gID: String) -> Guest{
return (self.guests?.filter({$0.guestId == gID}).first)!
}
}
I do not want to create separate classes that are conforming to GeneratorType & SequenceType protocols. Instead I have created a single class that is conforming to both protocols.
Below are some of my questions:
I would like to know if this a correct way to have a custom collection type ?
Can we use subscript as a way to perform a search based on a property for example "subscript(aGuestId gID: String)" in the sample code above ?
It is clear from the code for next() function implementation in above sample code that is resetting the "nextIndex" when the iteration reached at the end. How one will handle the situation wherein we use a break statement inside the for...in loop as below:
for aGuest in guestManager{//guestManager an instance of GuestManager class instantiated with several guest objects
print(aGuest.guestName)
}
for aG in guestManager{
print(aG.guestId)
break
}
In the 2nd for loop the code break out after getting the first Element [Guest object in this case]. The subsequent for loop will start at index 1 in the collection and not at 0. Is there anyway to handle this break situation so that for each subsequent for looping the index is always set to 0?
Thanks
Edit: It seems the "nextIndex" reset issue can be fixed with below code [added inside GuestManager class] for generate() method implementation
func generate() -> Self {
self.nextIndex = 0
return self
}
You should not store the nextIndex inside the class. You can use a local variable in the generate method and then let that variable be captured by the closure you pass to the generator you create in that method. That’s all you need to adopt SequenceType:
class GuestManager: SequenceType{
private var guests: [Guest]
init(guests: [Guest]) {
self.guests = guests
}
func generate() -> AnyGenerator<Guest> {
var nextIndex = 0
return AnyGenerator {
guard nextIndex < self.guests.endIndex else {
return nil
}
let next = self.guests[nextIndex]
nextIndex += 1
return next
}
}
}
For subscripting, you should adopt Indexable. Actually, the easiest way to fulfill all your requirements is to pass as much of the logic for SequenceType, Indexable, and eventually (if you want to support it) CollectionType, to your array, which already has these capabilities. I would write it like this:
class GuestManager {
private var guests: [Guest]
init(guests: [Guest]){
self.guests = guests
}
}
extension GuestManager: SequenceType {
typealias Generator = IndexingGenerator<GuestManager>
func generate() -> Generator {
return IndexingGenerator(self)
}
}
extension GuestManager: Indexable {
var startIndex: Int {
return guests.startIndex
}
var endIndex: Int {
return guests.endIndex
}
subscript(position: Int) -> Guest {
return guests[position]
}
}
Some more observations:
Your guests property should not be an optional. It makes the code more complicated, with no benefits. I changed it accordingly in my code.
Your Guest class should probably be a value type (a struct). GuestManager is also a good candidate for a struct unless you require the reference semantics of a class (all collection types in the standard library are structs).
I think the subscripting approach you're trying here is kind of convoluted. Personally, I would use a function to do this for the sake of clarity.
guestManager[aGuestId: guestId]
guestManager.guestWithID(guestId)
So stylistically I would probably land on something like this
import Foundation
class Guest{
var guestId: String
var guestName: String
init(guestId: String, guestName: String){
self.guestId = guestId
self.guestName = guestName
}
}
class GuestManager: GeneratorType, SequenceType{
private var guests: [Guest]
private var nextIndex = 0
init(guests: [Guest]){
self.guests = guests
}
func next() -> Guest? {
guard guests.count < nextIndex else {
nextIndex = 0
return nil
}
let currentGuest = guests[nextIndex]
nextIndex += 1
return currentGuest
}
func guestWithID(id: String) -> Guest? {
return guests.filter{$0.guestId == id}.first ?? nil
}
}

Cannot invoke 'indexOf' with an argument list of type '(ChecklistItem)'

When I am writing code for finding an item from the array with the use of indexOf it shows me the above stated error.
Here is my code:-
func addItemViewController(controller: AddItemViewController, didFinishEditingItem item: ChecklistItem)
{
if let index = items.indexOf(item)
{
let indexPath = NSIndexPath(forRow: index, inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath)
{
configureTextForCell(cell, withChecklistItem: item)
}
}
In order to use indexOf the ChecklistItem must adopt Equatable protocol. Only by adopting this protocol the list can compare an item with other items to find the desired index
indexOf can only be applied to Collections of Equatable types, your ChecklistItem doesn't conform to Equatable protocol (have an == operator).
To be able to use indexOf add this to the file containing ChecklistItem class in the global scope :
func ==(lhs: ChecklistItem, rhs: ChecklistItem) -> Bool {
return lhs === rhs
}
Swift3:
public static func ==(lhs: Place, rhs: Place) -> Bool {
return lhs === rhs
}
Please note it will make comparison by comparing instances addresses in memory. You may want to check equality by comparing members of the class instead.
In Swift 4 and Swift 3, update your Data Model to conform to "Equatable" protocol, and implement the lhs=rhs method , only then you can use ".index(of:...)", because you are comparing your custom object
Eg:
class Photo : Equatable{
var imageURL: URL?
init(imageURL: URL){
self.imageURL = imageURL
}
static func == (lhs: Photo, rhs: Photo) -> Bool{
return lhs.imageURL == rhs.imageURL
}
}
Usage:
let index = self.photos.index(of: aPhoto)
I realize this question already has an accepted answer, but I found another case that will cause this error so it might help someone else. I'm using Swift 3.
If you create a collection and allow the type to be inferred you may also see this error.
Example:
// UITextfield conforms to the 'Equatable' protocol, but if you create an
// array of UITextfields and leave the explicit type off you will
// also see this error when trying to find the index as below
let fields = [
tf_username,
tf_email,
tf_firstname,
tf_lastname,
tf_password,
tf_password2
]
// you will see the error here
let index = fields.index(of: textField)
// to fix this issue update your array declaration with an explicit type
let fields:[UITextField] = [
// fields here
]
The possible reason is you didn't tell the ChecklistItem type that it is equatable, maybe you forgot to mention ChecklistItem class is inherited from NSObject.
import Foundation
class ChecklistItem: NSObject {
var text = ""
var checked = false
func toggleChecked() {
checked = !checked
}
}
NSObject adopts or conforms to the equatable protocol

Resources