“Generic parameter could not be inferred” in SwiftUI UIViewRepresentable

后端 未结 2 1019
别那么骄傲
别那么骄傲 2021-01-14 07:25

I\'ve got the following code, which makes it possible to use the UIKit\'s UIScrollView in my SwiftUI code. It can be pasted in a new SwiftUI project.

         


        
相关标签:
2条回答
  • 2021-01-14 08:07

    Here is possible approach that allows usage of provided ContentView as-is.

    Just change the direction of... instead of making entire type generic, which is actually not needed in this case, just make a generic only initialisation, like below.

    Also it actually makes clear that Action is Content-independent, that is really correct.

    Tested & works with Xcode 11.2 / iOS 13.2 (w/o no changes in 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
            }
        }
    
    }
    
    0 讨论(0)
  • 2021-01-14 08:09

    I disagree with your assertion that the enum should be nested inside the class for the following reasons:

    • The enum is intended to be used both inside and outside of the class, with a generic type being required in order to use it.
    • The enum does not make use of, and therefore has no dependency on, the generic Content type.
    • With a good enough name, the intended use of the enum would be obvious.

    If you really want to nest the enum definition, I would suggest the following:

    • Drop the generic type requirement on the class definition,
    • Convert your content member to be of AnyView type,
    • Make your init functions generic and store the return values of the given view builders into type-erased views, like so:
    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())
    }
    
    

    Of course, with this approach, you will:

    • Lose the type information of the underlying content view.
    • Possibly incur a greater runtime cost with type-erased views.

    So it depends what you value more in this case... Ahhh, tradeoffs...

    0 讨论(0)
提交回复
热议问题