Include an extension for a class only if iOS11 is available

Makaronodentro picture Makaronodentro · Jul 7, 2017 · Viewed 8.9k times · Source

I am trying to extend a class written in Obj-C and include an extension written in Swift that makes it conform to the UIDropInteractionDelegate, like so:

@available(iOS 11.0, *)
extension NoteEditViewController: UIDropInteractionDelegate {
    @available(iOS 11.0, *)
    public func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
        let operation: UIDropOperation
        if session.localDragSession == nil {
            operation = .forbidden
        } else {
            // If a local drag session exists, we only want to move an
            // existing item in the pin board to a different location.
            operation = .forbidden
        }
        return UIDropProposal(operation: operation)
    }

    @objc(setupDropInteractions)
    @available(iOS 11.0, *)
    func setupDropInteractions() {
        // Add drop interaction
        self.view.addInteraction(UIDropInteraction(delegate: self))
    }
}

My problem is that Project_Name-Swift.h file contains the following code that will not compile:

@class UIDropInteraction;
@protocol UIDropSession;
@class UIDropProposal;

// This line is causing the issue saying "'UIDropInteractionDelegate' is partial: introduced in iOS 11.0"
@interface NoteEditViewController (SWIFT_EXTENSION(Bloomberg_Professional)) <UIDropInteractionDelegate>
- (UIDropProposal * _Nonnull)dropInteraction:(UIDropInteraction * _Nonnull)interaction sessionDidUpdate:(id <UIDropSession> _Nonnull)session SWIFT_WARN_UNUSED_RESULT SWIFT_AVAILABILITY(ios,introduced=11.0);
- (void)setupDropInteractions SWIFT_AVAILABILITY(ios,introduced=11.0);
@end

The compiler is complaining that the interface in that file is partial.

'UIDropInteractionDelegate' is partial: introduced in iOS 11.0

I assumed that including the @available(iOS 11.0, *) would generate a SWIFT_AVAILABILITY(ios,introduced=11.0) that would encapsulate the entire interface but I was wrong.

Is there a way to fix this?

UPDATE


I implemented a toy example.

Here is the toy ViewController:

enter image description here

Here is the swift extension:

enter image description here

And here is the generated dnd_toy-Swift.h file.

enter image description here

You were right that it is simply a warning but my problem is that we must treat all warnings as errors in our project.

Any ideas on how to get rid of this warning from here?

Answer

allenh picture allenh · Jul 10, 2017

You've done everything correctly, and the complaint is accurate if not extremely obtuse.

Basically, since you've (correctly) marked that function as available in iOS 11, the compiler will warn you about the use of it in any code that is targeting something < iOS 11.

So you can make the complaint go away by setting your deployment target to iOS 11, which means users of iOS 10 simply won't be allowed to install it. Or, you can use the new (to obj-c) @available construct to guard against the use of the API.

if (@available(iOS 11, *)) {
    [self setupDropInteractions];
}

This construct isn't supported by Xcode 8 since it's a recent back port of the #available construct provided by Swift.

Update

I'm clarifying where I'm coming from. It seems I haven't been able to reproduce the situation that the asker is experiencing, so I'm demonstrating what I have been able to do.

I can produce 2 different compiler warnings that are similar but it seems not identical to the original question.

Here is my generated objc interface from my_project-Swift.h:

@interface NoteEditViewController (SWIFT_EXTENSION(conditional_class_declaration)) <UIDropInteractionDelegate>
    - (UIDropProposal * _Nonnull)dropInteraction:(UIDropInteraction * _Nonnull)interaction sessionDidUpdate:(id <UIDropSession> _Nonnull)session SWIFT_WARN_UNUSED_RESULT SWIFT_AVAILABILITY(ios,introduced=11.0);
    - (void)setupDropInteractions SWIFT_AVAILABILITY(ios,introduced=11.0);
@end

Issue 1: Declaring and objc property to conform to the protocol

enter image description here

Issue 2: Using a method declared in the extension

enter image description here

With more information to reproduce the compilation error, I'll be able to help more.

Update 2: Hopefully the last

I found that the discrepancies in behavior between us was due to the fact that my project was created with Xcode 8.3 then migrated to 9. There seems to be a difference in build settings after these things happen. The setting in question is CLANG_WARN_UNGUARDED_AVAILABILITY, which I think is new for Xcode 9.

During migration the project ends up like this:

  • This maps to the term YES in the .pbxproject file enter image description here

After new project creation:

  • This maps to the term YES_AGGRESSIVE is the .pbxproject file enter image description here

This setting is discussed in WWDC2017 - What's new in LLVM, but nothing they say would suggest this minor behavioral difference. I'm guessing that it is a bug with clang and how it's handling the difference between the two settings (but I'd welcome other input). As I'm sure you've figured out, this code runs fine on iOS 10. Also, if you change the setting to simply "Yes", you will still get the correct warnings about iOS 11 APIs.