SwiftUI ForEach with Array of SubClasses - swift

I have found a weird issue with SwiftUI's ForEach (and List) where if you use an Array of subclass types where the parent class implements BindableObject, the ForEach loop insists each item is of the base class type not the Subclass you are using, see my example code below. A little experimenting has found if the subclass implements BindableObject then the issue goes away, which in the example I have shown is OK, but often is not really suitable.
Anybody seen this know how you are suppose to deal with this or perhaps this is a bug and I should raise it with Apple?
class Bar: BindableObject {
let didChange = PassthroughSubject<Bar, Never>()
let name: String
init(name aName: String) {
name = aName
}
}
class Foo: Bar {
let value: Int
init(name aName: String, value aValue: Int) {
value = aValue
super.init(name:aName)
}
}
let arrayOfFoos: Array<Foo> = [ Foo(name:"Alpha",value:12), Foo(name:"Beta",value:13)]
struct ContentView : View {
var body: some View {
VStack {
ForEach(arrayOfFoos) { aFoo in
Text("\(aFoo.name) = \(aFoo.value)") // error aFoo is a Bar not a Foo
}
}
}
}

Tried this on Xcode Beta 2
I think this is not a bug but rather a "feature" of Swift type system and SwiftUI API.
If you look at the signature of ForEach (just Cmd + Click on ForEach)
public init(_ data: Data, content: #escaping (Data.Element.IdentifiedValue) -> Content)
you can notice that it accepts Data.Element.IdentifiedValue type
So, from your example
struct ContentView : View {
var body: some View {
VStack {
ForEach(arrayOfFoos) { aFoo in
Text("\(aFoo.name) = \(aFoo.value)") // error aFoo is a Bar not a Foo
}
}
}
}
aFoo local value has type Foo.IdentifiedValue
Lets ask Swift what it thinks about this type:
Foo.IdentifiedValue.self == Bar.IdentifiedValue.self // true
Foo.IdentifiedValue.self == Foo.self // false
Foo.IdentifiedValue.self == Bar.self // true
As you can see, Foo.IdentifiedValue is actually Bar.
To bypass this we can create a wrapper using a new feature of Swift 5.1 - 'Key Path Member Lookup'! :D
I updated your example. Added AnyBindable class and mapped elements of arrayOfFoos to it.
class Bar: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
let name: String
init(name aName: String) {
name = aName
}
}
class Foo: Bar {
let value: Int
init(name aName: String, value aValue: Int) {
value = aValue
super.init(name:aName)
}
}
#dynamicMemberLookup
class AnyBindable<T: BindableObject>: BindableObject {
let didChange: T.PublisherType
let wrapped: T
init(wrapped: T) {
self.wrapped = wrapped
self.didChange = wrapped.didChange
}
subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
return wrapped[keyPath: keyPath]
}
}
let arrayOfFoos = [ Foo(name:"Alpha",value:12), Foo(name:"Beta",value:13)]
.map(AnyBindable.init)
struct ContentView : View {
var body: some View {
VStack {
ForEach(arrayOfFoos) { aFoo in
Text("\(aFoo.name) = \(aFoo.value)") // it compiles now
}
}
}
}

Related

Compile error on Property Wrapper in Swift 5.1

I'm figuring out Property Wrappers in Swift, but I seem to miss something.
This is how I wrote a property wrapper for a dependency injection framework we use:
#propertyWrapper
struct Inject<Value> {
var _value: Value
var wrappedValue: Value {
get {
return _value
}
set {
_value = newValue
}
}
init(_ container: Container = AppContainer.shared) {
do {
_value = try container.resolve(Value.self)
} catch let e {
fatalError(e.localizedDescription)
}
}
}
But when I use it in my class like below, I get a compile error. I've seen a lot of examples that to me do the exact same thing but probably there are some differences.
class X: UIViewController {
#Inject var config: AppConfiguration
....
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
config.johnDoSomething() // Compile error: 'AppConfiguration' is not convertible to 'AppConfiguration?'
}
}
I few days ago I came across a reference that Xcode had compile issues with Generic Property Wrappers, but I can't find it anymore. I'm not sure if that's relevant but maybe somebody on SO can help me out.
Using Xcode 11.3.1
As requested, hereby a reprex (one file in playground):
import UIKit
/// This class is only to mimick the behaviour of our Dependency Injection framework.
class AppContainer {
static let shared = AppContainer()
var index: [Any] = ["StackOverflow"]
func resolve<T>(_ type: T.Type) -> T {
return index.first(where: { $0 as? T != nil }) as! T
}
}
/// Definition of the Property Wrapper.
#propertyWrapper
struct Inject<Value> {
var _value: Value
var wrappedValue: Value {
get {
return _value
}
set {
_value = newValue
}
}
init(_ container: AppContainer = AppContainer.shared) {
_value = container.resolve(Value.self)
}
}
/// A very minimal case where the compile error occurs.
class X {
#Inject var text: String
init() { }
}
Just dealing with your "reprex":
Change
#Inject var text: String
to
#Inject() var text: String

Can a Swift Property Wrapper reference the owner of the property its wrapping?

From within a property wrapper in Swift, can you someone refer back to the instance of the class or struck that owns the property being wrapped? Using self doesn't obviously work, nor does super.
I tried to pass in self to the property wrapper's init() but that doesn't work either because self on Configuration is not yet defined when #propertywrapper is evaluated.
My use case is in a class for managing a large number of settings or configurations. If any property is changed, I just want to notify interested parties that something changed. They don't really need to know which value just, so use something like KVO or a Publisher for each property isn't really necessary.
A property wrapper looks ideal, but I can't figure out how to pass in some sort of reference to the owning instance that the wrapper can call back to.
References:
SE-0258
enum PropertyIdentifier {
case backgroundColor
case textColor
}
#propertyWrapper
struct Recorded<T> {
let identifier:PropertyIdentifier
var _value: T
init(_ identifier:PropertyIdentifier, defaultValue: T) {
self.identifier = identifier
self._value = defaultValue
}
var value: T {
get { _value }
set {
_value = newValue
// How to callback to Configuration.propertyWasSet()?
//
// [self/super/...].propertyWasSet(identifier)
}
}
}
struct Configuration {
#Recorded(.backgroundColor, defaultValue:NSColor.white)
var backgroundColor:NSColor
#Recorded(.textColor, defaultValue:NSColor.black)
var textColor:NSColor
func propertyWasSet(_ identifier:PropertyIdentifier) {
// Do something...
}
}
The answer is no, it's not possible with the current specification.
I wanted to do something similar. The best I could come up with was to use reflection in a function at the end of init(...). At least this way you can annotate your types and only add a single function call in init().
fileprivate protocol BindableObjectPropertySettable {
var didSet: () -> Void { get set }
}
#propertyDelegate
class BindableObjectProperty<T>: BindableObjectPropertySettable {
var value: T {
didSet {
self.didSet()
}
}
var didSet: () -> Void = { }
init(initialValue: T) {
self.value = initialValue
}
}
extension BindableObject {
// Call this at the end of init() after calling super
func bindProperties(_ didSet: #escaping () -> Void) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? BindableObjectPropertySettable {
child.didSet = didSet
}
}
}
}
You cannot do this out of the box currently.
However, the proposal you refer to discusses this as a future direction in the latest version:
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
For now, you would be able to use a projectedValue to assign self to.
You could then use that to trigger some action after setting the wrappedValue.
As an example:
import Foundation
#propertyWrapper
class Wrapper {
let name : String
var value = 0
weak var owner : Owner?
init(_ name: String) {
self.name = name
}
var wrappedValue : Int {
get { value }
set {
value = 0
owner?.wrapperDidSet(name: name)
}
}
var projectedValue : Wrapper {
self
}
}
class Owner {
#Wrapper("a") var a : Int
#Wrapper("b") var b : Int
init() {
$a.owner = self
$b.owner = self
}
func wrapperDidSet(name: String) {
print("WrapperDidSet(\(name))")
}
}
var owner = Owner()
owner.a = 4 // Prints: WrapperDidSet(a)
My experiments based on : https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type
protocol Observer: AnyObject {
func observableValueDidChange<T>(newValue: T)
}
#propertyWrapper
public struct Observable<T: Equatable> {
public var stored: T
weak var observer: Observer?
init(wrappedValue: T, observer: Observer?) {
self.stored = wrappedValue
}
public var wrappedValue: T {
get { return stored }
set {
if newValue != stored {
observer?.observableValueDidChange(newValue: newValue)
}
stored = newValue
}
}
}
class testClass: Observer {
#Observable(observer: nil) var some: Int = 2
func observableValueDidChange<T>(newValue: T) {
print("lol")
}
init(){
_some.observer = self
}
}
let a = testClass()
a.some = 4
a.some = 6
The answer is yes! See this answer
Example code for calling ObservableObject publisher with a UserDefaults wrapper:
import Combine
import Foundation
class LocalSettings: ObservableObject {
static var shared = LocalSettings()
#Setting(key: "TabSelection")
var tabSelection: Int = 0
}
#propertyWrapper
struct Setting<T> {
private let key: String
private let defaultValue: T
init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
) -> T {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
}
}
}

swift 3 downcast to dynamic class

I am trying to create a couple of objects which are dependent one to each other and they mush have a method to downcast directly the concrete class of the other object. Something like this:
protocol aProt
{
var bVar:bProt! { get set }
}
protocol bProt
{
var aVar:aProt! { get set }
}
class a: aProt
{
var bVar: bProt!
func bConcrete() -> b {
return bVar as! b
}
}
class b: bProt
{
var aVar: aProt!
func aConcrete() -> a {
return aVar as! a
}
Now, the problem is that I want this behavior (func aConcrete(),func bConcrete()) to be inherited by the subclasses of a and b. Then I thought the perfect way of doing this was using generics, but... There's no way of doing this.
class a: aProt
{
var bVar: bProt!
func bConcrete() -> T {
return bVar as! T
}
}
class b: bProt
{
var aVar: aProt!
func aConcrete<T>() -> T {
return aVar as! T
}
You can do it but when you have to use it you must downcast the variable anyway, so there is no way of doing it in a clean manner:
let aObject = a()
let bSubclassObject = a.bConcrete() // The compiler complains it cannot infer the class of T
let bSubclassObject = a.bConcrete() as! bSubclass // this works, but this is exactly which I wanted to avoid... :(
Define the generic function and add where to T:
protocol aProt {
var bVar: bProt! { get set }
}
protocol bProt {
var aVar:aProt! { get set }
}
class a: aProt {
var bVar: bProt!
func bConcrete<T: b>(_ type: T.Type) -> T? {
return bVar as? T
}
}
class b: bProt {
var aVar: aProt!
func aConcrete<T: a>(_ type: T.Type) -> T? {
return aVar as? T
}
}
class a1: a { }
class b1: b {
var fullName: String = "new object"
}
let aObj = a()
aObj.bVar = b1()
let bObj = aObj.bConcrete(b1.self)
bObj?.fullName
According to your requirement, calls bConcrete(b1.self) might still not good enough, but at least you need to know what type of data you are expecting to return.

Type casting in a generic swift function

Supposing I have a UICollectionViewCell and a UITableViewCell with identical properties. Rather than have two functions which populate those cells, could I have a generic that takes something , determine what that was and then cast it to the correct thing to perform actions on it before returning?
my thinking is:
func setUpCell<T>(event: Event, cell:T) -> T {
// figure out what T is and cast it
cell.event.bar = event.bar
return cell
}
is this a good way of avoiding large amounts of code duplication?
Given your model type
struct Event {
let title: String
let desc: String
}
define this protocol
protocol EventCell: class {
var id: String? { get set }
var desc: String? { get set }
}
Now conform your UITabelViewCell and UICollectionViewCell to it
class TableCell: UITableViewController, EventCell {
var id: String?
var desc: String?
}
class CollectionCell: UICollectionViewCell, EventCell {
var id: String?
var desc: String?
}
And finally define this extension
extension EventCell {
func populate(event:Event) {
self.id = event.id
self.desc = event.desc
}
}
That's it. Now both your cells (UITabelViewCell and UICollectionViewCell) have the populate method!
Does this match what you were thinking?
import UIKit
struct Event {
var bar:Int = 0
}
// Protocol to group common additions
protocol ViewCellAdditions {
init()
var separatorInset:Int { get set }
var event:Event { get set}
}
// Generic function to work on any class that adopts ViewCellAdditions
func setUpCell<T: ViewCellAdditions>(event: Event, cell:T, foo:Int) -> T {
var newCell = T()
newCell.separatorInset = foo
newCell.event.bar = event.bar
return newCell
}
// Class that adopts ViewCellAdditions
class NewCellClass: ViewCellAdditions {
required init() {}
var separatorInset:Int = 10
var event:Event = Event()
}
// How to use it
let aCell = NewCellClass()
let aEvent = Event()
let newCell = setUpCell(aEvent, cell: aCell, foo: 5)

override func in class swift

I am quite new to swift and I have a question regarding the definition of functions in a class.
I want to generate several items and give each of them a special function so I can run them by itemxy.useitem()
class itemĀ {
var name = "test"
func useitem(){
print("test")
}
}
let staff = item()
staff.name = "Staff"
staff.useitem() // prints: test
*override staff.useitem() = {print("this is a staff")}*
staff.useitem() // prints: this is a staff
how can I align a new function to my item staff?
These are not entirely swift related and are more general programming, you wont get your answers to such problems here. You should read up on basic programming principles before you tackle things further.
Having said that your problem is easily solved with Inheritance or Protocols.
Inheritance
class Item {
var name: String
init(name: String) {
self.name = name
}
func use() {
print("using a \(name)")
}
}
class Hat: Item {
override func use() {
print("I put on a \(name)")
}
}
class Pen: Item {
init() {
super.init(name: "Pen")
}
}
let pen = Pen()
pen.use()
let hat = Hat(name: "Beanie")
hat.use()
Protocol
protocol Item {
var name: String { get set }
func use()
}
extension Item {
func use() {
print("using a \(name)")
}
}
struct Pen: Item {
var name: String
init() {
self.name = "Pen"
}
}
struct Hat: Item {
var name: String
func use() {
print("I put on a \(name)")
}
}
let pen = Pen()
pen.use()
let hat = Hat(name: "Beanie")
hat.use()