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()
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 MyCLibraryInterfacedoes 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 )
The third part is a simple SwiftUI app that receive change and update interface
C Library
MyCLibrary.h
#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 */
MyCLibrary.c
#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
MyApp-Bridging-Header.h
#import "MyCLibrary.h"
MyCLibraryInterface.swift
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()
}
}
Related
I want to generate vibration when I press the button but I get no results.
Helper class I created to manage vibrations:
import Foundation
import UIKit
final class HapticsManager{
static let shared = HapticsManager()
private init(){}
public func selectionVibrate(){
DispatchQueue.main.async {
let selectionImpactGenerator = UIImpactFeedbackGenerator()
selectionImpactGenerator.prepare()
selectionImpactGenerator.impactOccurred()
}
}
public func haptic(for type: UIImpactFeedbackGenerator.FeedbackStyle){
DispatchQueue.main.async {
let notificationGenerator = UIImpactFeedbackGenerator()
notificationGenerator.prepare()
notificationGenerator.impactOccurred()
}
}
}
in ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
HapticsManager.shared.selectionVibrate()
addTargets()
setStartGradientView()
showLayout()
}
Function of button with click target added:
#objc fileprivate func setButtonClicked(){
HapticsManager.shared.haptic(for: .heavy)
}
I tried many methods but no result.
Thanks...
There is no problem about your code but needs some additional information. You need to check if device hardware is support for CHHapticEngine like that;
public func haptic(for type: UIImpactFeedbackGenerator.FeedbackStyle) {
if CHHapticEngine.capabilitiesForHardware().supportsHaptics {
let notificationGenerator = UIImpactFeedbackGenerator()
notificationGenerator.prepare()
notificationGenerator.impactOccurred()
} else {
AudioServicesPlaySystemSound(1520)
}
}
I've tried, without success, to use the new property wrapper #FocusedBinding.
The code example given here by a Frameworks Engineer, and placed below, during beta 1 period for iOS 14 and Big Sur compiles, but it doesn't seem to work for both OSs, for enabling the keyboard shortcuts.
Does anyone knows if something changed in the meantime, and how, or is something still under development?
// This example runs on macOS, iOS, and iPadOS.
//
// Big Sur Seed 1 has some known issues that prevent state-sharing between
// windows and the main menu, so this example doesn't currently behave as
// expected on macOS. Additionally, the Commands API is disabled on iOS in Seed
// 1. These issues will be addressed in future seeds.
//
// The Focused Value API is available on all platforms. The Commands and
// Keyboard Shortcut APIs are available on macOS, iOS, iPadOS, and
// tvOS—everywhere keyboard input is accepted.
#main
struct MessageApp : App {
var body: some Scene {
WindowGroup {
MessageView()
}
.commands {
MessageCommands()
}
}
}
struct MessageCommands : Commands {
// Try to observe a binding to the key window's `Message` model.
//
// In order for this to work, a view in the key window's focused view
// hierarchy (often the root view) needs to publish a binding using the
// `View.focusedValue(_:_:)` view modifier and the same `\.message` key
// path (anologous to a key path for an `Environment` value, defined
// below).
#FocusedBinding(\.message) var message: Message?
// FocusedBinding is a binding-specific convenience to provide direct
// access to a wrapped value.
//
// `FocusedValue` is a more general form of the property wrapper, designed
// to work with all value types, including bindings. The following is
// functionally equivalent, but places the burden of unwrapping the bound
// value on the client.
// #FocusedValue(\.message) var message: Binding<Message>?
var body: some Commands {
CommandMenu("Message") {
Button("Send", action: { message?.send() })
.keyboardShortcut("D") // Shift-Command-D
.disabled(message?.text.isEmpty ?? true)
}
}
}
struct MessageView : View {
#State var message = Message(text: "Hello, SwiftUI!")
var body: some View {
TextEditor(text: $message.text)
.focusedValue(\.message, $message)
.frame(idealWidth: 600, idealHeight: 400)
}
}
struct Message {
var text: String
// ...
mutating func send() {
print("Sending message: \(text)")
// ...
}
}
struct FocusedMessageKey : FocusedValueKey {
typealias Value = Binding<Message>
}
extension FocusedValues {
var message: FocusedMessageKey.Value? {
get { self[FocusedMessageKey.self] }
set { self[FocusedMessageKey.self] = newValue }
}
}
Basically - I run up against this a lot - I don't understand how you correctly do asynchronous initialisation in swift with callbacks. (with combine - I can do it). In particular - I have this code:
struct MyView : View {
#State var initialised : Bool = false
init()
{
var initialisedBinding = $initialised
Photos.PHPhotoLibrary.RequestAuthorization {
status in
if (status == Photos.PHAuthorizationStatus.authorized) {
print("here I am")
initialisedBinding.wrappedValue = true
initialisedBinding.update()
}
}
}
var body : some View {
VStack {
if (initialised) {
Text("yep")
} else {
Text("nope")
}
}
}
And when I run it - I get the print out - but the text never changes - it always remains "nope". What am I doing wrong, and how do I do it right? (Without using combine - I can do it with like a currentValueSubject and a .onreceive - but it's extra overhead, and I really want to know why the above code doesn't work - obviously I'm understanding something bad)
State is not ready in init yet, so you bound to nowhere. Moreover such activity in init is not good, because view can be created many times during rendering. The more appropriate place is .onAppear
struct MyView : View {
#State var initialised : Bool = false
var body : some View {
VStack {
if (initialised) {
Text("yep")
} else {
Text("nope")
}
}.onAppear {
Photos.PHPhotoLibrary.RequestAuthorization {
status in
if (status == Photos.PHAuthorizationStatus.authorized) {
print("here I am")
self.initialised = true
}
}
}
}
}
I've created a trivial project to try to understand this better. Code below.
I have a source of data (DataSource) which contains a #Published array of MyObject items. MyObject contains a single string. Pushing a button on the UI causes one of the MyObject instances to update immediately, plus sets off a timer to update a second one a few seconds later.
If MyObject is a struct, everything works as I imagine it should. But if MyObject is a class, then the refresh doesn't fire.
My expectation is that changing a struct's value causes an altered instance to be placed in the array, setting off the chain of updates. However, if MyObject is a class then changing the string within a reference type leaves the same instance in the array. Array doesn't realise there has been a change so doesn't mention this to my DataSource. No UI update happens.
So the question is – what needs to be done to cause the UI to update when the MyObject class's property changes? I've attempted to make MyObject an ObservableObject and throw in some didchange.send() instructions but all without success (I believe these are redundant now in any case).
Could anyone tell me if this is possible, and how the code below should be altered to enable this? And if anyone is tempted to ask why I don't just use a struct, the reason is because in my actual project I have already tried doing this. However I am using collections of data types which modify themselves in closures (parallel processing of each item in the collection) and other hoops to jump through. I tried re-writing them as structs but ran in to so many challenges.
import Foundation
import SwiftUI
struct ContentView: View
{
#ObservedObject var source = DataSource()
var body: some View
{
VStack
{
ForEach(0..<5)
{i in
HelloView(displayedString: self.source.results[i].label)
}
Button(action: {self.source.change()})
{
Text("Change me")
}
}
}
}
struct HelloView: View
{
var displayedString: String
var body: some View
{
Text("\(displayedString)")
}
}
class MyObject // Works if declared as a Struct
{
init(label: String)
{
self.label = label
}
var label: String
}
class DataSource: ObservableObject
{
#Published var results = [MyObject](repeating: MyObject(label: "test"), count: 5)
func change()
{
print("I've changed")
results[3].label = "sooner"
_ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: {_ in self.results[1].label = "Or later"})
}
}
struct ContentView_Previews: PreviewProvider
{
static var previews: some View
{
ContentView()
}
}
When MyObject is a class type the results contains references, so when you change property of any instance inside results the reference of that instance is not changed, so results is not changed, so nothing published and UI is not updated.
In such case the solution is to force publish explicitly when you perform any change of internal model
class DataSource: ObservableObject
{
#Published var results = [MyObject](repeating: MyObject(label: "test"), count: 5)
func change()
{
print("I've changed")
results[3].label = "sooner"
self.objectWillChange.send() // << here !!
_ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) {[weak self] _ in
self?.results[1].label = "Or later"
self?.objectWillChange.send() // << here !!
}
}
}
I'm making a new Swift (3.0) framenwork which involves the use of a NSSearchField programmatically created, like the rest of the views.
This framenwork has the deployment target set to 10.10 and I found strange being not able to set the delegate metods for it. In fact NSSearchFieldDelegate only appear availabe in 10.11 on.
class PlistEditor: NSObject, NSOutlineViewDelegate, NSOutlineViewDataSource, NSTextFieldDelegate, NSSearchFieldDelegate {
var searchField : NSSearchField?
// more code..
init(tabItem: NSTabViewItem,
plistPath: String?) {
// more code..
let sf = NSSearchField(frame: NSMakeRect(80, 0, 80, 22))
self.searchField? = sf
// more code..
super.init()
// more code..
if #available(OSX 10.11, *) {
self.searchField?.delegate = self
} else {
// Fallback on earlier versions
}
// more code
}
}
Ok, I thought it was inherent from NStextField, and maybe I can access the cell and set up using the superclass delegate, but unfurtunately I cannot found a way to do that.
What I need is to be able to receive NSText/NSTextField notification in 10.10. How can I do this?
EDITED:
added more info on how is made plus some picts
Without providing more info, i am guessing you forgot to declare that your class is conforming to the NSSearchFieldDelegate.
See the example below, how to set it up for example a viewController. You just create an extension for your vc, and declare it to conform to the delegate.
class SearchViewController: NSViewController {
let searchField: NSSearchField? = NSSearchField(frame: .zero)
override func viewWillAppear() {
super.viewWillAppear()
if #available(OSX 10.11, *) {
self.searchField?.delegate = self
} else {
// Fallback on earlier versions
}
}
}
extension SearchViewController: NSSearchFieldDelegate {
func searchFieldDidStartSearching(_ sender: NSSearchField) {
print("didStart")
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
print("didEnd")
}
}
EDIT:
To capture the textDidChange event in earlier versions of Mac OS, you need to subclass NSSearchField and override textDidChange function. Every time a change happens in the searchField, it will call the function for you.
class DLSearchField: NSSearchField {
override func textDidChange(_ notification: Notification) {
Swift.print("textDidChange")
}
}