Search code examples
swiftobjective-cuikitprotocols

Can't assign instance of a Swift class conforming to a protocol to an Objective-C property expecting that protocol


I'm trying to use a Swift class in a mostly Objective-C project. This revolves around UIContentConfiguration for use with UITableViewCell.

Here's my Swift code:

@objc class MyCellConfiguration: NSObject, UIContentConfiguration {
    let label: String

    @objc init(label: String) {
        self.label = label
    }

    func makeContentView() -> UIView & UIContentView {
        return MyCellContent(self)
    }

    func updated(for state: UIConfigurationState) -> Self {
        return self
    }
}

class MyCellContent: UIView, UIContentView {
    // Implementation of this class is not relevant to the question
}

When I try to setup the cell in some Objective-C code, I get a compiler warning. If I ignore the warning, the app hangs at runtime when the code is reached.

Here's the Objective-C code:

#import "MyApp-Swift.h" // The bridging header is being imported

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:key forIndexPath:indexPath];

MyCellConfiguration *config = [[MyCellConfiguration alloc] initWithLabel:@"Some Label"];
cell.contentConfiguration = config; // warning here

The compiler warning I receive is:

Assigning to 'id<UIContentConfiguration> _Nullable' from incompatible type 'MyCellConfiguration *__strong'

Why doesn't the compiler recognize that MyCellConfiguration conforms to the UIContentConfiguration protocol in the Objective-C code?

Swift code recognizes the conformance just fine. For example, the following compiles without issue:

let cell = UITableViewCell(style: .default, reuseIdentifier: "foo")
cell.contentConfiguration = MyCellConfiguration(label: "foo")

I'm using Xcode 15.0 on macOS 13.6.1.


Solution

  • Some observations:

    • The documentation page for UIContentConfiguration doesn't have an Objective-C version that you can choose using the "Language: Swift" selector. The Objective-C version is here.
    • UIContentConfiguration doesn't inherit NSObjectProtocol, like other Objective-C protocols do, when they are imported into Swift.
    • The Swift version of UIContentConfiguration has a makeContentView method that returns an intersection type, which cannot be represented in Objective-C. (The Objective-C version returns __kindof UIView<UIContentView> *)

    These all suggest that UIContentConfiguration in Swift is an entirely different protocol from its Objective-C version. Just conforming to it in Swift doesn't mean you conformed to it in Objective-C.

    You can also see a similar pattern in other related types like UIContentView and UIConfigurationState.

    In fact, try setting contentConfiguration to an instance of MyCellConfiguration in Swift and see what happens on the Objective-C's side. Try printing out [cell.contentConfiguration class] in Objective-C. The output is not MyCellConfiguration!

    All of that is to say, you have to re-implement the Objective-C version of UIContentConfiguration in Objective-C, or you can just add a method like this in Swift:

    @objc
    func addToCell(_ cell: UITableViewCell) {
        cell.contentConfiguration = self
    }
    
    UITableViewCell *cell = ...;
    MyCellConfiguration *config = [[MyCellConfiguration alloc] initWithLabel:@"Some Label"];
    [config addToCell:cell];