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.
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
}
}
}
I disagree with your assertion that the enum should be nested inside the class for the following reasons:
enum is intended to be used both inside and outside of the class, with a generic type being required in order to use it.enum does not make use of, and therefore has no dependency on, the generic Content type.enum would be obvious.If you really want to nest the enum definition, I would suggest the following:
content member to be of AnyView type,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:
So it depends what you value more in this case... Ahhh, tradeoffs...