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...