Skip to content

Instantly share code, notes, and snippets.

@mbernson
Last active April 26, 2022 13:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbernson/9090953d3f5ca4f129eb72ea58436fdd to your computer and use it in GitHub Desktop.
Save mbernson/9090953d3f5ca4f129eb72ea58436fdd to your computer and use it in GitHub Desktop.
UIScrollView wrapped for SwiftUI, with support for pull to refresh using the refreshable modifier
//
// ScrollViewWrapper.swift
//
// Created by Mathijs Bernson on 10/03/2022.
// Copyright © 2022 Q42. All rights reserved.
//
import SwiftUI
import UIKit
struct ScrollViewWrapper<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
GeometryReader { proxy in
ScrollViewControllerRepresentable(refreshControl: UIRefreshControl()) {
content
.frame(width: proxy.size.width)
}
.ignoresSafeArea()
}
}
}
private struct ScrollViewControllerRepresentable<Content: View>: UIViewControllerRepresentable {
let refreshControl: UIRefreshControl
@ViewBuilder let content: Content
@Environment(\.refresh) private var action
@State var isRefreshing: Bool = false
func makeUIViewController(context: Context) -> ScrollViewController<Content> {
let viewController = ScrollViewController(
refreshControl: refreshControl,
view: content
)
viewController.onRefresh = {
refresh()
}
return viewController
}
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
if isRefreshing {
viewController.refreshControl.beginRefreshing()
} else {
viewController.refreshControl.endRefreshing()
}
viewController.hostingController.rootView = content
}
func refresh() {
Task {
isRefreshing = true
await action?()
isRefreshing = false
}
}
}
private class ScrollViewController<Content: View>: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView()
let refreshControl: UIRefreshControl
let hostingController: UIHostingController<Content>
var onRefresh: (() -> Void)?
init(refreshControl: UIRefreshControl, view: Content) {
self.refreshControl = refreshControl
self.hostingController = UIHostingController(rootView: view)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = scrollView
}
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged)
scrollView.refreshControl = refreshControl
scrollView.delegate = self
hostingController.willMove(toParent: self)
scrollView.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])
hostingController.didMove(toParent: self)
hostingController.view.backgroundColor = .clear
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
hostingController.view.invalidateIntrinsicContentSize()
}
@objc func didPullToRefresh(_ sender: UIRefreshControl) {
onRefresh?()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment