Skip to content

Instantly share code, notes, and snippets.

@eito
Created April 29, 2020 23:17
Show Gist options
  • Save eito/144ce4fd80bda6ed03551a5763a18e2f to your computer and use it in GitHub Desktop.
Save eito/144ce4fd80bda6ed03551a5763a18e2f to your computer and use it in GitHub Desktop.
import SwiftUI
struct PullToRefreshSample: View {
let items = [
"one", "two", "three", "four", "five"
]
@State private var isRefreshing = true
var body: some View {
List(items, id: \.self) { item in
Text(item)
}
.navigationBarTitle("My List")
.pullToRefresh(isRefreshing: $isRefreshing) {
print("refreshing...")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("done...")
self.isRefreshing = false
}
}
}
}
struct RefreshControl: UIViewRepresentable {
@Binding var isRefreshing: Bool
let refreshHandler: () -> Void
init(isRefreshing: Binding<Bool>, refreshHandler: @escaping () -> Void) {
_isRefreshing = isRefreshing
self.refreshHandler = refreshHandler
}
class Coordinator {
let isRefreshing: Binding<Bool>
let refreshHandler: () -> Void
init(isRefreshing: Binding<Bool>, refreshHandler: @escaping () -> Void) {
self.isRefreshing = isRefreshing
self.refreshHandler = refreshHandler
}
@objc
func refresh() {
isRefreshing.wrappedValue = true
refreshHandler()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(isRefreshing: $isRefreshing, refreshHandler: refreshHandler)
}
func makeUIView(context: UIViewRepresentableContext<RefreshControl>) -> UIView {
let view = UIView(frame: .zero)
view.isHidden = true
view.isUserInteractionEnabled = false
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<RefreshControl>) {
// this gets called a lot early, without the dispatch it doesn't work
// TODO - find out why
//
DispatchQueue.main.asyncAfter(deadline: .now()) {
guard let tableView = self.findTableView(from: uiView) else {
return
}
if let refreshControl = tableView.refreshControl {
if self.isRefreshing {
refreshControl.beginRefreshing()
} else {
refreshControl.endRefreshing()
}
return
}
let refreshControl = UIRefreshControl()
refreshControl.addTarget(context.coordinator, action: #selector(Coordinator.refresh), for: .valueChanged)
tableView.refreshControl = refreshControl
}
}
private func findTableView(from view: UIView) -> UITableView? {
var tableView: UITableView?
if let tv = view.findChild(ofType: UITableView.self) {
tableView = tv
} else if let tv = view.findChildInAncestors(ofType: UITableView.self) {
tableView = tv
} else if let tv = view.findChildInSiblings(ofType: UITableView.self) {
tableView = tv
}
return tableView
}
}
extension UIView {
func findChild<T>(ofType type: T.Type) -> T? {
for subview in subviews {
if let t = subview as? T {
return t
}
}
for subview in subviews {
if let foundInChild = subview.findChild(ofType: type) {
return foundInChild
}
}
return nil
}
func findChild<T>(ofType type: T.Type, in view: UIView) -> T? {
return view.findChild(ofType: type)
}
func findChildInAncestors<T>(ofType type: T.Type) -> T? {
var sv = superview
while let s = sv {
if let t = s.findChildInSiblings(ofType: type) {
return t
}
sv = s.superview
}
return nil
}
func findChildInSiblings<T>(ofType type: T.Type) -> T? {
guard let superview = superview else { return nil }
for sibling in superview.subviews.filter( { $0 != self } ).reversed() {
if let t = sibling.findChild(ofType: type) {
return t
}
}
return nil
}
}
extension View {
public func pullToRefresh(isRefreshing: Binding<Bool>, refreshHandler: @escaping () -> Void) -> some View {
return overlay(
RefreshControl(isRefreshing: isRefreshing, refreshHandler: refreshHandler)
.frame(width: 0, height: 0)
)
}
}
struct PullToRefreshSample_Previews: PreviewProvider {
static var previews: some View {
PullToRefreshSample()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment