Reputation: 157
I would like to notify swift code whenever something changes or event triggered in my .cpp functions.
I've following structure in my app.
(.cpp)[.hpp] ->(.mm)[.h] -> .swift
I can handle changeThisString from swift code via :
let swiftString = CPPWrapper().MyMethodWrapper()
this is okay with button click / viewDidLoad but i would like to update this value whenever i set it up from c++.
If C++ passes new string to swift, it shouldn't wait the button click it should work like listener.
I'll be really glad for any kind of help, thank you.
Example:
my.cpp:
std::string changeThisString = "";
...
virtual void myCallBack(MyCallBackParam ¶mter){
changeThisString = "I WANT TO SEE THIS MESSAGE ON MY APP!"
}
std::string MyClass::MyMethod() {
return changeThisString;
}
my.hpp:
#include <string>
class MyClass{
public:
std::string MyMethod();
};
wrapper.mm
#import "wrapper.h"
#import "my.hpp"
@implementation CPPWrapper
MyClass myClass;
- (NSString*) MyMethodWrapper {
NSString* result = [NSString stringWithUTF8String:myClass.MyMethod().c_str()];
return result;
}
@end
Wrapper.h
#import <Foundation/Foundation.h>
@interface CPPWrapper : NSObject
-(NSString*) MyMethodWrapper;
@end
.swift
let swiftString = CPPWrapper().MyMethodWrapper()
Upvotes: 0
Views: 573
Reputation: 2737
This is an example of C callback that triggers a Combine notification.
( Moved on GitHub right here : https://github.com/moosefactory/C-callback-to-Swift )
This example changes a value of a C String in a C Library and displays it in a field in a SwiftUI view.
You don't need to go through Objective-C.
The first part is the C Library ( Few changes to make it work with C++ )
The second part is the C<->Swift class
I think it's nice to have an object that makes the bridge between your C code and your swift application, to remove esoteric syntax from the app code. In this example, the file MyCLibraryInterface
does the job.
This class is an observable object that will publish the value change using combine, so it goes a bit beyond the question - You can stop there and do what you want once you are in the callback block. Note that we can't catch the swift context in c calls ( no calls to self or variables declared on the heap )
C Library
#ifndef MyCLibrary_h
#define MyCLibrary_h
#include <stdio.h>
#include <dispatch/dispatch.h>
#include <stdlib.h>
/// The callback to call when the string is changed
typedef void callback_t(const char* string);
void setCallBack(callback_t _callback);
/// A function that will change the string
void setString(const char* string);
void startTimer(void);
void cancelTimer(void);
#endif /* MyCLibrary_h */
#include "MyCLibrary.h"
const char* myString;
dispatch_queue_t queue;
dispatch_source_t timer;
bool running;
callback_t* callback;
void setCallBack(callback_t _callback) {
callback = _callback;
}
void setString(const char* string) {
myString = string;
callback(myString);
}
/// A function that will start a timer that changes string
int ticks = 0;
void idle(dispatch_source_t timer)
{
ticks++;
char ticksStr[32];
sprintf(ticksStr, "Time : %ds", ticks);
setString(ticksStr);
}
void startTimer() {
if (running) { cancelTimer(); sleep(1); }
queue = dispatch_queue_create("timerQueue", 0);
// Create dispatch timer source
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_event_handler(timer, ^{idle(timer);});
dispatch_source_set_cancel_handler(timer, ^{
dispatch_release(timer);
dispatch_release(queue);
});
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0);
// Set timer
dispatch_source_set_timer(timer, start, NSEC_PER_SEC, 0);
ticks = 0;
running = true;
dispatch_resume(timer);
}
void cancelTimer() {
running = false;
dispatch_source_cancel(timer);
char ticksStr[32];
sprintf(ticksStr, "Canceled after %ds", ticks);
setString(ticksStr);
}
C<>Swift Part
#import "MyCLibrary.h"
import Foundation
class MyCLibraryInterface: ObservableObject {
@Published var string: String = "This is a string"
static let shared = MyCLibraryInterface()
init() {
setCallBack { stringPtr in
let newString = CFStringCreateWithCString(kCFAllocatorDefault, stringPtr, kCFStringEncodingASCII) ?? "" as CFString
DispatchQueue.main.async {
MyCLibraryInterface.shared.string = newString as String
}
}
}
func setLibString(string: String) {
string.withCString { stringPointer in
setString(stringPointer)
}
}
func startLibTimer() {
startTimer()
}
func cancelLibTimer() {
cancelTimer()
}
}
SwiftUI Sample
This sample app present the intial string and a button. On click or tap, the setString
function is called in the CLibrary, the swift callback is called and the view is updated following the ObservableObject
modification
import SwiftUI
struct ContentView: View {
@ObservedObject var myCLibInterface: MyCLibraryInterface = MyCLibraryInterface.shared
var body: some View {
VStack {
Text(myCLibInterface.string).frame(width:150).padding()
Button("Reset") {
myCLibInterface.setLibString(string: "C Timer Example")
}.padding()
Button("Start Timer") {
myCLibInterface.startLibTimer()
}.padding()
Button("Cancel") {
myCLibInterface.cancelLibTimer()
}.padding()
}.padding(20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Upvotes: 1