Xcode 8 beta 6: main.swift won't compile - swift

We have a custom UIApplication object, so our main.swift was
import Foundation
import UIKit
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(MobileUIApplication), NSStringFromClass(AppDelegate))
and that didn't work in Xcode 8 beta 5 so we used this
//TODO Swift 3 workaround? https://forums.developer.apple.com/thread/46405
UIApplicationMain( Process.argc, UnsafeMutablePointer<UnsafeMutablePointer<CChar>>(Process.unsafeArgv), nil, NSStringFromClass(AppDelegate.self))
On Xcode 8 beta 6 we get Use of unresolved identifier 'Process'
What do we need to do in Xcode 8 beta 6/Swift 3 to define the UIApplicationMain?

I write it this way:
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv)
.bindMemory(
to: UnsafeMutablePointer<Int8>.self,
capacity: Int(CommandLine.argc)),
nil,
NSStringFromClass(AppDelegate.self)
)
To change the UIApplication class, substitute NSStringFromClass(MobileUIApplication.self) for nil in that formulation.
However, if your only purpose here is to substitute a UIApplication subclass as the shared application instance, there's an easier way: in the Info.plist, add the "Principal class" key and set its value to the string name of your UIApplication subclass, and mark your declaration of that subclass with an #objc(...) attribute giving it the same Objective-C name.
EDIT This problem is now solved in Swift 4.2. CommandLine.unsafeArgv now has the correct signature, and one can call UIApplicationMain easily:
UIApplicationMain(
CommandLine.argc, CommandLine.unsafeArgv,
nil, NSStringFromClass(AppDelegate.self)
)

It seems Process has been renamed to CommandLine in beta 6.
CommandLine
But the type of CommandLine.unsafeArgv is mismatching the second argument of UIApplication, so you may need to write something like this:
CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) {argv in
_ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(MobileUIApplication.self), NSStringFromClass(AppDelegate.self))
}
(UPDATE)This mismatching should be considered as a bug. Generally, you'd better send a bug report when you find "this-should-not-be" things, like the third parameter in beta 5. I hope this "bug" will be fixed soon.
If you just want to designate your custom UIApplication class, why don't you use Info.plist?
NSPrincipalClass | String | $(PRODUCT_MODULE_NAME).MobileUIApplication
(Shown as "Principal class" in non-Raw Keys/Values view.)
With this in your Info.plist, you can use your MobileUIApplication with normal way using #UIApplicationMain.
(ADDITION) Header doc of UIApplicationMain:
// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.

Related

Strange behavior of Obj-C Bridging Header and main function

I converted my app long ago from Obj-C to Swift. During conversion, I needed a Bridging-Header file.
Now I realized that this file is still defined for one target, although the project uses now only Swift files.
Thus I thought I can simply delete Target/Build Settings/Objective-C Bridging Header.
However, the project then no longer builds.
The bridging header file contains (for historical reasons, since I used earlier the Obj-C version of the StoreKit, but no longer) only a single entry:
#import <StoreKit/StoreKit.h>
If this entry is out commented, I get the error
Cannot find 'UIApplicationMain' in scope
in my main file that contains essentially only
let appDelegateClassName: String?
if !ProcessInfo.processInfo.isTesting {
// No unit test. Use the normal app delegate.
appDelegateClassName = NSStringFromClass(AppDelegate.self)
} else {
// Unit test. No app delegate is used.
appDelegateClassName = nil
}
let args = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, appDelegateClassName)
I assume, I don't need a Bridging-Header, if I don't have any Obj-C files. So why do I get this error when I simply out comment #import <StoreKit/StoreKit.h>? And how do I get rid of all old Obj-C traces?
UIApplicationMain is defined in the UIKit framework, so you need to add
import UIKit
to the main.swift file. With
#import <StoreKit/StoreKit.h>
in the bridging header file it happens to compile because StoreKit.h includes SKOverlay.h, which in turn includes UIKit.h (when compiled for iOS).

Undocumented API changes in CoreBluetooth

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

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.

How to make a Cocoa binding to a property of optional type in Swift 3?

I am exploring the use of Swift for a Mac Cocoa application - using Xcode 8.2.1, and it seems I hit a roadblock regarding Cocoa bindings.
In this toy Core Data document-based project, I added an NSArrayController in the StoryBoard. I need to bind it to a NSManagedObjectContext so that my user interface works by itself. I followed the solution outlined in Technical Q&A QA1871 (https://developer.apple.com/library/content/qa/qa1871/_index.html).
So I want to add a property of type ManagedObjectContext to my ViewController class.
I naturally declared it as an optional:
var moc: ManagedObjectContext?
But when I enter the property name in the bindings inspector of InterfaceBuilder, it complains: there is a red exclamation mark, and hovering over it pops up this error message:
“The Managed Object Context binding expects to be bound to an object of type NSObject, but moc is of type ManagedObjectContext?”
And it fails at run time too.
changing the type to ManagedObjectContext! doesn’t help: IB complains in exactly the same way.
changing the type to a non optional ManagedObjectContext silences the IB error, but now my ViewController class doesn’t compile anymore. The error I get is:
class ViewController has no initialiser
I completely understand this error message. And I can add an initialiser. But I get this new error message:
property ‘self.moc’ not initialised at super.init call.
I understand that one too, but what can I do? At initialiser-time, the managedObjectContext is not yet known. In Objective-C I would set it to nil, which I cannot do since the property is not an optional any more.
Do I really need to allocate a dummy sentinel ManagedObjectContext, just to make the compiler happy?
This would be ugly as hell, far worse than the nil value we use in Objective-C. Swift in that case, would not be safer, but less safe than Obj-C.
I find this idea repulsive. Or did I miss something?
Or is Swift fundamentally incompatible with this Apple-recommended Cocoa bindings pattern? That would be a pity too.
Note that an equivalent Objective-C implementation works just fine. In Objective-C, the property is declared as:
#property (nonatomic, strong) NSManagedObjectContext * _Nullable moc;
Edit: a zip archive containing two toy sample projects, one in Objective-C, the other in Swift 3, can be downloaded from http://dl.free.fr/pWkoKQLOc
Edit: here is where I set the viewController's managed object context:
class Document: NSPersistentDocument {
override func makeWindowControllers() {
let myMoc = self.managedObjectContext
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
let myViewController = windowController.contentViewController as! CarViewController
myViewController.moc = myMoc
self.addWindowController(windowController)
}
}
myViewController is created by storyboard, and I can only set its moc after it has been initialised. This in turns requires that the property be an optional or to have a dummy actual value to use as a sentinel value in replacement for nil

iOS 10 issue with storyboard custom view classes

Does anybody know why the ZoomingPDFViewer Apple Sample Code project no longer works? It was working prior to the iOS 10 release but now it keeps returning a unrecognized selector error when calling [PDFScrollView setPDFPage:].
It seems like the custom classes set in the storyboard are no longer being instantiated.
I had the same issue. It seems that the auto conversion to Swift 3 doesn't work well with IBs.
There are two options:
a) You can set the argument label--i.e.the parameter name to be shown externally--to _. For example,
#IBAction func setPDFPage(_ sender: AnyObject) {
// ...
}
b) You can remove the IBAction connection in the IB and re-set it. Notice instead of the previous setPDFPage(sender:) the connection will say setPDFPageWithSender(sender:) or something like that. (I didn't actually try it with the code in question, but writing from experience here.)
The same is true for segues.