Skip to content

Instantly share code, notes, and snippets.

@beliy
Created March 27, 2020 11:29
Show Gist options
  • Save beliy/1cee5d5aa8ce83357c822d0fcdaf7f48 to your computer and use it in GitHub Desktop.
Save beliy/1cee5d5aa8ce83357c822d0fcdaf7f48 to your computer and use it in GitHub Desktop.
SwiftUI - Pull to Refresh
//
// ContentView.swift
// PullToRefresh
//
// Created by Alexey Belousov on 20.03.2020.
// Copyright © 2020 AskJack. All rights reserved.
//
import SwiftUI
import Combine
private let kFirstNames = [
"James", "John", "Robert", "Michael", "William",
"David", "Richard", "Joseph", "Charles", "Thomas",
]
private let kLastNames = [
"Adams", "Allen", "Anderson", "Armstrong", "Atkinson",
"Bailey", "Baker", "Ball", "Barker", "Barnes",
]
struct Record: Identifiable {
let id: Int
let name: String
let amount: Int
}
struct Generator {
static let `default` = Generator()
private func randomName() -> String {
[
kFirstNames.randomElement(),
kLastNames.randomElement()
].compactMap { $0 }
.joined(separator: " ")
}
public func generate(_ count: Int = 10) -> AnyPublisher<[Record], Never> {
Just((0..<count).map {
Record(id: $0 + 1, name: randomName(), amount: Int.random(in: 50..<500))
}).setFailureType(to: Never.self)
.eraseToAnyPublisher()
}
}
struct ContentView: View {
@State private var records: [Record] = []
@State private var disposables = Set<AnyCancellable>()
private let generator: Generator = .default
var body: some View {
NavigationView {
ZStack {
ScrollViewAppearance(action: pullToRefresh) {
ZStack {
Color.red.opacity(0.05)
.edgesIgnoringSafeArea(.bottom)
ScrollView {
VStack(spacing: 8) {
ForEach(records) { record in
HStack {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("#\(record.id)")
Text(record.name)
}.font(.headline)
Text("Amount: \(record.amount)")
.font(.callout)
}
Spacer()
}.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
}
}.padding()
Divider()
.hidden()
}
}
}.edgesIgnoringSafeArea(.bottom)
}.navigationBarTitle("Pull to Refresh", displayMode: .inline)
.onAppear(perform: fetch)
}
}
}
extension ContentView {
private func fetch() {
generator.generate()
.receive(on: RunLoop.main)
.assign(to: \.records, on: self)
.store(in: &disposables)
}
private func pullToRefresh(refreshControl: UIRefreshControl) {
print(#function)
generator.generate()
.receive(on: RunLoop.main)
.sink { records in
self.records = records
refreshControl.endRefreshing()
}
.store(in: &disposables)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//
// ScrollViewAppearance.swift
// PullToRefresh
//
// Created by Alexey Belousov on 20.03.2020.
// Copyright © 2020 AskJack. All rights reserved.
//
import SwiftUI
import Combine
struct ScrollViewAppearance<Content>: UIViewControllerRepresentable where Content: View {
typealias UIViewControllerType = UIHostingController<Content>
class Coordinator: NSObject {
private let action: (UIRefreshControl) -> Void
init(action: @escaping (UIRefreshControl) -> Void) {
self.action = action
}
@objc func handleRefreshControl(sender: UIRefreshControl) {
action(sender)
}
}
private let action: (UIRefreshControl) -> Void
private let content: Content
init(action handler: @escaping (UIRefreshControl) -> Void, @ViewBuilder content builder: () -> Content) {
action = handler
content = builder()
}
func makeCoordinator() -> Coordinator {
Coordinator(action: action)
}
func makeUIViewController(context: Context) -> UIHostingController<Content> {
let scrollView = UIScrollView.appearance(whenContainedInInstancesOf: [UIViewControllerType.self])
scrollView.refreshControl = UIRefreshControl()
scrollView.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl(sender:)), for: .valueChanged)
return UIHostingController(rootView: content)
}
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
uiViewController.rootView = content
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment