.contains not working with custom object - arrays

I am using the below code to update my array. My requirement is that the new data does not exist, for that I am using the .contains method.
This method is not working properly when I change the viewcontroller and come again on same page. It returns false and I keep getting duplicate results due to that.
Is this an issue related to the custom object?
if let list = rooms as? [[String : AnyObject]]
{
// self.roomList = [Room]()
for item in list
{
let roomStr = json(from: item)
let roomObj : Room = Room(JSONString: roomStr!)!
if !self.roomList.contains(roomObj)
{
self.roomList.append(roomObj)
}
}
DispatchQueue.main.async {
//First sort by name and then sort by default room..
self.roomList.sort { $0.getRoomName()! < $1.getRoomName()! }
self.roomList.sort { $0.getDefaultRoom()! && !$1.getDefaultRoom()! }
self.LoadRoomsTableView.reloadData()
self.hideActivity()
}
}
Any idea how to solve it or any suggest on the effiecient way for add/update the data in array with swift.

You have to make sure your Room class/struct implements the protocol Equatable.
Example:
class Room {
let rooms: Int
let color: String
init(rooms: Int, color: String) {
self.rooms = rooms
self.color = color
}
}
extension Room: Equatable {
func ==(lhs: Room, rhs: Room) -> Bool {
return lhs.rooms == rhs.rooms && lhs.color == rhs.color
}
}
If you had a struct and Swift > 4.1+ you would not have this problem, as the == is kindly provided by Swift. Thanks Vadian for the reminder.

Related

Swift array binding filtering - Conformance of Binding<Value> to Sequence

I'm using the swift package [Defaults][1] to manage a list of preferences in my app.
One of this property is an array of structures that I get using #Default(.list) var list.
In Swift UI, I loop on this list to edit the various elements.
#Default(.list) var list;
ForEach($list, id: \.wrappedValue.id) { element in
...
}
It's working fine and it works as expected.
My problem is that I need to filter this list.
I'm using $list.filter(...) but I get a warning Conformance of 'Binding<Value>' to 'Sequence' is only available in MacOS 12.0.
Unfortunately, my app needs to run on MacOS 11.x.
I don't really understand what the warning means and how I can adapt my code to make it works with MacOS 11.x.
Thanks!
-- Update --
struct Stock: Codable, Identifiable, DefaultsSerializable {
var id: String
var currency: String = "USD"
}
extension Defaults.Keys {
static let stocks = Key<[Stock]>("stocks", default: [])
}
struct StocksView: View {
#Default(.stocks) var stocks
func filterStocks(stock: Stock) -> Bool
{
return stock.currency == "USD"
}
var body: some View {
ForEach($stocks.filter(filterStocks, id: \.wrappedValue.id) { stock in
....
}
}
}
extension Binding where Value == [Stock] {//where String is your type
func filter(_ condition: #escaping (Stock) -> Bool) -> Binding<Value> {//where String is your type
return Binding {
return wrappedValue.filter({condition($0)})
} set: { newValue in
wrappedValue = newValue
}
}
}
[1]: https://github.com/sindresorhus/Defaults
When you use $list.filter your are not filtering list you're filtering Binding<[List]> which doesn't conform to the protocol Sequence.
Add this extension to your project and see if it works
extension Binding where Value == [String] {//where String is your type
func filterB(_ condition: #escaping (String) -> Bool) -> Binding<Value> {//where String is your type
return Binding {
return wrappedValue.filter({condition($0)})
} set: { newValue in
wrappedValue = newValue
}
}
}
Edit: Binding in a ForEach requires SwiftUI 3+
However, you can pass the normal array and whenever you need a Binding use this function:
extension Stock {
func binding(_ array: Binding<[Stock]>) -> Binding<Stock> {
Binding {
self
} set: { newValue in
guard let index = array.wrappedValue.firstIndex(where: {$0.id == newValue.id}) else {return}
array.wrappedValue[index] = newValue
}
}
}
So your code would look like this:
ForEach(stocks.filter(filterStocks), id: \.id) { stock in
....
SomeViewRequiringBinding(value: stock.binding(stocks))
}

How we can achieve this Filter in Swift

How we can achieve this Filter in Swift.
I have exactly same problem and i am trying this way and i found this solution on stack overflow
but this is written in Javascript and i need code in Swift language.
Getting this error
Cannot convert value of type '[Model]' to closure result type
'GetModel'
My Code and Model
extension Array where Element == GetModel{
func matching(_ text: String?) -> [GetModel] {
if let text = text, text.count > 0{
return self.map{
$0.data.filter{
$0.name.lowercased().contains(text.lowercased())
}
}
}else{
return self
}
}
}
// MARK: - GetModel
struct GetModel: Codable {
let id: Int
let name: String
var data: [Model]
}
// MARK: - Model
struct Model:Codable {
let id: Int
let name: String
var isSelected: Bool? = nil
}
You are making two mistakes. First you are using map but you should be using filter. Second you are using filter when you should be using contains(where:). Note you can. use localizedStandardCompare instead of lowercasing your string.
Note: You shouldn't check if your string count is greater than zero. String has an isEmpty property exactly for this purpose.
To check whether a collection is empty, use its isEmpty property
instead of comparing count to zero. Unless the collection guarantees
random-access performance, calculating count can be an O(n) operation.
extension RangeReplaceableCollection where Element == GetModel {
func matching(_ text: String?) -> Self {
guard let text = text, !text.isEmpty else { return self }
return filter { $0.data.contains { $0.name.localizedStandardContains(text) } }
}
}
edit/update:
If you need to filter your GetModal data:
extension RangeReplaceableCollection where Element == GetModel, Self: MutableCollection {
func matching(_ text: String?) -> Self {
guard let text = text, !text.isEmpty else { return self }
var collection = self
for index in collection.indices {
collection[index].data.removeAll { !$0.name.localizedStandardContains(text) }
}
collection.removeAll(where: \.data.isEmpty)
return collection
}
}

Cast to right generic from array in Swift

I have a Protocol called Composite.
This protocol has an array composites: [Composite]
I also have a generic subclass GenericSubclass<T>: Composite
When iterating over the array the best I can come up with looks like this:
for item in composites {
if let item = item as? GenericSubclass<A> {
let sc = SomeOtherClass<A>
} else if let item = item as? GenericSubclass<B> {
let sc = SomeOtherClass<B>
} //and so on...
}
Is there any way to get a hold of GenericSubclass without specifying the Generic? In my use case there is absolutely no need for me to know about the T. I just have to instantiate another class with the same generic type.
Any help is much appreciated.
It's not clear what you're trying to accomplish with the "generic" (pun intended) class names you've chosen. I don't think there's a way to directly accomplish what you want. I.e. you can't just leave it as a generic T because the compiler needs some way to determine what T will be in use at runtime.
However, one way to solve the issue is to hoist the API into the Composite protocol:
protocol Composite {
var composites: [Composite] { get set }
func otherClass() -> OtherProtocol
}
protocol OtherProtocol { }
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
func otherClass() -> OtherProtocol {
return SomeOtherClass<T>()
}
}
class SomeOtherClass<T>: OtherProtocol {}
So now when you implement your loop, you can rely on the fact that since each element is a Composite, you know it must provide an instance of OtherProtocol via the otherClass() method:
var c = GenericSubclass<Int>()
c.composites = [GenericSubclass<Double>(), GenericSubclass<Int>(), GenericSubclass<Character>()]
for item in c.composites {
let sc = item.otherClass()
print(sc)
}
Alternatively, if only GenericSubclass should vend an OtherProtocol, you can make the return type Optional and define an extension for all the other implementations of Composite:
protocol Composite {
var composites: [Composite] { get set }
func optionalClass() -> OtherProtocol?
}
extension Composite {
func optionalClass() -> OtherProtocol? {
return nil
}
}
I did some experiment on this in the playground and i came up with this
protocol Composite {
var composites: [Composite] { get set }
}
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
}
let subclass = GenericSubclass<String>()
for item in subclass.composites {
let className = String(describing: type(of: item))
let aClassType = NSClassFromString(className) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(instance) //Output: GenericSubclass<String>
}
Hope this will help someone.
I think it's not possible to do that in array.
While you creat some different GenericSubclass<T> then put it in array , you will lose <T> no matter the composites is [Composite] or [Any].
// this line won't compile
let array = [GenericSubclass<Int>(),GenericSubclass<Double>()]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
You want donging something like this func below, the param should be GenericSubclass<T> to compile success
func genericFunc<T>(param:GenericSubclass<T>) {
let sc = SomeOtherClass<T>()
print(sc)
}
Anyway you can implement it with member var for the instance like the code below:
class Subclass {
var type : Any
init(type : Any) {
self.type = type
}
}
class SomeOtherClass : CustomDebugStringConvertible{
var type : Any
init(type : Any) {
self.type = type
}
var debugDescription: String{
return String(describing: type.self)
}
}
let array : [Subclass] = [Subclass(type : Int.self),Subclass(type : Double.self),Subclass(type : String.self)]
let scArray = array.flatMap {SomeOtherClass(type:$0.type.self)}
print(scArray) // prints [Int, Double, String]
You need to add one method to protocol which creates new item of Type supported this protocol. So now you can use enums, structs and classes without any knowledge of creating object of specific type.
You can play in playground with the following code:
import UIKit
//This is your protocol
protocol MyAwesomeProtocol {
//this methods leaves implementaion detailes
//to concrete type
func createNewObject()->MyAwesomeProtocol
}
//Just create empty string
extension String: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return String()
}
}
//create Enum with default value
extension UIControlState: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return UIControlState.normal
}
}
//create viewController of any type
extension UIViewController: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return type(of:self).init()
}
}
//This is test function
//it creates array of newly created items and prints them out
//in terminal
func doSomeCoolStuffWith(items:[MyAwesomeProtocol]){
var newItems = [MyAwesomeProtocol]()
for anItem in items {
let newOne = anItem.createNewObject()
newItems.append(newOne)
}
print("created new ones:\n\(newItems)\nfrom old ones:\n\(items)\n")
}
doSomeCoolStuffWith(items: [UIControlState.focused,UIControlState.disabled])
doSomeCoolStuffWith(items: [UISplitViewController(),UINavigationController(),UICollectionViewController()])
doSomeCoolStuffWith(items: ["I","love","swift"])
This will produce the following result:
created new ones:
[__C.UIControlState(rawValue: 0), __C.UIControlState(rawValue: 0)]
from old ones:
[__C.UIControlState(rawValue: 8), __C.UIControlState(rawValue: 2)]
created new ones:
[<UISplitViewController: 0x7fa8ee7092d0>, <UINavigationController: 0x7fa8f0044a00>, <UICollectionViewController: 0x7fa8ee705f30>]
from old ones:
[<UISplitViewController: 0x7fa8ee7011e0>, <UINavigationController: 0x7fa8f004e600>, <UICollectionViewController: 0x7fa8ee708fb0>]
created new ones:
["", "", ""]
from old ones:
["I", "love", "swift"]

Check If Swift Dictionary Contains No Values?

So I'm making a to-do list app, and I want the user to be notified when all of the shopping items have been deleted. I have a dictionary that contains the String:store as a key and the [String]:items as the values. Is there a fast way to check if all of the items' arrays are empty?
There's the easy way:
dicts.values.flatten().isEmpty
But that will walk through all the lists without any shortcuts. Usually that's really not a problem. But if you want to bail out when you find a non-empty one:
func isEmptyLists(dict: [String: [String]]) -> Bool {
for list in dicts.values {
if !list.isEmpty { return false }
}
return true
}
Of course you can make this much more generic to get short-cutting and convenience:
extension SequenceType {
func allPass(passPredicate: (Generator.Element) -> Bool) -> Bool {
for x in self {
if !passPredicate(x) { return false }
}
return true
}
}
let empty = dicts.values.allPass{ $0.isEmpty }
You can just use isEmpty
var dict: Dictionary<Int, String> = [:]
var result = dict.isEmpty
result will be true
A functional programming approach:
let allEmpty = arr.reduce(true) { $0 && $1.1.isEmpty }
If you're not a big fan of implicit closure arguments, you can of course name them:
let allEmpty = arr.reduce(true) { empty, tuple in empty && tuple.1.isEmpty }

Swift 1.2 Filter an Array of Structs by keyword

I need some help filtering an array of Structs.
This is what I am doing currently, it filters the array but not correctly.
For example lets say I search for an item in the array with "Mid" I have one item that should be shown however the item shown starts with "Bad".
var array = breweries.filter() { $0.name?.lowercaseString.rangeOfString(searchController.searchBar.text.lowercaseString) != nil }
results = array
here is my Struct
struct Breweries {
let name: String?
let breweryId: String?
let distance: Double?
let largeIconURL: String?
let streetAddress: String?
let locality: String?
let region: String?
let phone: String?
let website: String?
init(brewDictionary: [String: AnyObject]) {
name = brewDictionary["brewery"]?["name"] as? String
breweryId = brewDictionary["breweryId"] as? String
distance = brewDictionary["distance"] as? Double
largeIconURL = brewDictionary["brewery"]?["images"]??.objectForKey("large") as? String
streetAddress = brewDictionary["streetAddress"] as? String
locality = brewDictionary["locality"] as? String
region = brewDictionary["region"] as? String
phone = brewDictionary["phone"] as? String
website = brewDictionary["website"] as? String
}
}
Please point in the right direction!
Note: I am using Swift 1.2
Update:
I thought a video would be of help to better explain what I am trying to do.
Demo Of issue
What I want is to find the filter the array so only the item with a similar name is shown.
Update 2: As it turns out I forgot to handle the case when my UISearchController was active.
Assuming your Struct name is Breweries and it has a name property, try this:
let array = breweries.filter() {
($0.name!.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
}
Your usage of filter is correct, but your closure seem to be complicated with no clear goal. I suggest you to write an extension (or possibly use what I am using):
extension String {
func contains(search: String, ignoreCase: Bool = false, ignoreDiacritic: Bool = false) -> Bool {
var options = NSStringCompareOptions.allZeros
if ignoreCase { options |= NSStringCompareOptions.CaseInsensitiveSearch }
if ignoreDiacritic { options |= NSStringCompareOptions.DiacriticInsensitiveSearch }
return self.rangeOfString(search, options: options) != nil
}
}
This way you can use closure like this to search:
breweries.filter() {
$0.name?.contains("x") // Precise search
$0.name?.contains("x", ignoreCase: true, ignoreDiacritics: true) // Ignores diacritics and lower / upper case
}
of course, you can use | or & to search for multiple parameters
breweries.filter() {
$0.name?.contains("x") || $0.streetAddress?.contains("x")
}
Hope it helps!
Here is an example from an investing app with struct:
import Foundation
public struct SNStock {
public let ticker:NSString
public let name:NSString
init(ticker:NSString, name:NSString) {
self.ticker = ticker
self.name = name
}
}
Search on Main Thread:
public func searchStocksByKeyword(keyword:String) -> [SNStock] {
let lowercaseKeyword = keyword.lowercaseString
var searchResults:[SNStock] = []
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.ticker.lowercaseString.hasPrefix(lowercaseKeyword)
})
if (searchResults.count == 0) {
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.name.lowercaseString.hasPrefix(lowercaseKeyword)
})
}
searchResults.sortInPlace {
($0.ticker as String) < ($1.ticker as String)
}
return searchResults;
}
Search on Background Thread:
public func searchStocksByKeyword(keyword:String, completion:(stocks:[SNStock])->()) {
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let stocks:[SNStock] = self.searchStocksByKeyword(keyword)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(stocks: stocks)
})
})
}

Resources