"Generic parameter could not be inferred" in SwiftUI UIViewRepresentable



我有以下代码,这使得在我的 SwiftUI 代码中使用 UIKitUIScrollView成为可能。它可以粘贴到新的 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()
}
}
}

上面的代码将在ContentView的第一行给出Generic parameter 'Content' could not be inferred。我尝试将行更改为:

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

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

以下是允许按原样使用提供的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
}
}
}

我不同意你的说法,即enum应该嵌套在类中,原因如下:

enum旨在在
  • 类内部和外部使用,需要泛型类型才能使用它。
  • enum不使用泛型Content类型,因此不依赖于泛型类型。
  • 有了足够好的名字,enum的预期用途将是显而易见的。

如果你真的想嵌套enum定义,我会建议如下:

  • 删除类定义上的泛型类型要求,
  • 将您的content成员转换为AnyView类型,
  • 使init函数通用化,并将给定视图生成器的返回值存储到类型擦除的视图中,如下所示:
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())
}

当然,使用这种方法,您将:

  • 丢失基础内容视图的类型信息。
  • 使用类型擦除视图可能会产生更大的运行时成本。

所以这取决于你在这种情况下更看重什么......啊,权衡...