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:
Here is the swift extension:
And here is the generated dnd_toy-Swift.h file.
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?
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
Issue 2: Using a method declared in the extension
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
After new project creation:
This maps to the term YES_AGGRESSIVE is the .pbxproject file
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.
In ObjC, adding API_AVAILABLE(ios(11.0)) to end of the function definition will suppress the warning. Like this:
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0))
{
... function implementation ...
}
Xcode 9 beta 4 will build, if #available is added to each method. The build still fails if #available is added only at the extension level.
Related
In Xcode 12.3, CoreBluetooth.CBService.peripheral is defined in objective-c as:
#property(assign, readonly, nonatomic) CBPeripheral *peripheral;
Update: Here's the swift translation of the above in Xcode 12.3:
unowned(unsafe) open var peripheral: CBPeripheral { get }
In Xcode 13.0, CBService.peripheral is defined in swift as:
weak var peripheral: CBPeripheral? { get }
Apple's documentation states that this API has existing since iOS5 and there have been no changes. However in Xcode 13, the variable is clearly an optional. (And it is not an optional in Xcode 12.3 as it is missing the attribute nullable.)
The fix is relatively easy (e.g. service.peripheral -> service?.peripheral) - but it makes it impossible to use the same code for both Xcode 12.3 and 13.0. I'm wondering if there is some nuance here that that I'm missing?
Optionals are an inherent part of Swift and they are not part of Objective-C. A non-null reference in Swift in guaranteed to have a value while any reference in Objective C could, in theory, be null.
The nullable and nonnull decorators were introduced to improve interoperability with Swift with the side effect that they also more clearly document Objective C APIs.
Core Bluetooth is an Objective C API and, as you note, it has been available since iOS5; Well before Swift and the nullable decorator.
So, it isn't so much that the API has changed, but rather you are comparing the Objective C API with the Swift API and Apple has not added the nullable decorator to the Core Bluetooth API.
Neither of these APIs have changed; The Swift declaration for peripheral on CBService has always been an optional. The Objective-C declaration has never had nullable but a null value has always been possible.
Adding nullable wouldn't change the behaviour of the API, just its declaration in Objective C. This would potentially be a breaking change for Objective-C code, or at least could cause compilation to fail, so there is no reason for Apple to change it and a good reason not to.
Update
From your comment, there does, indeed, appear to have been a breaking change in the Swift declaration of CBService.peripheral from unowned(unsafe) to a weak optional.
This is a much safer declaration, since in the previous definition the onus was on you to check that peripheral wasn't nil and there was a potential crash if you didn't.
In my many years of programming with Core Bluetooth, I don't think I have ever needed to use CBService.peripheral, however, you can use conditional compilation based on the Swift version to write code that works in both Xcode 13 and earlier versions:
var p: CBPeripheral?
#if swift(<5.5)
if s.peripheral != nil {
p = s.peripheral
}
#else
p = s.peripheral
#endif
It's not a big problem, but I was a bit confused when I faced it for the first time.
This was the original declaration for an Obj C delegate method:
- (void)serialPortWasRemovedFromSystem:(ORSSerialPort *)serialPort
And when I translated it in swift it became:
func serialPortWasRemovedFromSystem(_ serialPort: ORSSerialPort)
But later Xcode showed an error and suggested me to change the name, because it was deprecated, in this one:
func serialPortWasRemoved(fromSystem serialPort: ORSSerialPort)
Why did they change this delegate name so many times? Can you tell me why? Thank you! ~
Because that, in large part, is what Swift 3 is. The Objective-C APIs are "renamified" to make their names terser and more Swift-like.
To learn more, read this and the other two documents to which it links.
In our application we're using the following code:
let lInvocationTarget = lUndoManager.prepare(withInvocationTarget: self)
let _ = (lInvocationTarget as! MyObjectType).myMethod(_: self.opacity, undoManager: lUndoManager)
This compiles without warnings and runs fine under macOS 10.12 Sierra. However it crashes at runtime on 10.9 - 10.11 (Mavericks till El Capitan).
The crash report notices:
Could not cast value of type 'NSUndoManagerProxy' (0x7fff76d6d9e8) to 'MyObjectType' (0x108b82218).
Then I rewrote the code to:
if let lInvocationTarget = lUndoManager.prepare(withInvocationTarget: self) as? MyObjectType {
let _ = lInvocationTarget.setOpacity(_: self.opacity, undoManager: lUndoManager)
}
Then it doesn't crash, but then Undo is not working at all.
This last way of writing comes directly out of Apples documentation, so apparently behaviour changed in Swift 3 or 10.12 SDK. I'm using Xcode 8.2 with Swift 3 and SDK 10.12
The registerUndo(withTarget, selector:, object:) is not suitable because I have a lot of other undoable methods with more arguments. Really don't want to wrap those in a dictionary orso. And even when selectors are pretty type safe nowadays, I still don't like them.
There is also the block based API (registerUndo(withTarget: handler:) ), but that is unfortunately only for 10.11+.
Anyone encountered the same problem? And more important: anyone come up with a way out?
I am so much astonished that I hear your first code works on macOS 10.12. The method prepare(withInvocationTarget:) has been a this-hardly-works-in-Swift thing. Not only the returned proxy object is not the instance of the original class, but neither the object is not a descendent of NSObject (at lease in some former OS Xs).
Anyway, this is one thing worth trying:
let lInvocationTarget = lUndoManager.prepare(withInvocationTarget: self)
_ = (lInvocationTarget as AnyObject).myMethod(self.opacity, undoManager: lUndoManager)
I found registerUndoWithTarget:handler: is a better API now.
I can't use WKInterfaceSlider without having exc_bad_access error.
I'm using this code:
#IBAction func sliderChanged(value:AnyObject) {
println(value)
}
Sometimes it works for the first click on +/-
Update:
WKInterfaceSlider works using Objective-C.
See XCode 6.2 Beta release notes: "Known Issues - XCode Interface Builder"
"After creating IB Actions in Swift for WKInterfaceSwitch or WKInterfaceSlider objects,
storyboards fail to compile with an error such as "Argument to 'IBAction' method cannot have
non-object type." (19003052"
Solution: remove "#IBAction" annotation (which implies #objc). Should still work ok.
This one has me stumped. I'm writing an iPhone app that tracks bus schedules. Users can bookmark their favorite bus stops so they can jump directly to them from the home screen. I manage the list of favorites in my AppDelegate class (unrelated code has been redacted):
#interface AppDelegate : NSObject <UIApplicationDelegate>
+ (BOOL) isInFavorites: (FavoriteStopData*) inStop;
#end
I have a view controller that presents the list of stops for a given bus route and lets users select one to see predicted bus arrival times for that stop in a new view (and maybe add the stop to their list of favorites):
#implementation RouteStopsViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
FavoriteStopData *stopData = [[FavoriteStopData alloc] init];
// ... set various properties in stopData from data in the selected cell
FavoriteStopViewController *fvc = [[FavoriteStopViewController alloc] initWithNibName:#"FavoriteStopViewController" bundle:nil];
fvc.stop = stopData;
fvc.isBookmarked = [AppDelegate isInFavorites:stopData];
[stopData release];
[self.navigationController pushViewController:fvc animated:YES];
[fvc release];
}
#end
The line
fvc.isBookmarked = [AppDelegate isInFavorites:stopData];
gets two warnings:
"'AppDelegate' may not respond to +isInFavorites:"
"Passing argument 1 of 'setIsBookmarked' makes integer from pointer without a cast"
I can't see any reason for Xcode to think '+isInFavorites:' is undefined, yet it does. I've verified that these possible causes for the warning are not in fact the case:
'+isInFavorites:' is declared in "AppDelegate.h" (as shown above)
"RouteStopsViewController.m" does #import "AppDelegate.h" (and "FavoriteStopData.h" and "FavoriteStopViewController.h")
'isBookmarked' is a public BOOL property on FavoriteStopViewController
The code is not being munged by some #define macro; when I preprocess "RouteStopsViewController.m", this code is unchanged.
The code behaves correctly, but I REALLY don't want to live with a warning that I must ignore every time the code compiles, and disabling this warning with some #pragma is a road I'd rather not take unless I have to.
I've tried renaming the method name, the variable names, using the method to set a local BOOL variable and then setting the property with that, using a conditional operator (x ? y : z) to make sure I'm passing a BOOL to the property ... nothing works. That first warning never goes away.
Can anyone suggest why Xcode is giving me this warning?
This is with Xcode 4.2 (Build 4C199) and iOS 5 SDK running in the 5.0 iPhone Simulator on a MacBook Pro running Mac OS X 10.6.8 (Snow Leopard).
If the +isInFavorites: method was completely unknown to the compiler then you'd see a warning like +isInFavorites: not found (return type defaults to 'id').
If you're not seeing that warning then we can assume that the compiler has seen a declaration of that method somewhere. However, the compiler expects this method to return a pointer rather than a BOOL, which is why you're seeing the makes integer from pointer without a cast warning.
Check for any other declarations of an isInFavorites: method in your project. Check for any global variables named AppDelegate that may conflict with your class name. Check for any circular imports between AppDelegate.h and RouteStopsViewController.h. Try renaming your AppDelegate class.
declare your isInFavorites method in your appdelegate.h file.
'AppDelegate' may not respond to +isInFavorites:
Passing argument 1 of 'setIsBookmarked' makes integer from pointer without a cast
The first error causes the second. The compiler's confusion on the existence of +isInFavorites: causes the compiler to assume the return type is id
This assumption causes the warning of making integer from pointer without a cast
You really have to focus on the first warning.
Are these the only warnings?
Try changing your AppDelegate.h to
#class FavoriteStopData
#interface AppDelegate : NSObject <UIApplicationDelegate>
+ (BOOL) isInFavorites: (FavoriteStopData*) inStop;
#end
If you still have issues, you might want to consider making this an instance method instead, considering the appDelegate is valid the whole runtime of your app
Found it. Even makes perfect sense ... now that I know exactly where to look.
My project has two targets: Dev for the version I use to test new code, and App for the user-facing version. Each has its own AppDelegate class (and some other duplicates). Code specific to one target or the other goes into either the ./Dev/ or the ./App/ folder. Common code goes into other folders.
Recently I promoted one Dev-specific class to be used in both targets ... but hadn't yet moved the files out of the Dev folder. This was my problematic RouteStopsViewController. My project was compiling the right "AppDelegate.m", but Xcode was finding the 'wrong' (to my thinking) "AppDelegate.h" because it was looking first in the same folder as "RouteStopsViewController.m".
The fix was easy: move RouteStopsViewController out of the Dev-specific folder into one for code shared by both targets. Now Xcode uses the "AppDelegate.m" file it's compiling to find the matching "AppDelegate.h".
I knew at the time I should move that RouteStopsViewController class when I decided to reuse it in the App target, I just didn't get around to it. When it comes to writing code, trust your nose. If it smells funny, it very probably is.