I’m trying to use an Objective-C model via a SwiftUI code. This is a simple demo I made:
@protocol CarProtocol <NSObject>
@property (strong, readwrite) NSString * color;
- (void)changeColor;
@end
@protocol PersonProtocol <NSObject>
@property (strong, readwrite) NSString * name;
- (void)changeName;
@end
@interface Car : NSObject <CarProtocol>
@property (strong, readwrite) NSString * color;
- (instancetype)initWithColor:(NSString *)color;
- (void)changeColor;
@end
@implementation Car
- (instancetype)init {
if ((self = [super init])) {
self.color = @"";
}
return self;
}
- (instancetype)initWithColor:(NSString *)color {
if ((self = [super init])) {
self.color = color;
}
return self;
}
- (void)changeColor {
NSArray *colors = @[@"Red", @"Blue", @"Green", @"Yellow", @"Black", @"White"];
NSString *newColor = self.color;
while (self.color == nil || [newColor isEqualToString:self.color]) {
newColor = colors[arc4random_uniform((uint32_t)colors.count)];
}
self.color = newColor;
NSLog(@"The car color has been changed to %@", self.color);
}
@end
Then, I’m trying to access the Car
object via the CarProtocol
from Objective-C. My goal is to be able to modify the Car
instance properties from both Swift (say, from a Text Field) and from the ObjC code (say, by calling car.changeColor()
).
I’ve tried the following:
import SwiftUI
import Combine
extension Car: ObservableObject {
public var objectWillChange: AnyPublisher<Void, Never> {
publisher(for: .color, options: .prior)
.map { _ in }
.eraseToAnyPublisher()
}
public static func randomCar() -> Car {
let colors = ["Red", "Blue", "Green", "Yellow", "Purple", "Orange"]
return Car(color: colors.randomElement() ?? "Red")
}
}
@Observable
class ObservableCar<CarT>
where CarT: CarProtocol {
public var car: CarT
public init(_ car: CarT) {
self.car = car
}
}
struct SwiftObservableView<CarT>: View
where CarT: CarProtocol & ObservableObject {
@State var car: ObservableCar<CarT>
public init(car: CarT) {
self.car = ObservableCar(car)
}
var body: some View {
NavigationStack {
Form {
Section(header: Text("Modification Form")) {
VStack {
TextField("Color", text: $car.car.color)
}
}
Section(header: Text("Results")) {
VStack {
HStack() {
Text("Color")
Spacer()
Text(car.car.color)
}
}
}
Section(header: Text("Actions")) {
Button("Random Car") {
self.car = ObservableCar<CarT>(Car.randomCar() as! CarT)
}
Button("Assign from Objective-C") {
self.car.car.changeColor()
}
}
}
.navigationTitle("Car Data (Swift)")
}
}
}
#Preview {
SwiftObservableView(car: Car(color: "Green"))
}
I can see swift modifications reflects, but not the call to changeColor
. Do you have an idea on how to achieve the desired behavior using the new #Observable
macros introduced in WWDC23? I could achieve it with plain Combine, without the usage of protocols. However, as my code is protocol oriented, I need to rely on protocols.