Remove Custom Object From Array - ios

I would like to remove a custom object of class A from an array [A]()
I am well aware that this is a well discussed question on SO however there doesn't seem to be a generic and authoritative answer to it, either for beginners or experts.
Example: I have a FriendRequest class with a few attributes.
When I accept a FriendRequest, it should be removed from friendRequestArray.
I'm looking for an answer that aggregates the most common methods and best practices; something along the lines of:
As of Swift 2.1, there are 3 ways to do this (...)
please don't use X as it is deprecated (...)
Y is the wrong way to do this" etc.

I'm not sure I understand the question, let's see.
FriendRequest
You have a FriendRequest class like this
class FriendRequest {
let id: String
init(id: String) {
self.id = id
}
}
Let's make it Equatable
extension FriendRequest: Equatable {}
func ==(left: FriendRequest, right: FriendRequest) -> Bool {
return left.id == right.id
}
requests
Now we create 3 FriendRequest(s)
let request0 = FriendRequest(id: "000")
let request1 = FriendRequest(id: "001")
let request2 = FriendRequest(id: "002")
and let's create the requests array
var requests = [request0, request1, request2]
Removing a request
Now let's say we want to remove request1. We need to find the index (if it does exists) and use it to remove an element of the array.
if let index = requests.indexOf(request1) {
requests.removeAtIndex(index)
}
That's it
requests // [{id "000"}, {id "002"}]
Done with Swift 2.1.1 and Xcode Playground 7.2.

Related

Dictionary not appending values

I think I'm introducing some logic error and I might be missing something here.
Please consider the following code:
// Model
class MyModel: NSObject {
let month: Int
let destiny: String
init(month: Int, destiny: String) {
self.month = month
self.destiny = destiny
}
}
var datasource: [MyModel] = []
var dict: [Int : [MyModel]] = [:]
func fillDatasource() {
for _ in 0...20 {
let month = Int.random(in: 1...12)
let destiny = "Any"
let model = MyModel(month: month, destiny: destiny)
datasource.append(model)
}
}
func fillDict() {
datasource.forEach {
let month = $0.month
dict[month]?.append($0)
}
print(dict) // always empty
}
fillDatasource()
fillDict()
Inside my fillDict function the array is always nil.
I think this is because the key doesn't exist , so the value cannot be appended to that specific key.
My question is: if the key doesn't exist, calling the append function would insert the key as well?
Am I missing something here?
Thanks.
Your assumption is incorrect and there is no reason to think that this would insert a new array.
It might seem intuitive for this case but it may be very wrong for some cases. How about something like this:
garages[myName]?.parkCar(myCar)
Should this construct a new garage for my car? I think not. But even if so; what if default constructor is unavailable and this is actually defined as a protocol:
protocol Garage {
func parkCar(_ car: Car)
}
var garages[String: Garage]
there is no way for Swift to fill in this object automatically.
Technically there would be a possible solution for this work that Swift would automatically construct an object for you in dictionary if this object had a default constructor and possibly the object type is a struct or a final class... But this would most likely only introduce more confusion than it would solve.
The most straight forward solution to your example is what #Sh_Khan wrote (but later deleted) which is:
if dict[month] == nil {
dict[month] = [$0]
}
else {
dict[month]?.append($0)
}
Probably some more feasible approach would be
dict[month] = (dict[month] ?? []) + [$0]
but as described in a comment there is already a method that does exactly that for you:
dict[month, default: []].append($0)
I hope we can agree that this is a more general approach and it fixes all cases. For instance
garages[myName, default: PublicGarage(parkingSpot: myName)].parkCar(myCar)
You can update your fillDict method to the following:
func fillDict() {
datasource.forEach {
let month = $0.month
if dict.keys.contains(month) {
dict[month]?.append($0)
} else {
dict[month] = [$0]
}
}
print(dict)
}
Explanation:
We need to check if the month key already exits in the dictionary, than append in it's array else we are assigning a new array against a month in the dictionary
Dic is empty because dic[month] is nil, the value has never been altered.
To group array by a property of the array elem, I'd use the following:
dic = Dictionary(grouping: datasource) { (model) -> Int in
return model.month
}

How to use Generics to handle dictionaries and arrrays

I am new to using generics but as according to my requirement I need to use it.
I have like 10 apis in which
4 returns array of custom objects(like multiple Person object data([Person]))
4 returns simple a object(like Company object data(Company))
two return simple dictionary
so what I'm trying to do is to create a common Response class
class Response<T>: NSObject {
#objc var responseData = T
}
But it is giving error on this line.
How should I suppose to use it so that it fulfills the requirements.
first of all generics cannot be represented in objc. so you need to use only Swift
class Response<T> {
var responseData: T!
}
you can use then T like this:
let response = Response<[String]>()
so response.responseData will be an array of String

Realm is creating multiple entries for nested objects on update

I'm experiencing trouble understanding how updating objects works in Realm. I'd appreciate help in helping me to understand how updating nested objects work and why it doesn't work the way I expect it.
I started using Realm just recently, and here's what I want to use it for: I have a set of key value pairs stored on my server, that serve as localized values for strings used in my iOS app. On app launch every now and then I want to update my strings, so I pull them from the server and store them locally in realm on my iOS device. I want to have only ONE instance of those strings on my device.
Here are the classes:
import RealmSwift
public class LocalizedStrings: Object {
dynamic var id = 1
dynamic var version: String = ""
let assets = List<LocalizedString>()
override public static func primaryKey() -> String? {
return "id"
}
}
public class LocalizedString: Object {
dynamic var key: String = ""
dynamic var value: String = ""
}
Here's how I update the LocalizedStrings object:
realm.add(localizedStrings, update: true)
Here's how I access my strings:
func getLocalizedString(forKey key: String) -> String {
var result = key
try! realm.write {
let queryResult = realm.objects(LocalizedString.self).filter("key == %#", key)
// print(queryResult)
if queryResult.count == 1 {
result = queryResult[0].value(forKey: "value") as! String
}
}
return result
}
Now, I would expect, that whenever I update my LocalizedStrings, that the localizedStrings.assets list would get updated with new values. But instead, the assets are not updated, the list reference gets updated and I end up having multiple instances of the same string, which is not what I would expect from an update function. When I try to access a particular LocalizedString, it turns out there's multiple instances:
(...)
[19] LocalizedString {
key = update;
value = Update;
},
[20] LocalizedString {
key = update;
value = Update;
}
Perhaps I'm missing something obvious and I would really appreciate if someone could point me in the right direction, so I'd be able to achieve the behavior I'm looking for (which would be having the nested object actually updated, rather than having unnecessary duplicates of my objects).
Thanks!
Ok, so this answer helped me figure out, what was wrong with my setup. I was missing primaryKey in LocalizedString class.
From the answer above on how realm.add(object, update: true) works:
Documentation :
parameter object: The object to be added to this Realm.
parameter update: If true, the Realm will try to find an existing copy of the object (with the same primary
key), and update it. Otherwise, the object will be added.
So the same thing happens with nested objects. They can not be updated unless they have primaryKey.

Using .map to map an array of one class to an array of another

I'm new to Swift, and am trying to get my head around using .map. My understanding is that the behaviour is similar to that of Javascript, but maybe I'm not nailing the Swift syntax correctly?
I've created a public Array of my custom class CustomItem (which is a subclass of the type coming back in the response):
public var availableItems: [CustomItem] = []
public static func getAvailableItems(id: String, completion: (items: [CustomItem]) -> ()) -> Void {
DataConnector.getRelated(type: "users", id: id, relationship: "available") { (response) -> Void in
availableItems = (response.data?.map { return $0 as! CustomItem })!
completion(items: availableItems)
}
}
When I do a po response.data.map { return $0 } in the console with a breakpoint after the response is received, I get:
(lldb) po response.data.map { return $0 }
▿ Optional([<AnSDK.RemoteDataObject: 0x7f8a0b9c16b0>])
▿ Some : 1 elements
▿ [0] : < AnSDK.RemoteDataObject: 0x7f8a0b9c16b0>
So it definitely seems that part works, but when I try to cast the data object to CustomItem class, I get:
Could not cast value of type 'AnSDK.RemoteDataObject' (0x100abbda0) to 'Project.CustomItem' (0x100882c60).
Here's my CustomItem class just in case:
import AnSDK
public class CustomItem: RemoteDataObject {
var displayName: String = ""
var value: Float = 0.0
var owner: User?
}
If I don't use the ! to force downcast, I get:
RemoteDataObject is not convertible to CustomItem [...]
... in the compiler.
(I'm really just restating Ben Gottlieb's answer here, but hopefully a bit clearer since I believe some readers were confused by his attempt.)
The message seems fairly clear. You've received an array of AnSDK.RemoteDataObject. As best I can tell from your output, that is the actual class of the objects. You can't just say "it's really this subclass of RDO" unless it really is that subclass. Looking at your code, that seems unlikely. Somewhere in AnSDK it would have to construct a CustomItem and then just happen to return it as a RemoteDataObject. That doesn't appear to be what's happening inside of getRelated. Given your code, I doubt AnSDK knows anything about CustomItem, so how would it have constructed one?
There are numerous ways to fix this depending on what the types really are and how they interact. Ben's solution is one, which basically creates a copy of the object (though in that case, there's no particular reason for CustomItem to be a subclass of RDO, and probably shouldn't be.)
If you just want to add methods to RemoteDataObject, you can do that with extensions. You don't need to create a subclass.

How do I do indexOfObject or a proper containsObject

With an array: How do I do indexOfObject or a proper containsObject?
I mean I know I could just bridge the Array to NSArray and do it there ^^
But there must be a 'native' way of doing this
P.S. for the containsObject I guess I could filter the array too but for indexOf?
You can use the built-in find, and thus avoid bridging to Objective-C — but only if your element type is Equatable. (If it isn't Equatable, you can make it so with a comparison function and an extension.)
Example:
func == (lhs:Piece,rhs:Piece) -> Bool {
return lhs.val == rhs.val
}
class Piece:Equatable,Printable {
var val : Int
var description : String { return String(val) }
init (_ v:Int) {
val = v
}
}
Now you can call find(arr,p) where arr is an Array<Piece> and p is a Piece.
Once you have this, you can develop utilities based on it. For example, here's a global function to remove an object from an array without bridging to Objective-C:
func removeObject<T:Equatable>(inout arr:Array<T>, object:T) -> T? {
if let found = find(arr,object) {
return arr.removeAtIndex(found)
}
return nil
}
And test it like this:
var arr = [Piece(1), Piece(2), Piece(3)]
removeObject(&arr,Piece(2))
println(arr)
You can do this for NSObject subclasses too. Example:
func == (v1:UIView, v2:UIView) -> Bool {
return v1.isEqual(v2)
}
extension UIView : Equatable {}
Now you can call find on an Array of UIView. It's sort of a pain in the butt, though, having to do this for every single class where you want to be able to use find on an Array of that class. I have filed an enhancement request with Apple requesting that all NSObject subclasses be considered Equatable and that == should fall back on isEqual: automatically.
EDIT Starting in Seed 3, this is automatic for UIView and other NSObject classes. So find now just works for them.
EDIT 2 Starting in Swift 2.0, indexOf will exist as a method:
let s = ["Manny", "Moe", "Jack"]
let ix = s.indexOf("Moe") // 1
Alternatively, it takes a function that returns Bool:
let ix2 = s.indexOf {$0.hasPrefix("J")} // 2
Again, this works only on collections of Equatable, since obviously you cannot locate a needle in a haystack unless you have a way of identifying a needle when you come to it.
EDIT 3 Swift 2.0 also introduces protocol extensions. This means I can rewrite my global function removeObject as a method!
For example:
extension RangeReplaceableCollectionType where Generator.Element : Equatable {
mutating func removeObject(object:Self.Generator.Element) {
if let found = self.indexOf(object) {
self.removeAtIndex(found)
}
}
}
Since Array adopts RangeReplaceableCollectionType, now I can write code like this:
var arr = [Piece(1), Piece(2), Piece(3)]
arr.removeObject(Piece(2))
Oh, happy day!
Its actually able to be done in Swift. To get the index use find(YourArray, ObjectToFind)
As I was told, this isn't available yet / I have to bridge it to NSArray
I don't like this and it feels dirty so I went and did this in an extension. that way it hides the usage of NSArray and allows apple to provide it later
import Foundation
extension Array {
func contains(object:AnyObject!) -> Bool {
if(self.isEmpty) {
return false
}
let array: NSArray = self.bridgeToObjectiveC();
return array.containsObject(object)
}
func indexOf(object:AnyObject!) -> Int? {
var index = NSNotFound
if(!self.isEmpty) {
let array: NSArray = self.bridgeToObjectiveC();
index = array.indexOfObject(object)
}
if(index == NSNotFound) {
return Optional.None;
}
return index
}
//#pragma mark KVC
func getKeyPath(keyPath: String!) -> AnyObject! {
return self.bridgeToObjectiveC().valueForKeyPath(keyPath);
}
}
https://gist.github.com/Daij-Djan/9d1c4b1233b4017f3b67
Apple provide an example of exactly this in the The Swift Programming Language book. Specifically, see the section on Type Constraints in Action (p621 in the iBook).
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
for (index, value) in enumerate(array) {
if value == valueToFind {
return index
}
}
return nil
}
Everything depends upon your type implementing Equatable.
The Swift Programming Language covers that and explains how to implement that protocol:
“The Swift standard library defines a protocol called Equatable, which
requires any conforming type to implement the equal to operator (==)
and the not equal to operator (!=) to compare any two values of that
type. ”
NSHipster has a couple of relevant posts on this subject:
Swift Default Protocol Implementations
Swift Comparison Protocols
I also found this answer very useful in implementing Equatable:
How do I implement Swift's Comparable protocol?
Alhough it mentions Comparable, Equatable is a subset and the explanation is good.
Excerpts above from: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/gb/jEUH0.l
One other option is to use filter:
haystack.filter({$0 == needle}).count > 0
This checks to see if array haystack contains object needle.
You can't. That's why NSArray is still there. However, the Apple documentation reads as follows about String and NSString:
Swift’s String type is bridged seamlessly to Foundation’s NSString
class. If you are working with the Foundation framework in Cocoa or
Cocoa Touch, the entire NSString API is available to call on any
String value you create, in addition to the String features described
in this chapter. You can also use a String value with any API that
requires an NSString instance.
Following that approach, the NSArray API should be available on Array, but it isn't because the native Swift Array is a primitive (most likely a struct or similar), so you have to "convert" it to an NSArray.
It appears that not all of the toll-free bridging from NS/CF space is in place. However, if you declare your array as an NSArray, it works fine:
let fruits: NSArray = [ "apple", "orange", "tomato (really?)" ]
let index = fruits.indexOfObject("orange")
println("Index of Orange: \(index)")
If your array elements are objects and you want to find an identical object in that array, you can use this function:
func findObject<C: CollectionType where C.Generator.Element: AnyObject>(domain: C, value: C.Generator.Element) -> Int? {
for (index, element) in enumerate(domain) {
if element === value {
return index
}
}
return nil
}

Resources