Skip to content

Instantly share code, notes, and snippets.

@Arutyun2312
Last active October 25, 2021 23:24
Show Gist options
  • Save Arutyun2312/0fc17976193d94aead8547f323ce1255 to your computer and use it in GitHub Desktop.
Save Arutyun2312/0fc17976193d94aead8547f323ce1255 to your computer and use it in GitHub Desktop.
Simple Scrollview created based on UIKit. Can control content offset and scroll direction. Note: to control content offset, set 'scrollOwner' to code.
//
// CustomScrollView.swift
// BearFitness
//
// Created by Arutyun Enfendzhyan on 05.10.21.
//
import SwiftUI
struct CustomScrollView: View {
@Binding var offset: CGFloat
@Binding var scrollOwner: ScrollOwner
var axis: Axis = .vertical
let content: AnyView
var body: some View {
GeometryReader { proxy in
Internal(offset: $offset, scrollOwner: $scrollOwner, axis: axis, content: .init(
Group {
switch axis {
case .horizontal:
content
.frame(height: proxy.size.height)
case .vertical:
content
.frame(width: proxy.size.width)
}
}
))
}
}
private struct Internal: UIViewControllerRepresentable {
@Binding var offset: CGFloat
@Binding var scrollOwner: ScrollOwner
let axis: Axis
let content: AnyView
func makeUIViewController(context: Context) -> UIViewControllerType {
let controller = UIViewControllerType()
controller.hostingController.rootView = self.content
controller.onOffsetChanged = { offset = axis == .vertical ? $0.y : $0.x }
return controller
}
func updateUIViewController(_ controller: UIViewControllerType, context: Context) {
controller.scrollView.isScrollEnabled = self.scrollOwner == .user
if self.scrollOwner == .code {
let offset: CGPoint = {
switch axis {
case .horizontal:
return .init(x: self.offset.atMost(controller.scrollView.contentSize.width).atLeast(0), y: 0)
case .vertical:
return .init(x: 0, y: self.offset.atMost(controller.scrollView.contentSize.height).atLeast(0))
}
}()
controller.scrollView.setContentOffset(offset, animated: true)
} else {
print("Content offset is controlled by user")
}
controller.hostingController.rootView = self.content
controller.scrollOwner = self.scrollOwner
}
class UIViewControllerType: UIViewController, UIScrollViewDelegate {
lazy var scrollView = UIScrollView()
var hostingController = UIHostingController(rootView: AnyView(EmptyView()))
var onOffsetChanged: (CGPoint) -> () = { _ in }
var scrollOwner: ScrollOwner = .user
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.pinEdges(of: self.scrollView, to: self.view)
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.pinEdges(of: self.hostingController.view, to: self.scrollView)
self.hostingController.didMove(toParent: self)
self.view.setNeedsUpdateConstraints()
self.view.updateConstraintsIfNeeded()
self.view.layoutIfNeeded()
self.scrollView.delegate = self
}
func pinEdges(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
])
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.scrollOwner == .user {
self.onOffsetChanged(scrollView.contentOffset)
}
}
}
}
}
extension CustomScrollView {
init<Content: View>(offset: Binding<CGFloat>, scrollOwner: Binding<ScrollOwner>, axis: Axis = .vertical, @ViewBuilder content: () -> Content) {
self.init(offset: offset, scrollOwner: scrollOwner, axis: axis, content: .init(content()))
}
}
enum ScrollOwner {
case user, code
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment