SwiftUI ForEach с массивом подклассов - PullRequest
1 голос
/ 25 июня 2019

Я обнаружил странную проблему с SwiftUI ForEach List ), когда вы используете массив типов подкласса, где родительский класс реализует BindableObject , цикл ForEach настаивает на том, что каждый элемент имеет тип базового класса, а не подкласс, который вы используете, см. Мой пример кода ниже. Небольшой эксперимент обнаружил, что если подкласс реализует BindableObject , тогда проблема исчезнет, ​​что в приведенном мною примере нормально, но часто не совсем подходит.

Кто-нибудь видел, что это знает, как вы думаете, как справиться с этим, или, возможно, это ошибка, и я должен поднять ее с 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
      }
    }
  }
}

1 Ответ

1 голос
/ 25 июня 2019

Попробовал это на Xcode Beta 2

Я думаю, что это не ошибка, а скорее "особенность" системы типов Swift и SwiftUI API.

Если вы посмотрите на подпись ForEach (просто Cmd + Нажмите на ForEach)

public init(_ data: Data, content: @escaping (Data.Element.IdentifiedValue) -> Content)

вы можете заметить, что он принимает Data.Element.IdentifiedValue тип

Итак, из вашего примера

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 локальное значение имеет тип Foo.IdentifiedValue

Давайте спросим Свифта, что он думает об этом типе:

Foo.IdentifiedValue.self == Bar.IdentifiedValue.self // true
Foo.IdentifiedValue.self == Foo.self // false
Foo.IdentifiedValue.self == Bar.self // true

Как видите, Foo.IdentifiedValue на самом деле Bar.

Чтобы обойти это, мы можем создать оболочку, используя новую функцию Swift 5.1 - 'Key Path Member Lookup'! : D

Я обновил ваш пример. Добавлен класс AnyBindable и сопоставленные элементы arrayOfFoos.

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
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...