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

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))
}

Related

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
}
}

Storing and retrieving class type as string in a Swift array, then casting a generic to known class string

I'm continuing my journey to discover the love and hate for Swift's generics, and in the end, I'm still struggling with a fundamental flaw that I can't get around: when storing a generic in an array (even with fancy type erasure), I still need to explicitly cast the resulting value in the array to a known type before I can get or set properties in the array.
I know the type, but the only way I can seem to store it, is by the string name of the class (since you can't make an array of types it seems). Maybe there is a proper way to code/decode the type so it can be stored in an array? I tried with NSClassFromString, but didn't get very far.
Here is a playground that illustrates the challenge:
enum Apple: String {
case braeburn
case macintosh
case honeycrisp
}
protocol AppleProtocol {
var brand: Apple { get set }
}
protocol AppleGetter {
func getApple<T>(for key: Apple) -> T?
}
protocol PropertyReflectable { }
extension PropertyReflectable {
subscript(key: String) -> Any? {
let m = Mirror(reflecting: self)
return m.children.first { $0.label == key }?.value
}
}
struct GenericApple<T: Equatable>: AppleProtocol, Hashable {
static func == (lhs: GenericApple<T>, rhs: GenericApple<T>) -> Bool {
return lhs.brand == rhs.brand
}
var hashValue: Int { return brand.hashValue }
var brand: Apple
var generic: T
init(brand: Apple, generic: T) {
self.brand = brand
self.generic = generic
}
}
struct Apples {
typealias Braeburn = GenericApple<Int>
var braeburn = Braeburn(brand: .braeburn, generic: 10)
typealias Honeycrisp = GenericApple<String>
var honeycrisp = Honeycrisp(brand: .honeycrisp, generic: "A generic")
}
extension Apples: PropertyReflectable {
func getApple<T>(for key: Apple, type: T.Type) -> T? {
return self[key.rawValue] as? T
}
}
This works great!
var applesSet = Apples()
var braeburn = applesSet.getApple(for: Apple.braeburn, type: Apples.Braeburn.self)
braeburn?.generic = 14
print(braeburn?.generic)
But what if I want to do:
struct AppleListElement {
let brand: Apple
let type: String
}
var apples = [AppleListElement]()
apples.append(AppleListElement(brand: .braeburn, type: "\(Apples.Braeburn.self)"))
apples.append(AppleListElement(brand: .honeycrisp, type: "\(Apples.Honeycrisp.self)"))
apples.forEach {
applesSet.getApple(for: $0.brand, type: NSClassFromString($0.type))
}

.contains not working with custom object

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.

Why does the Swift Compiler throw an error on inferring the type of my Generic Element when the Element is clearly constrained?

As I was working with implementing an Array that keeps weak references to its elements I stumbled on a Compile Error as soon as I used the methods of the Collection extension methods, before using the Collection method the code compiled correctly and expectedly.
Expected Behavior
The code should compile with no error.
Current Behavior
The compiler throws the two following Errors:
WeakRef requires that Element? be a class type
Could not infer type for 'items'
Possible Solution
The only solution I found is to make the property items public and use a for-loop instead of the Collection extensions methods. After doing this the compiler was able to infer the type for items and even the Collection methods worked.
Steps to Reproduce
First implement the WeakRef class:
final class WeakRef<T: AnyObject> {
weak var value: T?
init(_ value: T) {
self.value = value
}
}
Second implement the WeakArray struct:
struct WeakArray<Element: AnyObject> {
public var items: [WeakRef<Element>] = []
init(_ elements: [Element]) {
items = elements.map { WeakRef($0) }
}
}
Third implement the Collection extension implementation:
extension WeakArray: Collection {
var startIndex: Int { return items.startIndex }
var endIndex: Int { return items.endIndex }
subscript(_ index: Int) -> Element? {
return items[index].value
}
func index(after idx: Int) -> Int {
return items.index(after: idx)
}
}
Fourth create an instance of the WeakArray property not in the same source file however as WeakArray for example:
var objects: WeakArray<UIViewController> = WeakArray.init([])
Fifth and final step call a method of the Collection protocol for example:
objects.forEach({ $0?.view.backgroundColor = .white })
Context (Environment)
This code won't compile on the version of Xcode Version 9.3.1 (9E501) using Swift 4.1
Additional Description
The solution for the above code was found in the following links:
https://marcosantadev.com/swift-arrays-holding-elements-weak-references/
https://www.objc.io/blog/2017/12/28/weak-arrays/
Thank you in advance to any help provided. This post was completely edited to fit Stackoverflow standards of asking a question. Special thanks to MartinR for guiding me to post a good question on Stackoverflow.
A Collection has an associated Element type, and that seems to
conflict with your generic Element placeholder. Using a different
name E for the placeholder solves the problem:
struct WeakArray<E: AnyObject> {
public var items: [WeakRef<E>] = []
init(_ elements: [E]) {
items = elements.map { WeakRef($0) }
}
}
extension WeakArray: Collection {
var startIndex: Int { return items.startIndex }
var endIndex: Int { return items.endIndex }
subscript(_ index: Int) -> E? {
return items[index].value
}
func index(after idx: Int) -> Int {
return items.index(after: idx)
}
}

Sorting a Custom Object by different property values in Swift

I am trying to sort an array of Custom Structs by different property values easily.
struct Customer: Comparable, Equatable {
var name: String
var isActive: Bool
var outstandingAmount: Int
var customerGroup: String
}
var customerlist: [Customer] // This is downloaded from our backend.
I want to be able to sort the customerlist array in the UI by all the field values when the user selects the various icons.
I have tried a few methods to sort it using a switch statement - however I am told that the correct way to do this is using Sort Descriptors( which appear to be Objective-C based and mean I need to convert my array to an NSArray. ) I keep getting errors when I try this approach with native Swift structs.
What is the best way to allow the user to sort the above array using Swift?
Eg: below seems very verbose!
func sortCustomers(sortField:ColumnOrder, targetArray:[Customer]) -> [Customer] { //Column Order is the enum where I have specified the different possible sort orders
var result = [Customer]()
switch sortField {
case .name:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.name > cust1.name
})
case .isActive:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.isActive > cust1.isActive
})
case .outstandingAmount:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.outstandingAmount > cust1.outstandingAmount
})
case .customerGroup:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.customerGroup > cust1.customerGroup
})
}
return result
}
What I would go with, is using KeyPaths:
func sortCustomers<T: Comparable>(customers: [Customer], with itemPath: KeyPath<Customer, T>) -> [Customer] {
return customers.sorted() {
$0[keyPath: itemPath] < $1[keyPath: itemPath]
}
}
This approach avoids the need for your enum at all, and allows you to just do
let testData = [Customer(name: "aaaa", isActive: false, outstandingAmount: 1, customerGroup: "aaa"),
Customer(name: "bbbb", isActive: true, outstandingAmount: 2, customerGroup: "bbb")];
let testResultsWithName = sortCustomers(customers: testData, with: \Customer.name)
let testResultsWithActive = sortCustomers(customers: testData, with: \Customer.isActive)
// etc
Notice that I switched the > to a <. That is the default expectation and will result in "a" before "b", "1" before "2", etc.
Also, you need to add an extension for Bool to be comparable:
extension Bool: Comparable {
public static func <(lhs: Bool, rhs: Bool) -> Bool {
return lhs == rhs || (lhs == false && rhs == true)
}
}
To round out the approach, you can also pass in a comparison function:
func sortCustomers<T: Comparable>(customers: [Customer], comparing itemPath: KeyPath<Customer, T>, using comparitor: (T, T) -> Bool) -> [Customer] {
return customers.sorted() {
comparitor($0[keyPath: itemPath], $1[keyPath: itemPath])
}
}
let testResults = sortCustomers(customers: testData, comparing: \Customer.name, using: <)
This way you can use the normal comparison operators: (<, <=, >, >=) as well as a closure if you want custom sorting.
I re-packaged the verbose solution to make something nicer. I added a property to ColumnOrder that returns a ordering closure.
struct Customer {
var name: String
var isActive: Bool
var outstandingAmount: Int
var customerGroup: String
}
enum ColumnOrder {
case name
case isActive
case outstandingAmount
case customerGroup
var ordering: (Customer, Customer) -> Bool {
switch self {
case .name: return { $0.name > $1.name }
case .isActive: return { $0.isActive && !$1.isActive }
case .outstandingAmount: return { $0.outstandingAmount > $1.outstandingAmount}
case .customerGroup: return { $0.customerGroup > $1.customerGroup }
}
}
}
Here is how it's used:
let sortedCustomers = customers.sorted(by: ColumnOrder.name.ordering)
Next, I extended Sequence to make calling it from an array look good.
extension Sequence where Element == Customer {
func sorted(by columnOrder: ColumnOrder) -> [Element] {
return sorted(by: columnOrder.ordering)
}
}
Final result:
let sortedCustomers = customers.sorted(by: .name)

Resources