Interoperability between C++, Objective-C, Swift and SwiftUI

The below diagram shows the pattern I have been following to make my integration easy between the various parts of my code.
Dependencies: iOS 13 is the deployment target

Let’s start with an Xcode Objective-C project. Let’s name the project Interoperability. Please pay careful attention to your project name. You would be using the name to include the header file which is generated by Xcode. e.g. #import "interoperability-Swift.h" I will explain this part later in this blog.

Let’s start with the UX. I have used SwiftUI. Pay attention to this snippet of code below. What I have done here is, create an object of my model class swiftvc(), then called a method on that class. This is my opening to the swift backed for now.

let vc = swiftvc()
vc.challengerIsCalled()

Below is the complete code for SwiftUI.

import SwiftUI
struct ContentView: View {
	var challengerPrice : Int = 0
	var controlPrice : Int = 0
	@State var phoneOnlyViewBorderColor = Color.black
	@State var allPlatformsViewBorderColor = Color.black
	
	init(challengerPrice : Int, controlPrice : Int) {
		self.challengerPrice = challengerPrice
		self.controlPrice = controlPrice
	}
	
    var body: some View {
		GeometryReader { metrics in
			VStack {
					Image("purchasePhone")
					.resizable()
					.aspectRatio(contentMode: .fit)
					VStack {
						Text("Upgrade")
					}
				
					HStack(alignment: .center, spacing: 10) {
						phoneOnlyView(self.challengerPrice)
					.background(Rectangle())
					.foregroundColor(Color.clear)
					.overlay(
						Rectangle()
							.strokeBorder(
								style: StrokeStyle(
									lineWidth: 2
								)
							)
							.foregroundColor(.gray)
					)
					.contentShape(Rectangle())
					.onTapGesture {
						print("Show details for Enterprise")
						let vc = swiftvc()
						vc.challengerIsCalled()
					}
					allPlatformsView(self.controlPrice)
					.contentShape(Rectangle())
					.onTapGesture {
						print("Show details for all platforms")
						let vc = swiftvc()
						vc.controlIsCalled()
					}
					.background(Rectangle())
					.foregroundColor(Color.clear)
					.overlay(
						Rectangle()
							.strokeBorder(
								style: StrokeStyle(
									lineWidth: 2
								)
							)
							.foregroundColor(.gray)
					)
				}
			} .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
            .background(Color.clear)
		}
	}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
		ContentView(challengerPrice: 0, controlPrice: 0)
    }
}

struct phoneOnlyView: View {
	var price : Int = 0
	init(_ price : Int) {
		self.price = price
	}
	var body: some View {
		VStack(alignment: .center, spacing: 0) {
			Text("Phone Only")
				.font(.title)
				.foregroundColor(Color.black)
			Text(String(self.price)+String("/mo"))
				.font(.subheadline)
				.foregroundColor(Color.gray)
		}
		.padding(10)
		.frame(width: .none, height: 180, alignment: .top)
	}
}

struct allPlatformsView: View {
	var price : Int = 0
	init(_ price : Int) {
		self.price = price
	}
	var body: some View {
		VStack {
			Text("All platforms")
				.font(.title)
				.foregroundColor(Color.black)
			Text(String(self.price)+String("/mo"))
				.font(.subheadline)
				.foregroundColor(Color.gray)
		}
		.padding(10)
		.frame(width: .none, height: 180, alignment: .top)
	}
}

Add a swift file to your project. When you add this, Xcode will automatically ask you whether you would like to add a bridging file. Select YES.
In the below swift class, make sure to add the directive @objc to the methods, you want to expose in Objective-C.

If you don’t select YES, you might need to declare your own Objective-C/Swift interfaces, which is easy, but you will run into linker errors.
Pay close attention to this snippet of code. We are basically calling the objective-c classes. This is made possible by the bridging header, which I have explained below.

let vc : ViewController = ViewController()
vc.buyControl()

import Foundation
import UIKit
import SwiftUI

class swiftvc : NSObject {
	var priceFor499 = 0
	var priceFor999 = 0
	func returnClassInstance() -> Self {
		return self
	}
	@objc func printHello(controlPrice : Int, challengerPrice: Int) -> Void{
		self.priceFor499 = challengerPrice
		self.priceFor999 = controlPrice
		print("swiftvc printHello ", controlPrice, " challenger ", challengerPrice)
	}
// load SwiftUX
	@objc func loadSwiftUI() -> UIViewController {
		let ux = ContentView(challengerPrice: self.priceFor499, controlPrice: self.priceFor999)
		return UIHostingController(rootView: ux)
	}
	
	func challengerIsCalled() {
		// this will be accessible once viewcontroller.h is exposed in the bridging header file.
		let vc : ViewController = ViewController()
		vc.buyChallenger()
	}
	
	func controlIsCalled() {
		print("control is called")
		let vc : ViewController = ViewController()
		vc.buyControl()
	}
	@objc override init() {
		print("hello init in swiftvc")
	}
}

Next let’s create a view controller in Objective-C++. Not much difference here, just add the extension .mm to the file name. Pay special attention to this import statement import “Interoperability-Swift.h” This is needed to import your swift methods declared above.

You can work with types declared in Swift from within the Objective-C code in your project by importing an Xcode-generated header file. This file is an Objective-C header that declares the Swift interfaces in your target, and you can think of it as an umbrella header for your Swift code. You don’t need to do anything special to create the generated header—just import it to use its contents in your Objective-C code.

The header’s name is generated from your product module name, followed by "-Swift.h". By default, this name is the same as your product name, with any nonalphanumeric characters replaced with an underscore (_). If the name begins with a number, the first digit is replaced with an underscore.

#import "ViewController.h"
#import "interopability-Swift.h"
#include "HelloWorld.h"
#include "Hello.hpp"
using namespace HelloNS;
@interface ViewController ()

@end

@implementation ViewController
// .....
-(void)viewDidAppear:(BOOL)animated {
	
	Hello* hello = new Hello();
	int price = hello->sayHello();

	swiftvc* vc = [swiftvc new];
	[vc printHelloWithControlPrice:price challengerPrice:499];

	UIViewController* controller = [vc loadSwiftUI];
	[self presentViewController:controller animated:YES completion:^{
		NSLog(@"presented");
	}];
}

-(void) buyChallenger {
	NSLog(@"buychallenger");
}

-(void) buyControl {
	NSLog(@"buyControl");
}

@end
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
-(void) buyControl;
-(void) buyChallenger;
@end

Here comes, the C++ part, where I create a dummy class, to input certain values.


class Hello {
public:
	int sayHello();
};

using namespace HelloNS;
int Hello::sayHello() {
	printf("say hello");
	return 1; // return some random value
}

To expose your objective-c methods, in swift you need to import the header file in your bridging header file as shown below. This bridging header is automatically created for you.

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ViewController.h"

Github link is here: https://github.com/kmdarshan/interoperability

Leave a Reply

Your email address will not be published. Required fields are marked *