Get the second top view in objective c

SwiftUI is getting more mature with every new major iOS release and it is now in a state where it can be used for a lot of use cases. The app that I work on my daily job is based on Objective-C and I wanted to prototype some things in SwiftUI because it was faster and more fun. It was also a great way to escape UIKit for a little and learn more about SwiftUI. That means I had to display SwiftUI views from Objective-C ViewControllers. In this article I will explain how I did that.

There are some limitations between Objective-C and Swift regarding SwiftUI. SwiftUI views are structs and they are not visible from the Objective-C code. The only way to bridge the two worlds is to use some kind of a mediator class between Objective-C and Swift code.

SwiftUI and UIKit

Because we will be displaying SwiftUI views from UIKit we first need to know what abstractions to use to wrap SwiftUI views. UIKit can only present or push UIViewController instances. To create UIViewController instance from SwiftUI view we need to wrap it into a UIHostingController. In UIHostingController initializer we pass the SwiftUI view that we want to wrap like in the example below:

let controller = UIHostingController(rootView: SwiftUIView())

UIHostingController is a subclass of UIViewController and now

import SwiftUI

struct HelloWorldView: View {
  
    var text: String
    
    init(text: String) {
        self.text = text
    }
    
    var body: some View {
        Text(text)
    }
}

1 variable can be used from our UIKit code to present it, push it onto the navigation stack or add it as a child view controller. You can read more about UIHostingController here.

Adding Obj-C to the mix

Now that we know how to display SwiftUI views inside UIKit we need to bridge UIHostingController to the Objective-C world. As I mentioned earlier, Swift structs are not visible from the Objective-C context so we need to add a middleman that will create UIHostingController instances wrapped around SwiftUI views.

I like to call the middleman implementations as SwiftUIViewAdapter or SwiftUIViewFactory. I am still not settled on a final suffix but something between these two seems right.

Let’s start with a simple SwiftUI view that will display the text that is passed into its initializer:

import SwiftUI

struct HelloWorldView: View {
  
    var text: String
    
    init(text: String) {
        self.text = text
    }
    
    var body: some View {
        Text(text)
    }
}

To display the

import SwiftUI

struct HelloWorldView: View {
  
    var text: String
    
    init(text: String) {
        self.text = text
    }
    
    var body: some View {
        Text(text)
    }
}

5 we need to wrap it into the middleman class that will create the UIHostingController:

@objc class HelloWorldViewFactory: NSObject {
    
    @objc static func create(text: String) -> UIViewController {
        let helloWorldView = HelloWorldView(text: text)
        let hostingController = UIHostingController(rootView: helloWorldView)
        
        return hostingController
    }
}

There are a couple of things happening in the

import SwiftUI

struct HelloWorldView: View {
  
    var text: String
    
    init(text: String) {
        self.text = text
    }
    
    var body: some View {
        Text(text)
    }
}

7 class implementation above, so let’s cover them one by one:

  • import SwiftUI struct HelloWorldView: View {

    
    var text: String  
      
    init(text: String) {  
        self.text = text  
    }  
      
    var body: some View {  
        Text(text)  
    }  
    
    }

    7 class has one static function

    import SwiftUI struct HelloWorldView: View {

    
    var text: String  
      
    init(text: String) {  
        self.text = text  
    }  
      
    var body: some View {  
        Text(text)  
    }  
    
    }

    9 that will create the

    import SwiftUI struct HelloWorldView: View {

    
    var text: String  
      
    init(text: String) {  
        self.text = text  
    }  
      
    var body: some View {  
        Text(text)  
    }  
    
    }

    5 and initialize it, wrap it inside the UIHostingController and return it.
  • To expose

    import SwiftUI struct HelloWorldView: View {

    
    var text: String  
      
    init(text: String) {  
        self.text = text  
    }  
      
    var body: some View {  
        Text(text)  
    }  
    
    }

    7 to Objective-C code we need to prefix it with

    @objc class HelloWorldViewFactory: NSObject {

      
    @objc static func create(text: String) -> UIViewController {  
        let helloWorldView = HelloWorldView(text: text)  
        let hostingController = UIHostingController(rootView: helloWorldView)  
          
        return hostingController  
    }  
    
    }

    3 annotation and we need to subclass

    @objc class HelloWorldViewFactory: NSObject {

      
    @objc static func create(text: String) -> UIViewController {  
        let helloWorldView = HelloWorldView(text: text)  
        let hostingController = UIHostingController(rootView: helloWorldView)  
          
        return hostingController  
    }  
    
    }

    4. This is because

    @objc class HelloWorldViewFactory: NSObject {

      
    @objc static func create(text: String) -> UIViewController {  
        let helloWorldView = HelloWorldView(text: text)  
        let hostingController = UIHostingController(rootView: helloWorldView)  
          
        return hostingController  
    }  
    
    }

    4 is the root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects.
  • You can also notice that we are passing text String argument to

    @objc class HelloWorldViewFactory: NSObject {

      
    @objc static func create(text: String) -> UIViewController {  
        let helloWorldView = HelloWorldView(text: text)  
        let hostingController = UIHostingController(rootView: helloWorldView)  
          
        return hostingController  
    }  
    
    }

    6 function. This allows us to use custom initializers for SwiftUI views and to pass dependencies that views need.

Now we can display this view from Objective-C like this:

UIViewController *vc = [HelloWorldViewFactory createWithText:@"Hello from Obj-C!"];
[self presentViewController:vc animated:YES completion:nil];

In this example the device should display the view that has text centered and set to Hello from Obj-C!

Get the second top view in objective c

Importing Swift code into Objective-C

If you didn’t use Swift in your existing Objective-C project it is important to know that you need to import the Swift module header into the Objective-C file that will use your Swift code. That header file is named like

@objc class HelloWorldViewFactory: NSObject {
    
    @objc static func create(text: String) -> UIViewController {
        let helloWorldView = HelloWorldView(text: text)
        let hostingController = UIHostingController(rootView: helloWorldView)
        
        return hostingController
    }
}

7 and if you want, you change the name for that header file inside build settings for your target. The config option that defines the name is Objective-C Generated Interface Header Name.

How to get top most UIViewController in Objective

_topMostController (UIViewController *cont) is a helper function. Now all you need to do is call topMostController () and the top most UIViewController should be returned! Since 1983 I would say. Remember that Objective-C contains C... Wrapping ObjC code in C functions is a common practice, so yeah, this is Objective-C code.

How to extend a class in Objective

There are a few different ways to extend a class in Objective-C, with some approaches being much easier than others. Used if all you need to do is add additional methods to that class. If you also need to add instance variables to an existing class using a category, you can fake this by using associative references.

Can I use a custom container view to position subviews more precisely?

If the autoresizing behaviors do not offer the precise layout that you need for your views, you can use a custom container view and override its layoutSubviews method to position your subviews more precisely. It probably has to do with autoresizing masks read more about it here.

Which side of a Superview does a view appear anchored to?

Thus, the view appears anchored to the left side of its superview while both the view width and the gap to the right of the view increase.

How to pass data from one ViewController to another view controller in objective c?

Different ways to pass data between viewcontrollers/views.

using property..

using functions..

using init method..

using segues..

using closures..

using delegates..

using NotificationCenter..

using Singleton..

How to get current ViewController in Objective C?

You can get the current view controller from rootViewController by looking for its presentedViewController, like this: UIViewController *parentViewController = [[[UIApplication sharedApplication] delegate] window]. rootViewController; while (parentViewController. presentedViewController !=

How to find top most view controller in Swift?

extension UIViewController { /// Top most ViewController func topMostViewController() -> UIViewController { if self. presentedViewController == nil { return self } if let navigation = self.

How to compare two view controllers in Swift?

Related.

Compare self.parentViewController to a given UIViewController..

Compare instances or objects of same UIViewController class..

Compare ParentViewController..

Comparing UIViews..

Comparing UIViewController instances..

compare two NSObject in Swift..