I am not experienced with IOS development but have some basic understanding to work my way through it by reading docs and tutorials.
I wanted to call Objective C code from Swift and it worked fine, now I want to do the opposite and getting confused a bit.
Basically I first call an Objective C function in the action of a Button in SwiftUI, then I want that function to update an ObservedObject in the same SwiftUI view and want the view to re-render.
I have found and followed a few resources on that, which are
https://medium.com/@iainbarclay/adding-swiftui-to-objective-c-apps-63abc3b26c33
https://pinkstone.co.uk/how-to-use-swift-classes-in-objective-c/
Swift UI view looks like
class Foo : ObservableObject {
@Published var bar = ""
}
struct ContentView: View {
@ObservedObject var baz = Foo();
// Then access later as self.baz.bar as a parameter somewhere..
What would be the right way to update bar
here ?
I did the correct build settings and added @objc
tags and also imported project_name-swift.h
.
Implemented and modified the example in
https://medium.com/@iainbarclay/adding-swiftui-to-objective-c-apps-63abc3b26c33 but got lost a bit because of my lack of experience in these environments.
Maybe somebody can push me in the right direction.
Thank you.
Let's assume my project name is Project
.
Example code : (A code very similar to this, compiles fine and the Objective C function calls, but on the swift side I get no output to console and the text doesn't render. I would really appreciate if you point my mistakes in this, since I get very rarely involved in iOS development.)
ContentView.swift
import Foundation
import SwiftUI
var objectivec_class = Objectivec_Class()
class Foo : ObservableObject {
@Published var bar = ""
}
@objc
class BridgingClass: NSObject {
@ObservedObject var baz = Foo();
@objc func updateString(_ content: NSMutableString) {
print("This function is called from Objective C")
self.baz.bar += content as String
}
}
struct ContentView: View {
/**
* This part seems fishy to me,
* It would have been better to inject the instance of Foo here in
* BridgingClass but, couldn't figure out how to.
* This is only for showing my intention.
*/
@ObservedObject var baz = Foo();
var body: some View {
Button(action: {
objectivec_class.updateSwiftUi()
})
{
Text(self.baz.bar)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Objective C Bridging Header,
Project-Bridging-Header.h
#import "Objectivec_Class.h"
Objectivec_Class.h
#ifndef Objectivec_Class_h
#define Objectivec_Class_h
#import <Foundation/Foundation.h>
#import "Project-Swift.h"
@interface Objectivec_Class : NSObject
@property (strong, nonatomic) NSMutableString* stringWhichWillBeRendered;
@property BridgingClass *bridgingClass;
- (id) init;
- (void) updateSwiftUi;
@end
#endif /* Objectivec_Class_h */
Objectivec_Class.m
#import <Foundation/Foundation.h>
#import "Project-Swift.h"
#import "Objectivec_Class.h"
@implementation Objectivec_Class
- (id)init{
if( self = [super init] ){
_stringWhichWillBeRendered = [NSMutableString stringWithString:@""];
BridgingClass *bridgingClass = [BridgingClass new];
}
return self;
}
- (void) updateSwiftUi {
NSString *thisWillBeRendered = @"Render this string.";
[_stringWhichWillBeRendered appendString:thisWillBeRendered];
[[self bridgingClass] updateString:_stringWhichWillBeRendered];
}
@end
Try the following
@objc
class BridgingClass: NSObject {
var baz = Foo() // here !!
...
and
struct ContentView: View {
@ObservedObject var baz = objectivec_class.bridgingClass.baz // << this !!
var body: some View {
Button(action: {
objectivec_class.updateSwiftUi()
})
{
Text(self.baz.bar)
}
}
}
Objectivec_Class.m
@implementation Objectivec_Class
- (id)init{
if( self = [super init] ){
_stringWhichWillBeRendered = [NSMutableString stringWithString:@""];
self.bridgingClass = [BridgingClass new]; // here !!
...
I would like to answer my own question because I would also like to share how I have achieved this with the help of Asperi.
As a side subject, I had to switch back to the legacy build system because of the cyclic dependency errors I was getting from Xcode. This also implies me that there should be a better way to do all this :)
With that said and with the assumption that you did the prerequisites of bridging between Swift <-> ObjC both ways,
ContentView.swift
import Foundation
import SwiftUI
var objectivec_class = Objectivec_Class()
class Foo : ObservableObject {
@Published var bar = ""
}
@objc
class BridgingClass: NSObject {
@ObservedObject var sharedObj = Foo()
@objc func updateString(_ content: NSMutableString) {
print("This function is called from Objective C (update String)")
sharedObj.bar += content as String
}
}
struct ContentView: View {
@State var stringToBeUpdated = ""
var body: some View {
Button(action: {
objectivec_class!.updateSwiftUi()
self.stringToBeUpdated = objectivec_class!.bridgingClass.sharedObj.bar
})
{
Text(self.stringToBeUpdated.isEmpty ? "tap me" : self.stringToBeUpdated)
}
.background(Color.green)
.frame(height: 100)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Objective C Bridging Header,
Project-Bridging-Header.h
#import "Objectivec_Class.h"
Objectivec_Class.h
#ifndef Objectivec_Class_h
#define Objectivec_Class_h
#import <Foundation/Foundation.h>
// #import "Project-Swift.h"
/** Forward declaring the class and not including the "Project-Swift.h" file
in this header is important if you are using Xcode's legacy build system */
@class BridgingClass;
@interface Objectivec_Class : NSObject
@property (strong, nonatomic) NSMutableString* stringWhichWillBeRendered;
@property BridgingClass *bridgingClass;
- (id) init;
- (void) updateSwiftUi;
@end
#endif /* Objectivec_Class_h */
Objectivec_Class.m
#import <Foundation/Foundation.h>
#import "Project-Swift.h"
#import "Objectivec_Class.h"
@implementation Objectivec_Class
- (id)init{
if( self = [super init] ){
_stringWhichWillBeRendered = [NSMutableString stringWithString:@""];
self.bridgingClass = [BridgingClass new];
}
return self;
}
- (void) updateSwiftUi {
// Probably you did something there to update the string.
NSString *thisWillBeRendered = @"New information appended to string";
[_stringWhichWillBeRendered appendString:thisWillBeRendered];
[[self bridgingClass] updateString:_stringWhichWillBeRendered];
}
@end
Any comments are welcome, newbie here ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With