Last active
April 26, 2022 13:07
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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