NSUndoManager casting NSUndoManagerProxy crash in Swift code - swift

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.

Related

Include an extension for a class only if iOS11 is available

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.

Get Around the "Initialization of immutable value 'variableName' was never used" Warning in Swift 3 Switch Statements

So I have some code that looks like this:
fileprivate func enumSwitchTime(_ optionalString: String?) {
if let nextEnumToSwitchOn = funcToGetEnum()
switch nextEnumToSwitchOn {
case .enumStateOne(let associatedEnumValue):
if let optionalString = optionalString {
//do stuff with both associatedEnumValue and optionalString
}
case .enumStateTwo...
.
.
.
}
}
However since Swift 3 (I'm using Xcode 8.1) I am getting the "Initialization of immutable value 'associatedEnumValue' was never used..." warning on the that first case.
I am still getting my toes wet in Swift, but so far I see no way around this. I can't do an underscore parameter in the case statement and then declare the associated enum value later, after the let optionalString = optionalString, or at least I haven't found any way of doing that.
It is just a warning, but I don't like warnings in my code. Any way around this?
Well, as it turns out this was an Xcode error/issue.
After cleaning my project and restarting Xcode multiple times, and the warning persisting, I posted the question here thinking I was missing something.
After Martin R said if the variable is used in any code path the warning shouldn't be there, I futzed around with the code, undid those changes, cleaned the project, restarted Xcode and FINALLY the warning went away.

After updating to Xcode 8 beta 6, lost CKErrorCode and .rawValue from associated enums

I had a switch statement in Xcode 7.3 that (after passing error: NSError as an argument) worked like so:
if let code:CKErrorCode = CKErrorCode(rawValue: error.code) {
switch code {
case .NotAuthenticated: etc...
}
}
After migrating to Swift 3.0 in Xcode 8 beta 6, I started getting errors saying that CKErrorCode is not recognized. I'm still importing cloud, and the migration lowercased my enums and I've tried to change it like so:
switch error.code {
case .notAuthenticated.rawValue: etc...
}
but that also seems to error (the beta seems to be buggy on my system so the errors keep disappearing and reappearing, so it's hard to tell as I code now and it may just be my system, but they persist when I compile). I've been scanning https://swift.org/migration-guide/#known-migration-issues and https://swift.org/migration-guide/ but haven't found anything yet and when I google CKErrorCode the documentation (which I'm assuming is ignoring Swift 3 since it's in beta) seems to say my previous syntax is kosher.
Can anyone point me in the right direction on this? Did enums lose .rawValue? Enums seemed to have changed, but I'm having difficulty finding documentation on what I should be doing. Is there an alternative to CKErrorCode that will recognize these enum cases? Please don't tell me cloud kit error handling changed more dramatically than that :) Thanks in advance.
Conversion to CKError from NSError as shown below:
let nsError: NSError = NSError() // NSError variable.
let cError = CKError(_nsError: nsError)
let cErrorCode = cError.code

Is there way for Swift code to detect if it's being run in a playground or not?

To be able to test GameKit features, I'd like to be able to detect if the code is being run in a Playground, so that it can just skip the network calls. Is there a way to do this?
edit:
Forget about GameKit--that just muddies the issue. There are many different scenarios where this would be useful. It's really just a simple question about whether a specific function call exists or not: is there a method that returns true if the code is running in a Playground?
I don't know of a documented way to do it, but there are some undocumented things you can do.
Here's a technique that works in both Xcode 7 with Swift 2.2 and Xcode 8 with Swift 3.0, in both macOS and iOS playgrounds: check for a bundle whose identifier starts with "com.apple.dt".
// Swift 2.2
if NSBundle.allBundles().contains({ ($0.bundleIdentifier ?? "").hasPrefix("com.apple.dt.") }) {
print("in playground")
} else {
print("not in playground")
}
// Swift 3.0
if Bundle.allBundles.contains(where: { ($0.bundleIdentifier ?? "").hasPrefix("com.apple.dt.") }) {
print("in playground")
} else {
print("not in playground")
}
Note that com.apple.dt. is not a prefix reserved specifically for a playground-related bundle; the dt part standards for something like “developer tool”. So there might be circumstances where you get a false positive: there's a bundle that matches but you're not in a playground. I'm not aware of any such circumstances, but there might be some. I did test an IBDesignable view in a macOS xib under Xcode 7, and it didn't have any com.apple.dt. bundles loaded.

Swift View Controller Downcasting

I thought the following would populate my home variable with my HomeViewController
var home = self.parentViewController!.parentViewController! as HomeViewController;
Instead I get the following compile error 'Use of undeclared type HomeViewController' although it definitely exists and appears in the auto complete popup.
Often XCode6 displays the wrong error message. Is this an instance variable? It may be that parentViewController isn't set at init time (the fact that it's an implicitly unwrapped optional strongly suggests this). If this is in a function, I'd do this in an if let statement to give us a better sense of what's going on. Something like:
if let homeVC = self.parentViewController?.parentViewController as? HomeViewController {
self.home = homeVC
}
This would give us at least a better opportunity to debug. Two !s in a row maybe means you're not being totally respectful of what those declarations are trying to tell you.
The cause of this error was actually coming from a bug in xcode 6 rather than any kind of syntax error. It was related to this: Xcode 6 Strange Bug: Unknown class in Interface Builder file
I was able to fix this by clearing the projects derived data and build and restarting my machine.
I had faced the same problem while doing an unwind segue and trying to downcast the sourceviewcontroller. I bang my head for more than 30mins and then I realized it was fairly simple and bit crazy, I had all my files except this viewcontroller added to the Tests target and after adding this viewcontroller to the Tests target, everything worked Tada!! I m saved.