swiftUI ScrollView UIRefreshControl overlay on navigation bar

278 Views Asked by At

Until iOS 15.2 , the SwiftUI ScrollView doesn't support refreshable{}, so I using UIRefreshControl to solve this issues. Everything went smoothly and success was just one step away ;-(

enter image description here

full code ContentView.swift :


import SwiftUI


extension UIView {
  
    func viewsInHierarchy<ViewType: UIView>() -> [ViewType]? {
        var views: [ViewType] = []
        viewsInHierarchy(views: &views)
        return views.count > 0 ? views : nil
    }
    
    fileprivate func viewsInHierarchy<ViewType: UIView>(views: inout [ViewType]) {
        subviews.forEach { eachSubView in
            if let matchingView = eachSubView as? ViewType {
                views.append(matchingView)
            }
            eachSubView.viewsInHierarchy(views: &views)
        }
    }
    
    func searchViewAnchestorsFor<ViewType: UIView>(
        _ onViewFound: (ViewType) -> Void
    ) {
        if let matchingView = self.superview as? ViewType {
            onViewFound(matchingView)
        } else {
            superview?.searchViewAnchestorsFor(onViewFound)
        }
    }
    
    func searchViewAnchestorsFor<ViewType: UIView>() -> ViewType? {
        if let matchingView = self.superview as? ViewType {
            return matchingView
        } else {
            return superview?.searchViewAnchestorsFor()
        }
    }
    
    func printViewHierarchyInformation(_ level: Int = 0) {
        printViewInformation(level)
        self.subviews.forEach { $0.printViewHierarchyInformation(level + 1) }
    }
        
    func printViewInformation(_ level: Int) {
        let leadingWhitespace = String(repeating: "    ", count: level)
        let className = "\(Self.self)"
        let superclassName = "\(self.superclass!)"
        let frame = "\(self.frame)"
        let id = (self.accessibilityIdentifier == nil) ? "" : " `\(self.accessibilityIdentifier!)`"
        print("\(leadingWhitespace)\(className): \(superclassName)\(id) \(frame)")
    }
}


final class ViewControllerResolver: UIViewControllerRepresentable {
    
    let onResolve: (UIViewController) -> Void
        
    init(onResolve: @escaping (UIViewController) -> Void) {
        self.onResolve = onResolve
    }
    
    func makeUIViewController(context: Context) -> ParentResolverViewController {
        ParentResolverViewController(onResolve: onResolve)
    }
    
    func updateUIViewController(_ uiViewController: ParentResolverViewController, context: Context) { }
}

class ParentResolverViewController: UIViewController {
    
    let onResolve: (UIViewController) -> Void
        
    init(onResolve: @escaping (UIViewController) -> Void) {
        self.onResolve = onResolve
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("Use init(onResolve:) to instantiate ParentResolverViewController.")
    }
    
    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)
        if let parent = parent {
            //parent.navigationController?.navigationBar.prefersLargeTitles = true
            //parent.navigationItem.largeTitleDisplayMode = .never
            self.onResolve(parent)
            // print("didMove(toParent: \(parent)")
        }
    }
}



struct ContentView: View {
    

    var body: some View {
        NavigationView {
            
            ScrollView {
                VStack {
                    HStack {
                        Spacer()
                        Text("Content")
                        Spacer()
                    }
                    
                    Spacer()
                }
                .background(Color.red)
            }
            .navigationTitle("title")
            .navigationBarTitleDisplayMode(.inline)
            .overlay(
                ViewControllerResolver { parent in
                    
                    var scrollView: UIScrollView?
                    
                    if let scrollViewsInHierarchy: [UIScrollView] = parent.view.viewsInHierarchy() {
                        if scrollViewsInHierarchy.count == 1, let firstScrollViewInHierarchy = scrollViewsInHierarchy.first {
                            scrollView = firstScrollViewInHierarchy
                        }
                    }
                    
                    if let scrollView = scrollView {
                        
                        guard scrollView.refreshControl == nil else { return }
                        
                        let refreshControl = UIRefreshControl()
                        
                        refreshControl.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
                        
                        refreshControl.layoutIfNeeded()
                        
                        let uiAction = UIAction(handler: { uiAction in
                            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                                refreshControl.endRefreshing()
                            }
                        })

                        refreshControl.addAction(uiAction, for: .primaryActionTriggered)
                        scrollView.refreshControl = refreshControl
                    }
                    
                }
                .frame(width: 0, height: 0)
            )
            
        }
    }
}

0

There are 0 best solutions below