SwiftUI UIViewRepresentable 中的“无法推断通用参数”

Bar*_*uik 6 generics swift swiftui

我有以下代码,这使得可以在我的 SwiftUI 代码中使用 UIKit UIScrollView。可以将其粘贴到新的 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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码将Generic parameter 'Content' could not be inferredContentView. 我尝试将该行更改为:

@State private var action = LegacyScrollView<AnyView>.Action.idle
Run Code Online (Sandbox Code Playgroud)

但这会产生另一个错误。当我将其放在enum Action外部时它会起作用struct LegacyScrollView。但在我看来,这是一个相当不优雅的枚举范围。我该如何解决该错误消息?

Asp*_*eri 4

这是允许按ContentView原样使用提供的可能方法。

只需更改...的方向,而不是使整个类型通用(在本例中实际上不需要),只需进行通用初始化,如下所示。

而且它实际上清楚地表明了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
        }
    }

}
Run Code Online (Sandbox Code Playgroud)