Swift: typecast output array in Collection where Iterator.Element == Foo - arrays

I have the following function, it iterates over an array of Foo, and creates a new array using the items in the original array, but with different colors:
extension Collection where Iterator.Element == Foo {
public func color(colors: [NSColor]) -> [Foo] {
var result: [Foo] = []
for item in self {
for color in colors {
let newItem = Foo(name: item.name, color: color)
result.append(newItem)
}
}
return result
}
}
This works, but if I use it on a subclass Bar of Foo, it still returns [Foo], not [Bar].
I could do something like this:
let colors: [NSColor] = // set colors
let colorArray = array.color(colors).map{ Bar($0.name, $.color)
But then I need to remember to do that every time I call it for a subclass of Foo.
So how can I adjust the function to make it work on subclasses of Foo as well?
EDIT
Based on the comment below, I tried a generic function:
public func color<T>(colors: [NSColor]) -> [T] {
var result: [T] = []
for item in self {
for color in colors {
let newItem = T(name: item.name, color: color)
result.append(newItem)
}
}
return result
}
That gives me the error:
Non-nominal type 'T' does not support explicit initialization
So I searched for that, and found I need to use init:
let newItem = T.init(name: item.name, color: color)
Now I get this error:
Type 'T' has no member 'init'
Foo and Bar have an init, but that doesn't help here. Am I on the right track?
EDIT 2:
Martin's answer below made me realize an error in my code: where Iterator.Element == Foo should have been where Iterator.Element: Foo

You can replace the “same-type constraint” by a “subclass constraint” and use the collections Element type instead of Foo in the implementation:
extension Collection where Element: Foo {
func color(colors: [NSColor]) -> [Element] {
var result: [Element] = []
for item in self {
for color in colors {
let newItem = Element(name: item.name, color: color)
result.append(newItem)
}
}
return result
}
}
Note that this requires Foo (and its subclasses) to have a “required init” method:
required init(name: String, color: NSColor)
Another option is to define a protocol
protocol P {
var name: String { get }
init(name: String, color: NSColor)
}
to which Foo (and its subclasses) conform, and use the constraint
extension Collection where Element: P
for the extension method.
In any case, a more concise implementation would be
extension Collection where Element: Foo {
func color(colors: [NSColor]) -> [Element] {
return self.flatMap { item in
colors.map { Element(name: item.name, color: $0) }
}
}
}
Re your edit: As a free generic method (instead of an extension method) it would be something like
func color<T: P>(items: [T], with colors: [NSColor]) -> [T] {
return items.flatMap { item in
colors.map { T(name: item.name, color: $0) }
}
}
using the protocol defined above. The compiler needs to know that an instance of T can be created from an item name and a color.

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

Simple way to replace an item in an array if it exists, append it if it doesn't

Swift 4.2
I have multiple functions that replace an object or struct in an array if it exists, and if it does not exist, it adds it.
func updateFruit(_ fruit: Fruit)
{
if let idx = fruitArray.firstIndex(where: { $0.id == fruit.id })
{
fruitArray[idx] = fruit
}
else
{
fruitArray.append(fruit)
}
}
Obviously I could make this into extension on Array:
extension Array
{
mutating func replaceOrAppend(_ item: Element, whereFirstIndex predicate: (Element) -> Bool)
{
if let idx = self.firstIndex(where: predicate)
{
self[idx] = item
}
else
{
append(item)
}
}
}
However, is there a simpler, easier way of expressing this? Preferably using a closure or build-in function.
NOTE: current implementation does not allow using a set.
Given your use case, in which you're always checking $0.<prop> == newthing.<prop>, you can lift this a little more by adding:
mutating func replaceOrAppend<Value>(_ item: Element,
firstMatchingKeyPath keyPath: KeyPath<Element, Value>)
where Value: Equatable
{
let itemValue = item[keyPath: keyPath]
replaceOrAppend(item, whereFirstIndex: { $0[keyPath: keyPath] == itemValue })
}
You can then use it like:
struct Student {
let id: Int
let name: String
}
let alice0 = Student(id: 0, name: "alice")
let alice1 = Student(id: 1, name: "alice")
let bob = Student(id: 0, name: "bob")
var array = [alice0]
array.replaceOrAppend(alice1, firstMatchingKeyPath: \.name) // [alice1]
array.replaceOrAppend(bob, firstMatchingKeyPath: \.name) // [alice1, bob]
And of course if you do this a lot, you can keep lifting and lifting.
protocol Identifiable {
var id: Int { get }
}
extension Student: Identifiable {}
extension Array where Element: Identifiable {
mutating func replaceOrAppendFirstMatchingID(_ item: Element)
{
replaceOrAppend(item, firstMatchingKeyPath: \.id)
}
}
array.replaceOrAppendFirstMatchingID(alice0) // [alice1, alice0]
I can suggest to create protocol Replacable with replaceValue that will represent identifier which we can use to enumerate thru objects.
protocol Replacable {
var replaceValue: Int { get }
}
now we can create extension to Array, but now we can drop predicate from example code like this
extension Array where Element: Replacable {
mutating func replaceOrAppend(_ item: Element) {
if let idx = self.firstIndex(where: { $0.replaceValue == item.replaceValue }) {
self[idx] = item
}
else {
append(item)
}
}
}
Since Set is not ordered collection, we can simply remove object if set contains it and insert new value
extension Set where Element: Replacable {
mutating func replaceOrAppend(_ item: Element) {
if let existItem = self.first(where: { $0.replaceValue == item.replaceValue }) {
self.remove(existItem)
}
self.insert(item)
}
}
Assuming your Types are Equatable, this is a generic extension:
extension RangeReplaceableCollection where Element: Equatable {
mutating func addOrReplace(_ element: Element) {
if let index = self.firstIndex(of: element) {
self.replaceSubrange(index...index, with: [element])
}
else {
self.append(element)
}
}
}
Though, keep in mind my (and your) function will only replace one of matching items.
Full Working playground test:

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

Generic Array of weak references to class bound protocol in Swift 4.1

I'm trying to create a generic WeakReference type that I can put into an array (and ultimately create a generic weak array type).
So far so good, but the following code:
class WeakReference<ElementType: AnyObject> {
weak var element: ElementType?
init(_ element: ElementType) {
self.element = element
}
}
protocol Element: AnyObject {}
class WeakElementHolder {
var weakElements: [WeakReference<Element>] = []
}
Produces this compiler error:
WeakReference.swift:12:21: error: 'WeakReference' requires that 'Element' be a class type
var weakElements: [WeakReference<Element>] = []
^
WeakReference.swift:1:7: note: requirement specified as 'ElementType' : 'AnyObject' [with ElementType = Element]
class WeakReference<ElementType: AnyObject> {
^
This is strange because the Protocol definitely requires a class (AnyObject).
Strangely everything works fine if I leave out the generics:
protocol Element: AnyObject {}
class WeakElementReference {
weak var element: Element?
init(_ element: Element) {
self.element = element
}
}
class WeakElementHolder {
var weakElements: [WeakElementReference] = []
}
Searching around I found this question but it wasn't really answered.
Is there a workaround to still somehow implement a generic array of weak references that works with class bound protocols?
UPDATE:
My concrete use case is to store a list of observers that get notified when something happens:
protocol Observer: AnyObject {
func notify()
}
class WeakReference<ElementType: AnyObject> {
weak var element: ElementType?
init(_ element: ElementType) {
self.element = element
}
}
class WeakArray<ElementType: AnyObject> {
var content: [WeakReference<ElementType>] = []
}
class Observable {
var observers: WeakArray<Observer>()
func notifyAllObservers() {
// call notify() on all observers here
}
}
These observers can be many different concrete types.
More Clarification:
There is not only one Observer protocol, there are many that have nothing in common, this is why I want this to be generic in the first place.
As discussed in Protocol doesn't conform to itself?, the (non-#objc) protocol defined by
protocol Element: AnyObject {}
inherits from AnyObject, but does not conform to AnyObject.
A possible solution using a type-eraser:
protocol Observer: AnyObject {
func notify()
}
struct AnyObserver {
weak var base: Observer?
init(_ base: Observer ) {
self.base = base
}
}
class Observable {
var observers: [AnyObserver] = []
func add(_ observer: Observer) {
observers.append(AnyObserver(observer))
}
func notifyAllObservers() {
for observer in observers {
observer.base?.notify()
}
}
}
Example usage:
class A: Observer {
func notify() {
print("Hello A")
}
}
class B: Observer {
func notify() {
print("Hello B")
}
}
let a = A()
let b = B()
let o = Observable()
o.add(a)
o.add(b)
o.notifyAllObservers()
// Hello A
// Hello B

How to find if array contains object

I have created a custom class called MenuItem:
import Foundation
class MenuItem {
var title: String
var tag: Int
var image: UIImage
init (title: String, tag: Int, image: UIImage) {
self.title = title
self.tag = tag
self.image = image
}
}
I am adding these objects to an array.
How can I check if an array contains a specific menu item?
This is a simplified version of what I have tried.
let menuOptionInventory = MenuItem(title: "Inventory", tag: 100, image: UIImage(imageLiteral: "871-handtruck"))
var menuOptions = [MenuItem]()
menuOptions.append(menuOptionInventory)
if (menuOptions.contains(menuOptionInventory)) {
//does contain object
}
When I do this I get this error message:
Cannot convert value of type 'MenuItem' to expected argument type '#noescape (MenuItem) throws -> Bool'
Try this out:
if menuOptions.contains( { $0 === menuOptionInventory } ) {
//does contain object
}
Edit: As JAL pointed out this does compare pointers.
So instead you could override the == operator specifically for a comparison between two MenuItem objects like this:
func == (lhs: MenuItem, rhs: MenuItem) -> Bool {
return lhs.title == rhs.title && lhs.tag == rhs.tag && lhs.image == rhs.image
}
And then you could use the contain method closure to perform a comparison between the objects.
if menuOptions.contains( { $0 == menuOptionInventory } ) {
//does contain object
}
A few issues:
contains takes in a closure. You would need to do your comparison like this:
if menuOptions.contains( { $0 == menuOptionInventory } ) {
// do stuff
}
But now you'll get the issue that == cannot be applied to two MenuItem objects. Conform your object to Hashable and Equatable and define how two MenuItem objects are equal, or use a base class that conforms to Hashable and Equatable for you, such as NSObject.

Resources