Swift noob here. I have a simple app that has a main page which is a ViewController, relevant code below:
import SwiftUI
class GameViewController: UIViewController {
...
func updateUI() {
switch (SettingsView().selectedBGColor) {
case 0: self.view.backgroundColor = .black
case 1: self.view.backgroundColor = .red
case 2: self.view.backgroundColor = .blue
default: break
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
updateUI()
...
}
}
And the second page is a Settings page invoked by a UIbutton press. This page is not a ViewController, just a View. Relevant code below:
struct SettingsView: View {
...
var bgcolors = ["Black", "Red", "Blue"]
#AppStorage("selectedBGColor") var selectedBGColor = 0
#Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
Form {
...
Section {
Picker(selection: $selectedBGColor, label: Text("Background color")) {
ForEach(0 ..< bgcolors.count, id: \.self) {
Text(self.bgcolors[$0]).tag($0)
}
}
}
Section {
Button("Save and exit", action: {
GameViewController().updateUI()
dismiss.callAsFunction()
}).navigationBarTitle(Text("Settings"))
}
}
}
}
}
When I change the selected background color in the settings and close the settings by clicking the button, the background color of the ViewController doesn't change until I relaunch the app. How do I force it to update? I tried various things I found here like loadView(), setNeedsDisplay(), DispatchQueue, ViewWillAppear/ViewDidAppearand performSegue()but nothing worked. Help would be greatly appreciated!
Related
Have a UIKit Navigation Controller that is the root view controller in the hierarchy of the storyboards. One of the button items navigates to a UIHostedViewController that produces a SwiftUI View. That SwiftUI View is in and of itself a Tab View with tabs - and all of it works fine.
One of the crux of our development is displaying large data models for the user, and we could regain a bit of screen real estate if we utilize the Navigation bar of the UIKit Navigation.
A defining attribute of our individual views is the color coding of the header and footer, we have been able to change these colors easily as the SwiftUI Tab View just calls different views with different modifiers.
What we would like to do is have the selection of the SwiftUI Tab View set a shared property with the UIKit view that would then change the navigation bar color - however the way we setup a bound variable and reload the NavigationBar setup with a didSet on that variable does not seem to be working.
The code is almost exactly as follows, less a couple of views.
class SomeViewController: UIHostingController< MySwiftUITabView > {
var headerColor: Binding<UIColor> = .constant(UIColor.blue) {
didSet {
DispatchQueue.main.async(qos: .userInteractive) {
self.setupUI()
}
}
}
var defaultColor: UIColor!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder, rootView: MySwiftUITabView(headerColor: headerColor))
}
override func viewDidLoad() {
super.viewDidLoad()
defaultColor = self.navigationController?.navigationBar.backgroundColor
setupUI()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
teardownUI()
}
func setupUI() {
if let naviController = self.navigationController {
let newNavBarAppearance = UINavigationBarAppearance()
newNavBarAppearance.backgroundColor = headerColor.wrappedValue
naviController.navigationBar.scrollEdgeAppearance = newNavBarAppearance
naviController.navigationBar.compactAppearance = newNavBarAppearance
naviController.navigationBar.standardAppearance = newNavBarAppearance
}
}
func teardownUI() {
if let naviController = self.navigationController {
let oldNavBarAppearance = UINavigationBarAppearance()
oldNavBarAppearance.backgroundColor = defaultColor
naviController.navigationBar.scrollEdgeAppearance = oldNavBarAppearance
naviController.navigationBar.compactAppearance = oldNavBarAppearance
naviController.navigationBar.standardAppearance = oldNavBarAppearance
}
}
}
And the SwiftUI Tab View:
struct MySwiftUITabView: View {
#Binding var headerColor: UIColor
var body: some View {
TabView(selection: $selectedTab){
TestView()
.tag(0)
.onAppear {
headerColor = .orange
}
.tabItem {
Label("Test", image: "circle.fill")
}
TestView()
.tag(1)
.onAppear {
headerColor = .red
}
.tabItem {
Label("Test", image: "square.fill")
}
}
.accentColor(.white)
}
}
Would a callback be more appropriate for this? We wrapped the didSet function call in a DispatchGroup as a final attempt since it is updating the UI, but the was not effective still for what we are looking for.
In this example, the navigation bar remains the same color as the original set value (.blue) and never gets updated to the color set to the binding by the .onAppear of the swiftUI tab items.
Any help would be greatly appreciated as always!
I have an issue with Xcode 12 / iOS 14. Using multiple NavigationLinks in a sheet with NavigationView leads to NavigationLink entries staying highlighted after going back a page. This is not only a problem with the simulator. See the attached GIF:
Does anybody know how to fix this?
Similar question: SwiftUI - NavigationLink cell in a Form stays highlighted after detail pop (but that's not the problem here).
struct ContentView: View {
var body: some View {
Text("")
.sheet(isPresented: .constant(true), content: {
NavigationView {
Form {
Section {
NavigationLink("Link to ViewB", destination: ViewB())
}
}
.navigationBarTitle("ViewA")
}
})
}
}
struct ViewB: View {
#State var selection = 0
let screenOptions = ["a", "b", "c"]
var body: some View{
Form {
Section {
NavigationLink("Link to ViewC", destination: ViewC())
}
}
.navigationBarTitle("ViewB")
}
}
struct ViewC: View {
var body: some View{
Form {
Section {
Text("Test")
}
}
.navigationBarTitle("ViewC")
}
}
I've also run into this problem when using a NavigationLink inside a sheet. My solution on iOS 14 has been too Swizzle didSelectRowAt: of UITableView. When the row is selected, I deselect it. There is more code for detecting if its in a sheet, etc, but this is the basic, get it working code:
extension UITableView {
#objc static func swizzleTableView() {
guard self == UITableView.self else {
return
}
let originalTableViewDelegateSelector = #selector(setter: self.delegate)
let swizzledTableViewDelegateSelector = #selector(self.nsh_set(delegate:))
let originalTableViewMethod = class_getInstanceMethod(self, originalTableViewDelegateSelector)
let swizzledTableViewMethod = class_getInstanceMethod(self, swizzledTableViewDelegateSelector)
method_exchangeImplementations(originalTableViewMethod!,
swizzledTableViewMethod!)
}
#objc open func nsh_set(delegate: UITableViewDelegate?) {
nsh_set(delegate: delegate)
guard let delegate = delegate else { return }
let originalDidSelectSelector = #selector(delegate.tableView(_:didSelectRowAt:))
let swizzleDidSelectSelector = #selector(self.tableView(_:didSelectRowAt:))
let swizzleMethod = class_getInstanceMethod(UITableView.self, swizzleDidSelectSelector)
let didAddMethod = class_addMethod(type(of: delegate), swizzleDidSelectSelector, method_getImplementation(swizzleMethod!), method_getTypeEncoding(swizzleMethod!))
if didAddMethod {
let didSelectOriginalMethod = class_getInstanceMethod(type(of: delegate), NSSelectorFromString("tableView:didSelectRowAt:"))
let didSelectSwizzledMethod = class_getInstanceMethod(type(of: delegate), originalDidSelectSelector)
if didSelectOriginalMethod != nil && didSelectSwizzledMethod != nil {
method_exchangeImplementations(didSelectOriginalMethod!, didSelectSwizzledMethod!)
}
}
}
#objc open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView(tableView, didSelectRowAt: indexPath)
// This is specifically to fix a bug in SwiftUI, where a NavigationLink is
// not de-selecting itself inside a sheet.
tableView.deselectRow(at: indexPath,
animated: true)
}
}
(Original swizzle code is from https://stackoverflow.com/a/59262109/127853), this code sample just adds the deselectRow call.)
Don't forget to call UITableView.swizzleTableView() somewhere such as application:didFinishLaunchingWithOptions:
Add the following modifier to your NavigationView to set navigation view style and fix this issue:
.navigationViewStyle(StackNavigationViewStyle())
Explanation:
Default style is DefaultNavigationViewStyle(), from documentation: "The default navigation view style in the current context of the view being styled".
For some reason this will pick up DoubleColumnNavigationViewStyle instead of StackNavigationViewStyle on iPhone, if you set style explicitly it behaves as expected.
Is there a way to round the corners on an iOS page sheet view controller? Currently, iOS page sheets by default present like this:
But instead, I would like the corners to be like this:
iOS 15 added an API to customize the corner radius of sheets, UISheetPresentationController.preferredCornerRadius:
let myViewController = UIViewController()
myViewController.view.backgroundColor = .systemYellow
myViewController.sheetPresentationController?.preferredCornerRadius = 25
present(myViewController, animated: true, completion: nil)
In your view controller, you can change the view.layer.cornerRadius property to the value you want
override func viewDidLoad() {
super.viewDidLoad()
view.layer.cornerRadius = 10.0 // You can freely change this value
}
As an example, the following code:
override func viewDidLoad() {
super.viewDidLoad()
view.layer.cornerRadius = 25.0
view.backgroundColor = .systemPurple
}
gives me the following result:
I found a way to make it work.
In the onAppear method of your sheet, get the viewcontroller that is displaying the sheet using UIApplication.shared.activeWindows.last?.rootViewController then get the sheet viewcontroller with presentedViewController and do your things on it.
struct Example: View {
#State var showSheet = true
var body: some View {
Text("Hello, World!")
.sheet(isPresented: $showSheet, content: {
Text("hello")
.onAppear {
if let controller = UIApplication.shared.activeWindows.last?.rootViewController {
if let presentedVC = controller.presentedViewController {
presentedVC.view.backgroundColor = .red
presentedVC.view.layer.cornerRadius = 30
}
}
}
})
}
}
It might sound like a trivial task but I can't find a proper solution for this problem. Possibly I haven't internalized the "SwiftUI-ish" way of thinking yet.
I have a view with a button. When the view loads, there is a condition (already logged in?) under which the view should directly go to the next view. If the button is clicked, an API call is triggered (login) and if it was successful, the redirect to the next view should also happen.
My attempt was to have a model (ObservableObject) that holds the variable "shouldRedirectToUploadView" which is a PassThroughObject. Once the condition onAppear in the view is met or the button is clicked (and the API call is successful), the variable flips to true and tells the observer to change the view.
Flipping the "shouldRedirectToUploadView" in the model seems to work but I can't make the view re-evaluate that variable so the new view won't open.
Here is my implementation so far:
The model
import SwiftUI
import Combine
class SboSelectorModel: ObservableObject {
var didChange = PassthroughSubject<Void, Never>()
var shouldRedirectToUpdateView = false {
didSet {
didChange.send()
}
}
func fetch(_ text: String) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.shouldRedirectToUpdateView = true
}
}
}
The view
import SwiftUI
struct SboSelectorView: View {
#State var text: String = ""
#ObservedObject var model: SboSelectorModel
var body: some View {
return ZStack {
if (model.shouldRedirectToUpdateView) {
UpdateView()
}
else {
Button(action: {
self.reactOnButtonClick()
}) {
Text("Start")
}
}
}.onAppear(perform: initialActions)
}
public func initialActions() {
self.model.shouldRedirectToUpdateView = true
}
private func reactOnButtonClick() {
self.model.fetch()
}
}
In good old UIKit I would have just used a ViewController to catch the action of button click and then put the new view on the navigation stack. How would I do it in SwiftUI?
In the above example I would expect the view to load, execute the onAppear() function which executes initialActions() to flip the model variable what would make the view react to that change and present the UploadView. Why doesn't it happen that way?
There are SO examples like Programatically navigate to new view in SwiftUI or Show a new View from Button press Swift UI or How to present a view after a request with URLSession in SwiftUI? that suggest the same procedure. However it does not seem to work for me. Am I missing something?
Thank you in advance!
Apple has introduced #Published which does all the model did change stuff.
This works for me and it looks much cleaner.
You can also use .onReceive() to perform stuff on a view when something in your model changes.
class SboSelectorModel: ObservableObject {
#Published var shouldRedirectToUpdateView = false
func fetch(_ text: String) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.shouldRedirectToUpdateView = true
}
}
}
struct UpdateView: View {
var body: some View {
Text("Hallo")
}
}
struct SboSelectorView: View {
#State var text: String = ""
#ObservedObject var model = SboSelectorModel()
var body: some View {
ZStack {
if (self.model.shouldRedirectToUpdateView) {
UpdateView()
}
else {
Button(action: {
self.reactOnButtonClick()
}) {
Text("Start")
}
}
}.onAppear(perform: initialActions)
}
public func initialActions() {
self.model.shouldRedirectToUpdateView = true
}
private func reactOnButtonClick() {
self.model.fetch("")
}
}
I hope this helps.
EDIT
So this seems to have changed in beta 5
Here a working model with PassthroughSubject:
class SboSelectorModel: ObservableObject {
let objectWillChange = PassthroughSubject<Bool, Never>()
var shouldRedirectToUpdateView = false {
willSet {
objectWillChange.send(shouldRedirectToUpdateView)
}
}
func fetch(_ text: String) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.shouldRedirectToUpdateView = true
}
}
}
Just in case any wants an alternative to the SwiftUI way of having Observable Objects and the like - which can be great, but as I was building out, I noticed I had like, 100 objects and didn't like in the slightest how complicated it all felt. (Oh, how I wanted to just type self.present("nextScene", animated: true)). I know a large part of this is my mind just not up to that SwiftUI life yet but just in case anyone else wants a more... UIKit meets SwiftUI alternative, here's a system that works.
I'm not a professional so I don't know if this is the best memory management way.
First, create a function that allows you to know what the top view controller is on the screen. The code below was borrowed from db0Company on GIT.
import UIKit
extension UIViewController {
func topMostViewController() -> UIViewController {
if let presented = self.presentedViewController {
return presented.topMostViewController()
}
if let navigation = self.presentedViewController as? UINavigationController {
return navigation.visibleViewController?.topMostViewController() ?? navigation
}
if let tab = self as? UITabBarController {
return tab.selectedViewController?.topMostViewController() ?? tab
}
return self
}
}
extension UIApplication {
func topMostViewController() -> UIViewController? {
return self.keyWindow?.rootViewController?.topMostViewController()
}
}
Create an enum - now this is optional, but I think very helpful - of your SwiftUI and UIViewControllers; for demonstration purposes, I have 2.
enum RootViews {
case example, welcome
}
Now, here's some fun; create a delegate you can call from your SwiftUI views to move you from scene to scene. I call mine Navigation Delegate.
I added some default presentation styles here, to make calls easier via the extension.
import UIKit //SUPER important!
protocol NavigationDelegate {
func moveTo(view: RootViews, presentation: UIModalPresentationStyle, transition: UIModalTransitionStyle)
}
extension NavigationDelegate {
func moveTo(view: RootViews) {
self.moveTo(view: view, presentation: .fullScreen, transition: .crossDissolve)
}
func moveTo(view: RootViews, presentation: UIModalPresentationStyle) {
self.moveTo(view: view, presentation: presentation, transition: .crossDissolve)
}
func moveTo(view: RootViews, transition: UIModalTransitionStyle) {
self.moveTo(view: view, presentation: .fullScreen, transition: transition)
}
}
And here, I create a RootViewController - a classic, Cocoa Touch Class UIViewController. This will conform to the delegate, and be where we actually move screens.
class RootViewController: UIViewController, NavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.moveTo(view: .welcome) //Which can always be changed
}
//The Moving Function
func moveTo(view: RootViews, presentation: UIModalPresentationStyle = .fullScreen, transition: UIModalTransitionStyle = .crossDissolve) {
let newScene = self.returnSwiftUIView(type: view)
newScene.modalPresentationStyle = presentation
newScene.modalTransitionStyle = transition
//Top View Controller
let top = self.topMostViewController()
top.present(newScene, animated: true)
}
//Swift View switch. Optional, but my Xcode was not happy when I tried to return a UIHostingController in line.
func returnSwiftUIView(type: RootViews) -> UIViewController {
switch type {
case .welcome:
return UIHostingController(rootView: WelcomeView(delegate: self))
case .example:
return UIHostingController(rootView: ExampleView(delegate: self))
}
}
}
So now, when you create new SwiftUI Views, you just need to add the Navigation Delegate, and call it when a button is pressed.
import SwiftUI
import UIKit //Very important! Don't forget to import UIKit
struct WelcomeView: View {
var delegate: NavigationDelegate?
var body: some View {
Button(action: {
print("full width")
self.delegate?.moveTo(view: .name)
}) {
Text("NEXT")
.frame(width: UIScreen.main.bounds.width - 20, height: 50, alignment: .center)
.background(RoundedRectangle(cornerRadius: 15, style: .circular).fill(Color(.systemPurple)))
.accentColor(.white)
}
}
And last but not least, in your scene delegate, create your RootViewController() and use that as your key, instead of the UIHostingController(rootView: contentView).
Voila.
I hope this can help someone out there! And for my more professional senior developers out there, if you can see a way to make it... cleaner? Or whatever it is that makes code less bad, feel free!
Using a List view, is there a way to access (and therefore modify) the underlying UITableView object without reimplementing the entire List as a UIViewRepresentable?
I've tried initializing a List within my own UIViewRepresentable, but I can't seem to get SwiftUI to initialize the view when I need it to, and I just get an empty basic UIView with no subviews.
This question is to help find an answer for Bottom-first scrolling in SwiftUI.
Alternatively, a library or other project that reimplements UITableView in SwiftUI would also answer this question.
The answer is Yes. There's an amazing library that lets you inspect the underlying UIKit views. Here's a link to it.
The answer is no. As of iOS 13, SwiftUI's List is not currently designed to replace all the functionality and customizability of UITableView. It is designed to meet the most basic use of a UITableView: a standard looking, scrollable, editable list where you can place a relatively simply view in each cell.
In other words, you are giving up customizability for the simplicity of having swipes, navigation, moves, deletes, etc. automatically implemented for you.
I'm sure that as SwiftUI evolves, List (or an equivalent view) will get more customizable, and we'll be able to do things like scroll from the bottom, change padding, etc. The best way to make sure this happens is to file feedback suggestions with Apple. I'm sure the SwiftUI engineers are already hard at work designing the features that will appear at WWDC 2020. The more input they have to guide what the community wants and needs, the better.
I found a library called Rotoscope on GitHub (I am not the author of this).
This library is used to implement RefreshUI also on GitHub by the same author.
How it works is that Rotoscope has a tagging method, which overlays a 0 sized UIViewRepresentable on top of your List (so it's invisible). The view will dig through the chain of views and eventually find the UIHostingView that's hosting the SwiftUI views. Then, it will return the first subview of the hosting view, which should contains a wrapper of UITableView, then you can access the table view object by getting the subview of the wrapper.
The RefreshUI library uses this library to implement a refresh control to the SwiftUI List (you can go into the GitHub link and check out the source to see how it's implemented).
However, I see this more like a hack than an actual method, so it's up to you to decide whether you want to use this or not. There are no guarantee that it will continue working between major updates as Apple could change the internal view layout and this library will break.
You can Do it. But it requires a Hack.
Add Any custom UIView
Use UIResponder to backtrack until you find table View.
Modify UITableView The way you like.
Code Example of Adding Pull to refresh:
//1: create a custom view
final class UIKitView : UIViewRepresentable {
let callback: (UITableView) -> Void
init(leafViewCB: #escaping ((UITableView) -> Void)) {
callback = leafViewCB
}
func makeUIView(context: Context) -> UIView {
let view = UIView.init(frame: CGRect(x: CGFloat.leastNormalMagnitude,
y: CGFloat.leastNormalMagnitude,
width: CGFloat.leastNormalMagnitude,
height: CGFloat.leastNormalMagnitude))
view.backgroundColor = .clear
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
if let superView = uiView.superview {
superView.backgroundColor = uiView.backgroundColor
}
if let tableView = uiView.next(UITableView.self) {
callback(tableView)
}
}
}
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
////Use:
struct Result: Identifiable {
var id = UUID()
var value: String
}
class RefreshableObject: ObservableObject {
let id = UUID()
#Published var items: [Result] = [Result(value: "Binding"),
Result(value: "ObservableObject"),
Result(value: "Published")]
let refreshControl: UIRefreshControl
init() {
refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action:
#selector(self.handleRefreshControl),
for: .valueChanged)
}
#objc func handleRefreshControl(sender: UIRefreshControl) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
sender.endRefreshing()
self?.items = [Result(value:"new"), Result(value:"data"), Result(value:"after"), Result(value:"refresh")]
}
}
}
struct ContentView: View {
#ObservedObject var refreshableObject = RefreshableObject()
var body: some View {
NavigationView {
Form {
Section(footer: UIKitView.init { (tableView) in
if tableView.refreshControl == nil {
tableView.refreshControl = self.refreshableObject.refreshControl
}
}){
ForEach(refreshableObject.items) { result in
Text(result.value)
}
}
}
.navigationBarTitle("Nav bar")
}
}
}
Screenshot:
To update from refresh action, binding isUpdateOrdered is being used.
this code is based on code I found in web, couldn't find the author
import Foundation
import SwiftUI
class Model: ObservableObject{
#Published var isUpdateOrdered = false{
didSet{
if isUpdateOrdered{
update()
isUpdateOrdered = false
print("we got him!")
}
}
}
var random = 0
#Published var arr = [Int]()
func update(){
isUpdateOrdered = false
//your update code.... maybe some fetch request or POST?
}
}
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
NavigationView {
LegacyScrollViewWithRefresh(isUpdateOrdered: $model.isUpdateOrdered) {
VStack{
if model.arr.isEmpty{
//this is important to fill the
//scrollView with invisible data,
//in other case scroll won't work
//because of the constraints.
//You may get rid of them if you like.
Text("refresh!")
ForEach(1..<100){ _ in
Text("")
}
}else{
ForEach(model.arr, id:\.self){ i in
NavigationLink(destination: Text(String(i)), label: { Text("Click me") })
}
}
}
}.environmentObject(model)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct LegacyScrollViewWithRefresh: UIViewRepresentable {
enum Action {
case idle
case offset(x: CGFloat, y: CGFloat, animated: Bool)
}
typealias Context = UIViewRepresentableContext<Self>
#Binding var action: Action
#Binding var isUpdateOrdered: Bool
private let uiScrollView: UIScrollView
private var uiRefreshControl = UIRefreshControl()
init<Content: View>(isUpdateOrdered: Binding<Bool>, content: Content) {
let hosting = UIHostingController(rootView: content)
hosting.view.translatesAutoresizingMaskIntoConstraints = false
self._isUpdateOrdered = isUpdateOrdered
uiScrollView = UIScrollView()
uiScrollView.addSubview(hosting.view)
let constraints = [
hosting.view.leadingAnchor.constraint(equalTo: uiScrollView.leadingAnchor),
hosting.view.trailingAnchor.constraint(equalTo: uiScrollView.trailingAnchor),
hosting.view.topAnchor.constraint(equalTo: uiScrollView.contentLayoutGuide.topAnchor),
hosting.view.bottomAnchor.constraint(equalTo: uiScrollView.contentLayoutGuide.bottomAnchor),
hosting.view.widthAnchor.constraint(equalTo: uiScrollView.widthAnchor)
]
uiScrollView.addConstraints(constraints)
self._action = Binding.constant(Action.idle)
}
init<Content: View>(isUpdateOrdered: Binding<Bool>, #ViewBuilder content: () -> Content) {
self.init(isUpdateOrdered: isUpdateOrdered, content: content())
}
init<Content: View>(isUpdateOrdered: Binding<Bool>, action: Binding<Action>, #ViewBuilder content: () -> Content) {
self.init(isUpdateOrdered: isUpdateOrdered, content: content())
self._action = action
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIScrollView {
uiScrollView.addSubview(uiRefreshControl)
uiRefreshControl.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl(arguments:)), for: .valueChanged)
return uiScrollView
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
switch self.action {
case .offset(let x, let y, let animated):
uiView.setContentOffset(CGPoint(x: x, y: y), animated: animated)
DispatchQueue.main.async {
self.action = .idle
}
default:
break
}
}
class Coordinator: NSObject {
let legacyScrollView: LegacyScrollViewWithRefresh
init(_ legacyScrollView: LegacyScrollViewWithRefresh) {
self.legacyScrollView = legacyScrollView
}
#objc func handleRefreshControl(arguments: UIRefreshControl){
print("refreshing")
self.legacyScrollView.isUpdateOrdered = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2){
arguments.endRefreshing()
//refresh animation will
//always be shown for 2 seconds,
//you may connect this behaviour
//to your update completion
}
}
}
}
There is currently no way to access or modify the underlying UITableView