Upgrading Swift 3 to 4, swift extension no longer in objective c - 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()
}

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

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);
}
}

Convert typedef to Swift

How do I convert this code to Swift
typedef UIViewController<CalendarViewControllerNavigation> CalendarViewController;
tried to find solution everywhere but seems I can't find a reference to this.
In Swift 4 you can use:
typealias CalendarViewController = UIViewController & CalendarViewControllerNavigation
Which uses the new protocol composition type.
Also see SE-156.
I was facing same issue to use calendar library from https://github.com/jumartin/Calendar.
I have solved this problem as below.
protocol CalendarViewControllerNavigation: NSObjectProtocol {
func move(to date: Date, animated: Bool)
}
typealias CalendarTypeController = UIViewController
extension CalendarTypeController:CalendarViewControllerNavigation{
internal func move(to date: Date, animated: Bool) {
}
internal func deviceIsRotated() {
}
internal func selectedEventForAddSession(event:EventDetail) {
}
}
protocol CalenderViewControllerDelegate : NSObjectProtocol {
func calendarViewController(_ controller:CalendarTypeController, showDate:NSDate)
}
Hope this code will help you!

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)
}
}