Praanto
Praanto

Reputation: 370

Is it possible to assign a ViewController to a SwiftUI view?

I am building an SwiftUI app with the SwiftUI app protocol.

I need to access some UIKIt functions, particularly the ones that control NavigationView as in here: https://stackoverflow.com/a/56555928/13727105.

Apparently, to do that I need to bind my SwiftUI view with a ViewController.

I've tried doing the following:

ContentView.swift

struct ContentView: View {
    
    var body: some View {
        ZStack {
            ContentViewIntegratedController() // <- here
            NavigationView{
                ScrollView {
                    Text("Content View")
                        .navigationTitle("Content View")

                }
            }
        }
    }
}

class ContentViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

struct ContentViewIntegratedController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> some UIViewController {
        return ContentViewController()
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType,
                                context: UIViewControllerRepresentableContext<ContentViewIntegratedController>) {}
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


but calling ContentViewIntegratedContoller (on line 5) seems to create a new preview instead of modifying the existing one.

Is there anyway to integrate a ViewController to modify a SwiftUI view?

What I don't want to do:

Is there any alternative ways I could access those functions without adding a ViewController to my SwiftUI view?

TIA.

Upvotes: 2

Views: 5559

Answers (2)

AdR
AdR

Reputation: 882

    struct ContentView: View {
    var body: some View {
        ContentViewIntegratedController(view: YourView())
            .ignoresSafeArea()
    }
    }




--------------------------------------------------------------
    struct YourView: View {
    var body: some View {
        ScrollView{
        Text("Hello, World!")
        }
    }}
------------------------------------------------------
    import Foundation
    import SwiftUI

    struct ContentViewIntegratedController :UIViewControllerRepresentable{
    
    var view: YourView
    
    init(view:YourView) {
        self.view = view
    }
    
    func makeUIViewController(context: Context) -> UINavigationController{
        
        let childView = UIHostingController(rootView: view)
        let controller =     UINavigationController(rootViewController:childView)
        let appearance = UINavigationBarAppearance()
        let searchController = UISearchController()
        
        
        searchController.searchBar.barStyle = .black
        
        appearance.backgroundColor = UIColor(Color(.red))
        appearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
        appearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
        
        
        controller.navigationBar.topItem?.compactAppearance = appearance
        controller.navigationBar.topItem?.scrollEdgeAppearance = appearance
        controller.navigationBar.topItem?.standardAppearance = appearance
        

        controller.navigationBar.topItem?.title = "navigation bar"
        controller.navigationBar.prefersLargeTitles = true
        
        searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Rechercher...", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
        searchController.searchBar.setValue("Annuler", forKey: "cancelButtonText")
        
        
        searchController.searchBar.showsBookmarkButton = true
        searchController.searchBar.searchTextField.leftView?.tintColor = .white
        
        let sfConfiguration = UIImage.SymbolConfiguration(pointSize: 30)
        let barCodeIcon = UIImage(systemName: "barcode.viewfinder")?.withTintColor(.white, renderingMode: .alwaysOriginal).withConfiguration(sfConfiguration)
    

        searchController.searchBar.setImage(barCodeIcon, for: .bookmark, state:.normal)
        searchController.obscuresBackgroundDuringPresentation = false
  

        let attributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
        UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(attributes, for: .normal)
       
        controller.navigationBar.topItem?.hidesSearchBarWhenScrolling = false
        controller.navigationBar.topItem?.searchController = searchController
        
        return controller
        
    }
    
    
    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
        
    }}

Example

Upvotes: 3

George
George

Reputation: 30391

There is no way in pure SwiftUI to directly detect the changing between large and inline display modes. However, using SwiftUI-Introspect, you can drop down into UIKit to solve this.

I solved this by getting the view for the large title, and detecting when the alpha property changed. This changes at the exact moment when the title changes from large to inline, or vice-versa.

Code:

struct ContentView: View {
    @State private var observer: NSKeyValueObservation?
    @State private var title: String = "Large Title"

    var body: some View {
        NavigationView {
            ScrollView {
                Text("Hello world!")
            }
            .navigationTitle(title)
        }
        .introspectNavigationController { nav in
            let largeTitleView = nav.navigationBar.subviews.first { view in
                String(describing: type(of: view)) == "_UINavigationBarLargeTitleView"
            }
            observer = largeTitleView?.observe(\.alpha) { view, change in
                let isLarge = view.alpha == 1
                title = isLarge ? "Large Title" : "Inline title"
            }
        }
    }
}

Result:

Result

Upvotes: 2

Related Questions