2

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:

  • Create a Storyboards app with SwiftUI views in it
  • Create a Storyboards view for ContentViewIntegratedViewController.

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

TIA.

Praanto
  • 216
  • 4
  • 14
  • @Andrew I did. ``ContentViewIntegratedViewController`` extends a `UIViewControllerRepresentable`. But it still doesn't seem to work. – Praanto Sep 17 '21 at 07:03
  • If you want Storyboards based app you should create project with UIKit-based template and then integrate SwiftUI views in it using `UIHostingController` – Asperi Sep 17 '21 at 07:09
  • 1
    Looking at the linked question, what you want to do is a bit more tricky than simply using a UIKit view with SwiftUI. You want to access the properties of the navigation controller that underpins the `NavigationView`. I don't think that is possible. – Paulw11 Sep 17 '21 at 07:11

2 Answers2

3
    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

AdR
  • 572
  • 1
  • 4
  • 13
1

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

George
  • 25,988
  • 10
  • 79
  • 133
  • This method surely works but the compact title changes to large title for a split second which is something I'd want to avoid. There is a new [insert Swift jargon] called Toolbar. Is there anyway to observe the change in the alpha value of the toolbar? – Praanto Sep 19 '21 at 11:03
  • @PraantoSamadder I don't believe there is a way to fix this. It's due to the observer being called _after_ the navigation bar display mode has changed. – George Sep 19 '21 at 18:40