«Generi c параметр не может быть выведен» в SwiftUI UIViewRepresentable - PullRequest
2 голосов
/ 11 февраля 2020

У меня есть следующий код, который позволяет использовать UIKit UIScrollView в моем коде SwiftUI. Его можно вставить в новый проект SwiftUI.

struct LegacyScrollView<Content: View>: UIViewRepresentable {
    enum Action {
        case idle
        case offset(x: CGFloat, y: CGFloat, animated: Bool)
    }

    @Binding var action: Action
    let content: Content

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UIScrollView {
        let hosting = UIHostingController(rootView: self.content)
        hosting.view.translatesAutoresizingMaskIntoConstraints = false

        let uiScrollView = UIScrollView()
        uiScrollView.addSubview(hosting.view)

        let constraints = [
            hosting.view.leadingAnchor.constraint(equalTo: uiScrollView.leadingAnchor),
            hosting.view.trailingAnchor.constraint(equalTo: uiScrollView.trailingAnchor),
            hosting.view.topAnchor.constraint(equalTo: uiScrollView.contentLayoutGuide.topAnchor),
            hosting.view.bottomAnchor.constraint(equalTo: uiScrollView.contentLayoutGuide.bottomAnchor),
            hosting.view.widthAnchor.constraint(equalTo: uiScrollView.widthAnchor)
        ]
        uiScrollView.addConstraints(constraints)

        return uiScrollView
    }

    func updateUIView(_ uiView: UIScrollView, context: Context) {
        switch self.action {
        case .offset(let x, let y, let animated):
            uiView.setContentOffset(CGPoint(x: x, y: y), animated: animated)
            DispatchQueue.main.async {
                self.action = .idle
            }
        default:
            break
        }
    }

    class Coordinator: NSObject {
        let legacyScrollView: LegacyScrollView

        init(_ legacyScrollView: LegacyScrollView) {
            self.legacyScrollView = legacyScrollView
        }
    }

    init(@ViewBuilder content: () -> Content) {
        self._action = Binding.constant(Action.idle)
        self.content = content()
    }

    init(action: Binding<Action>, @ViewBuilder content: () -> Content) {
        self._action = action
        self.content = content()
    }
}

struct ContentView: View {
    @State private var action = LegacyScrollView.Action.idle

    var body: some View {
        VStack(spacing: 0) {
            LegacyScrollView(action: self.$action) {
                ForEach(0 ..< 40) { _ in
                    Text("Hello, World!")
                }
            }
            .padding(20)
            .background(Color.gray)
            Spacer()
            Button("Set offset") {
                self.action = LegacyScrollView.Action.offset(x: 0, y: 200, animated: true)
            }.padding()
        }
    }
}

Приведенный выше код даст Generic parameter 'Content' could not be inferred в первой строке ContentView. Я попытался изменить строку на:

@State private var action = LegacyScrollView<AnyView>.Action.idle

, но это даст другую ошибку. Это работает, когда я помещаю enum Action вне struct LegacyScrollView. Но, на мой взгляд, это довольно не элегантное определение этого перечисления. Как я могу решить сообщение об ошибке?

Ответы [ 2 ]

2 голосов
/ 11 февраля 2020

Здесь возможен подход, который позволяет использовать предоставленный ContentView как есть.

Просто измените направление ... вместо создания целого типа generi c, который на самом деле не нужен в этом случае, просто сделайте инициализацию c generic, как показано ниже.

Также на самом деле ясно, что Action не зависит от содержимого, что действительно правильно.

Протестировано и работает с Xcode 11.2 / iOS 13.2 (без изменений в ContentView)

struct LegacyScrollView: UIViewRepresentable {
    enum Action {
        case idle
        case offset(x: CGFloat, y: CGFloat, animated: Bool)
    }

    @Binding var action: Action
    private let uiScrollView: UIScrollView

    init<Content: View>(content: Content) {
        let hosting = UIHostingController(rootView: content)
        hosting.view.translatesAutoresizingMaskIntoConstraints = false

        uiScrollView = UIScrollView()
        uiScrollView.addSubview(hosting.view)

        let constraints = [
            hosting.view.leadingAnchor.constraint(equalTo: uiScrollView.leadingAnchor),
            hosting.view.trailingAnchor.constraint(equalTo: uiScrollView.trailingAnchor),
            hosting.view.topAnchor.constraint(equalTo: uiScrollView.contentLayoutGuide.topAnchor),
            hosting.view.bottomAnchor.constraint(equalTo: uiScrollView.contentLayoutGuide.bottomAnchor),
            hosting.view.widthAnchor.constraint(equalTo: uiScrollView.widthAnchor)
        ]
        uiScrollView.addConstraints(constraints)

        self._action = Binding.constant(Action.idle)
    }

    init<Content: View>(@ViewBuilder content: () -> Content) {
        self.init(content: content())
    }

    init<Content: View>(action: Binding<Action>, @ViewBuilder content: () -> Content) {
        self.init(content: content())
        self._action = action
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UIScrollView {
        return uiScrollView
    }

    func updateUIView(_ uiView: UIScrollView, context: Context) {
        switch self.action {
        case .offset(let x, let y, let animated):
            uiView.setContentOffset(CGPoint(x: x, y: y), animated: animated)
            DispatchQueue.main.async {
                self.action = .idle
            }
        default:
            break
        }
    }

    class Coordinator: NSObject {
        let legacyScrollView: LegacyScrollView

        init(_ legacyScrollView: LegacyScrollView) {
            self.legacyScrollView = legacyScrollView
        }
    }

}
1 голос
/ 11 февраля 2020

Я не согласен с вашим утверждением, что enum должен быть вложен в класс по следующим причинам:

  • enum предназначен для использования как внутри, так и вне класса, с типом generi c, необходимым для его использования.
  • enum не использует и поэтому не зависит от типа generi c Content.
  • При достаточно хорошем названии предполагаемое использование enum было бы очевидным.

Если вы действительно хотите, чтобы вложил enum определение, я бы предложил следующее:

  • Отменить требование типа * generic c для определения класса,
  • Преобразовать ваш член content в тип AnyView,
  • Сделайте ваши init функции обобщенными c и сохраните возвращаемые значения заданных конструкторов представлений в стертых типах представлениях, например:
init<Content: View>(@ViewBuilder content: () -> Content) {
    self._action = Binding.constant(Action.idle)
    self.content = AnyView(content())
}

init<Content: View>(action: Binding<Action>, @ViewBuilder content: () -> Content) {
    self._action = action
    self.content = AnyView(content())
}

Конечно, При таком подходе вы будете:

  • Потерять информацию о типе основного кон просмотр палатки.
  • Возможно, при просмотрах со стертыми типами могут быть увеличены затраты времени выполнения.

Так что это зависит от того, что вы цените больше в этом случае ... Ааа, компромиссы ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...