NSAttributedString swift extension is not accessable in Objective C [duplicate] - objective-c

I just finished upgrading a mixed language project (objective-c and Swift) from Swift 3 to Swift 4.
Everything seemed to go well except all of my Swift extensions are no longer accessible in objective-c. I can't figure out how to get any Swift extension to show up in objective-c. I've tried searching, but I can't find any mention of changes to extensions in Swift 4 except for loosening up the private scope.
All of these extensions were accessible from Objective-c in Swift 3, so there are no incompatible types (structs or non-raw enums).
The extensions are marked public.
The extensions are part of the same target and in the same project as the objective-c files.
Yes, I have imported "ProjectName-Swift.h" in the relevant objective-c files.
Other compatible Swift classes do show up. It seems just the extensions are missing.
I've tried marking each func public as well.
For example, the following extension used to be available to objective-c code when I was using Swift 3:
public extension UIAlertController {
class func alert(_ title: String? = nil, message: String? = nil) -> UIAlertController {
return UIAlertController(title: title, message: message, preferredStyle: .alert)
}
#discardableResult func action(_ action: String) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .default, handler: nil))
return self
}
#discardableResult func action(_ action: String, style: UIAlertActionStyle = .default, onSelected: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: style, handler: onSelected))
return self
}
#discardableResult func cancel(_ action: String? = "Cancel", onCancel: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .cancel, handler: { alert in
onCancel?(alert)
}))
return self
}
#discardableResult func destructive(_ action: String, onDestruct: #escaping ((UIAlertAction) -> Void)) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .destructive, handler: { action in
onDestruct(action)
}))
return self
}
func presentOn(_ viewController: UIViewController, animated: Bool = true) {
viewController.present(self, animated: animated, completion: nil)
}
}

A super simple solution is to just add #objc public extension to the declaration of each Swift extension.

Related

Swift and #objc methods: How do I transform a method so that it can be represented by #objc?

As Swift is my first programming language and also seeing that I have no Objective C experience...
I'm having difficulty understanding #objc in relation to methods.
How do I use the #objc syntax to conform to my methods?
Is there another way to select a method without using the #selector syntax?
Here is the code that I'm having difficulty with(mainly the #objc attempt at the startGame method):
import UIKit
#objc class ViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .add, target: self, action:
#selector(promptForAnswer))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New
Word", style: .plain, target: self, action: #selector(startGame))
if let startWordsURL = Bundle.main.url(forResource: "start",
withExtension: "txt") {
if let startWords = try? String(contentsOf: startWordsURL) {
allWords = startWords.components(separatedBy: "\n")
}
}
if allWords.isEmpty {
allWords = ["silkworm"]
}
#objc func startGame() {
title = allWords.randomElement()
usedWords.removeAll(keepingCapacity: true)
tableView.reloadData()
{
startGame()
}
A few observations:
You do not need #objc in your view controller declaration.
The two action/selector methods should bear #objc qualifier.
I would suggest that you give these two methods descriptive names that clearly indicate that they are called when the user taps on a particular button, e.g.:
#objc func didTapNewWord(_ sender: UIBarButtonItem) {
...
}
#objc func didTapAdd(_ sender: UIBarButtonItem) {
...
}
Note, I also added a parameter to these methods. That makes it entirely unambiguous that they are button handlers. You do not need to do that, but now you can glance at the code and immediately grok what the method is for.
Obviously, you will change the code that adds these target actions accordingly:
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
target: self,
action: #selector(didTapAdd(_:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New Word",
style: .plain,
target: self,
action: #selector(didTapNewWord(_:)))
Be careful with the placement of braces. Swift allows you to declare functions inside functions. So make sure that these selector methods are instance methods of the view controller, and not, for example, private functions declared inside another function (i.e. viewDidLoad).
If you start to lose track of the braces, you can select all the code in this file and press control+i (or in Xcode menus, “Editor” » “Structure” » “Re-Indent”). If you have missing braces somewhere, the re-indentation of the code will make this jump out at you.
So pulling that together, you get something like:
// ViewController.swift
import UIKit
class ViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()
override func viewDidLoad() {
super.viewDidLoad()
configureButtons()
fetchData()
}
}
// MARK: - Actions
extension ViewController {
#objc func didTapNewWord(_ sender: UIBarButtonItem) {
startGame()
}
#objc func didTapAdd(_ sender: UIBarButtonItem) {
...
}
}
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
}
}
// MARK: - Private utility methods
private extension ViewController {
func configureButtons() {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
target: self,
action: #selector(didTapAdd(_:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New Word",
style: .plain,
target: self,
action: #selector(didTapNewWord(_:)))
}
func fetchData() {
guard
let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt"),
let startWords = try? String(contentsOf: startWordsURL).components(separatedBy: "\n"),
!startWords.isEmpty
else {
allWords = ["silkworm"]
return
}
allWords = startWords.filter { !$0.isEmpty }
}
func startGame() {
title = allWords.randomElement()
usedWords.removeAll(keepingCapacity: true)
tableView.reloadData()
}
}
A few final observations on my code sample (not directly related to your question, but just to explain why structured it like I did):
I like to put methods into extensions, so that they are in logical groups. This makes it easier to follow what is going on at a glance. You can also collapse/expand these extensions so that while you are editing, you can focus on the relevant code.
The MARK comments just puts nice section headers in the Xcode jump bar, again, making it easier to jump about in one’s code.
I personally don't put anything in the action methods except a call to some method with the “business logic”. This separates the “view” code (the handling of the button) from the business logic. Some day, you may start using view models or presenter objects, so embracing this separation of responsibilities now will make that eventual transition easier. It will also make it easier to write unit tests when you get around to that (e.g. you write unit tests for the "start game" logic, not not the tapping of a button).
I think you have syntax error in the #objc method. It should be:
#objc
func functionName() {
}
for you it will be:
#objc
func startGame() {
title = allWords.randomElement()
usedWords.removeAll(keepingCapacity: true)
tableView.reloadData()
}

Swift to Objective-C call with trailing closure calls wrong method

Given the following Objective-C methods
#import Foundation;
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
// Variant 1. Notice that arg1 is nullable, and that all closure arguments are nullable.
+ (void)test:(NSString *_Nullable)arg1
action:(nullable void (^)(void))action;
// Variant 2. Notice that arg2 is non null, there is an additional, nullable, closure argument
+ (void)test:(NSString *)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2;
#end
NS_ASSUME_NONNULL_END
I find that when I attempt to call the first method from Swift with fully specified args, it actually calls the second variant when I use a trailing closure
// Calls first variant (This is an erroneous claim, hence Matt's answer)
TestClass.test("arg1", action: {})
// Calls second variant
TestClass.test("arg2") {}
I was expecting Variant 1 to be called in both cases. I'm unclear if I'm doing something wrong or not. I also seem to have missed the fact that Swift could provide generate arguments at all when calling Obj-C methods and am struggling to find the relevant documentation on it.
If I replace the Obj-C TestClass with the equivalent
class TestClass {
class func test(_ arg1: String?, action: (() -> ())? = nil) {
}
class func test(_ arg1: String!, action: (() -> ())? = nil, action2: (() -> ())? = nil) {
// Should not get here
assert(false)
}
}
Then I get a compiler warning about ambiguous use of 'test' in both calls.
Tested on Xcode 12.3 and 12.4.
Obviously, I expect Variant 1 to be called in both cases.
Actually, I find that Variant 2 is called in both cases.
First generate the Swift interface for your Objective-C interface. You get this:
open class func test(_ arg1: String?, action: (() -> Void)? = nil)
open class func test(_ title: String, action: (() -> Void)?, action2: (() -> Void)? = nil)
We have now eliminated the Objective-C component from the story and can just use these methods in our testing:
typealias VoidVoid = () -> Void
func test(_ arg1: String?, action: VoidVoid? = nil) { print("1") }
func test(_ title: String, action: VoidVoid?, action2: VoidVoid? = nil) { print("2") }
func f() {
test("arg1", action: {})
test("arg2") {}
}
If we call f(), the console prints "2" twice. I don't get any compile error, but it does appear that the first variant is unreachable unless you omit both function arguments.
In matt’s answer, he shared his experience where the compiler would resolve both Swift calls to the second Objective-C variant of the method (the one with the action2 parameter). We should note that this behavior is unique to the fact that the two methods have a different nullability for the first argument, being an nullable in the first variant, and not nullable in the second variant.
Switch the nullability of the first parameter of the two methods, and the behavior changes, favoring the first variant. Likewise, if both of the renditions use the same nullability, then, again, the first variant will be used. Consider:
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action;
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2;
#end
NS_ASSUME_NONNULL_END
That this translates to the following Swift interface:
open class TestClass : NSObject {
open class func test(_ title: String?, action: (() -> Void)? = nil)
open class func test(_ title: String?, action: (() -> Void)?, action2: (() -> Void)? = nil)
}
Now, both Swift calls will call the first variant, not the second:
TestClass.test("foo", action: {}) // variant 1
TestClass.test("foo") {} // variant 1
In your example (where the first variant made the first argument nullable, but the second variant did not), it is resolving to the second variant because we passed a non-optional string.
Given that (hopefully) the first variant is just an Objective-C convenience method to the second variant, it probably does not matter which is called, but the moral of the story is that the resolution to the appropriate Objective-C method is subject to very subtle considerations that may not be obvious at a glance.
At the risk of premature optimization, if you really are concerned about ensuring that Swift always call the second Objective-C variant regardless of the nullability/optionality of the first parameter in these two variations, one could make the intent explicit with NS_REFINED_FOR_SWIFT as discussed in Improving Objective-C API Declarations for Swift. For example:
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action NS_REFINED_FOR_SWIFT;
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2 NS_REFINED_FOR_SWIFT;
#end
NS_ASSUME_NONNULL_END
And then manually declare your own explicit Swift interface:
extension TestClass {
#inlinable
static func test(_ title: String? = nil, action: (() -> Void)? = nil) {
__test(title, action: action, action2: nil)
}
#inlinable
static func test(_ title: String? = nil, action: (() -> Void)?, action2: (() -> Void)? = nil) {
__test(title, action: action, action2: action2)
}
}
That will always call the second variant of the Objective-C method. This eliminates method resolution behaviors that might be otherwise not be obvious, making your intent explicit.

Upgrading Swift 3 to 4, swift extension no longer in objective c

I just finished upgrading a mixed language project (objective-c and Swift) from Swift 3 to Swift 4.
Everything seemed to go well except all of my Swift extensions are no longer accessible in objective-c. I can't figure out how to get any Swift extension to show up in objective-c. I've tried searching, but I can't find any mention of changes to extensions in Swift 4 except for loosening up the private scope.
All of these extensions were accessible from Objective-c in Swift 3, so there are no incompatible types (structs or non-raw enums).
The extensions are marked public.
The extensions are part of the same target and in the same project as the objective-c files.
Yes, I have imported "ProjectName-Swift.h" in the relevant objective-c files.
Other compatible Swift classes do show up. It seems just the extensions are missing.
I've tried marking each func public as well.
For example, the following extension used to be available to objective-c code when I was using Swift 3:
public extension UIAlertController {
class func alert(_ title: String? = nil, message: String? = nil) -> UIAlertController {
return UIAlertController(title: title, message: message, preferredStyle: .alert)
}
#discardableResult func action(_ action: String) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .default, handler: nil))
return self
}
#discardableResult func action(_ action: String, style: UIAlertActionStyle = .default, onSelected: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: style, handler: onSelected))
return self
}
#discardableResult func cancel(_ action: String? = "Cancel", onCancel: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .cancel, handler: { alert in
onCancel?(alert)
}))
return self
}
#discardableResult func destructive(_ action: String, onDestruct: #escaping ((UIAlertAction) -> Void)) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .destructive, handler: { action in
onDestruct(action)
}))
return self
}
func presentOn(_ viewController: UIViewController, animated: Bool = true) {
viewController.present(self, animated: animated, completion: nil)
}
}
A super simple solution is to just add #objc public extension to the declaration of each Swift extension.

Cannot Import Protocol from Sinch Framework - SinRegionInfo

I have a project that I have built where I am trying to include the sinch framework for making SMS verification. I added Sinch to the project VIA cocoapods, and also by manually adding the folder/framework to my xcode file system.
The problem i'm having right now is that my version of the CountrySelectionViewController.swift cannot access the SinRegionInfo protocol defined inside of the Sinch framework... i'm not sure what is going wrong. An example of the code/error inside CountrySelectionViewController.swift is here:
var entries: Array<SINRegionInfo> = []; ---> Use of undeclared type 'SinRegionInfo'
The strange part is that everything seems to be the same in my project as it is in the sample project, but in the sample project, there is no problem accessing the protocol.
CountrySelectionViewController.swift
import UIKit
import SinchVerification
class CountrySelectionViewController : UITableViewController {
var isoCountryCode: String?
var onCompletion: ((String) -> Void)?
var entries: Array<SINRegionInfo> = [];
override func viewDidLoad() {
super.viewDidLoad();
let regionList = PhoneNumberUtil().regionList(forLocale: Locale.current);
entries = regionList.entries.sorted(by: { (a: SINRegionInfo, b: SINRegionInfo) -> Bool in
return a.countryDisplayName < b.countryDisplayName;
})
}
override func viewWillAppear(_ animated: Bool) {
let row = entries.index { (region: SINRegionInfo) -> Bool in
return region.isoCountryCode == isoCountryCode;
}
if row != nil {
tableView.selectRow(at: IndexPath.init(row: Int(row!), section: 0), animated: animated, scrollPosition: .middle);
}
}

React native bridge is sometimes nil in swift module

I have a swift module created, which starts listening on a GCDAsyncUdpSocket when a connect method is called from swift
#objc(MyModule)
class MyModule: NSObject, GCDAsyncUdpSocketDelegate {
var bridge: RCTBridge!
var socket: GCDAsyncUdpSocket!
func methodQueue() -> dispatch_queue_t {
return dispatch_queue_create("com.mycompany.greatapp", DISPATCH_QUEUE_SERIAL)
}
#objc func connect(resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: methodQueue())
//...start listening, etc
}
#objc func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
bridge.eventDispatcher().sendAppEventWithName("got_msg", body: nil)
}
}
I've also created a private implementation
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
#interface RCT_EXTERN_MODULE(MyModule, NSObject)
RCT_EXTERN_METHOD(connect resolver:(RCTPromiseResolveBlock *)resolve
rejecter:(RCTPromiseRejectBlock *)reject)
#end
However on occasion bridge.eventDispatcher() unwraps to nil and it is unable to broadcast the event. Any thoughts would be appreciated.
This Github issue led me to the solution: https://github.com/facebook/react-native/issues/3454. Turns out this was only happening on reload. One needs to implement RCTInvalidating and then clean up any dangling references in invalidate. This allows ARC to cleanup your native module properly and reinstantiate RCTBridge
#objc(MyModule)
class MyModule: NSObject, GCDAsyncUdpSocketDelegate, RCTInvalidating {
var bridge: RCTBridge!
var socket: GCDAsyncUdpSocket!
func invalidate() {
self.socket = nil
}
func methodQueue() -> dispatch_queue_t {
return dispatch_queue_create("com.mycompany.greatapp", DISPATCH_QUEUE_SERIAL)
}
#objc func connect(resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: methodQueue())
//...start listening, etc
}
#objc func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
bridge.eventDispatcher().sendAppEventWithName("got_msg", body: nil)
}
}