I am trying to switch one of my apps from Objective-C to swift. There are some things in swift language that I can't get to work. First of all, I have a Model class which I initialise in AppDelegate's didFinishLaunchingWithOptions. I declare the Model's object in the .h file in Objective-C like this:
#property(nonatomic,retain)Model *model;
The in the didFinishLaunchingWithOptions I initialise it:
self.model=[[Model alloc]init];
And then I have the following method:
+(Model *)getCurrentModel{
AppDelegate *appdelegate=(AppDelegate *)[UIApplication sharedApplication].delegate;
return appdelegate.model;
}
And then I can access the current instance of Model from any class by calling:
model = [AppDelegate getCurrentModel];
So, the first question, how can I do the same in Swift? Especially the static functions(the function that begin with +). I tried this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var model : Model?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
// Override point for customization after application launch.
model = Model()
return true
}
class var currentModel : Model {
struct Static {
static let instance : Model = AppDelegate.sharedApplication.delegate //error AppDelegate.Type does not have a member named sharedApplication
}
return Static.instance.model
}
Second question. In Objective C, we can make an instance variable and global variable and global function by declaring in.h file like this:
#interface ViewController : UIViewController {
//Instance Variables
NSString *aName;
}
//Properties or variables that can be accessed globally
NSString *anotherName;
//Methods that have global scope
- (void)doSomething;
#end
Those functions that are not declared in the .h file have a local scope. How do I do the same in Swift?
sharedApplication is a method of UIApplication, not of AppDelegate.
In addition, you have to unwrap the optional for then return value:
class var currentModel : Model {
struct Static {
static let instance = UIApplication.sharedApplication().delegate as AppDelegate
}
return Static.instance.model!
}
Related
I am using a Swift class which uses a variable sharedInstance. When I am trying to use this variable in my Objective-C file, I am getting the error:
No known class method for selector 'sharedInstance'
My Swift class:
class SpeakToMeForUnity : NSObject, SFSpeechRecognizerDelegate {
fileprivate var speechRecognizer : SFSpeechRecognizer?
fileprivate var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
fileprivate var recognitionTask : SFSpeechRecognitionTask?
fileprivate var audioEngine : AVAudioEngine?
static let sharedInstance = SpeakToMeForUnity()
override fileprivate init() {
super.init()
self.speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
self.audioEngine = AVAudioEngine()
self.speechRecognizer?.delegate = self
}
// Some other functions
}
Below is my Objective-C file:
#import <Speech/Speech.h>
#import "SpeakToMeForUnity-Swift.h"
extern "C"
{
void _prepareRecording()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance prepareRecording];
}
void _recordButtonTapped()
{
SpeakToMeForUnity *instance = [SpeakToMeForUnity sharedInstance];
[instance recordButtonTapped];
}
}
I am using Swift 4 and Xcode 10.3.
Normally to make Swift available in Objective-C you just need to import your swift module #import "ProductModuleName-Swift.h", as you're doing.
If you need the swift code in the Objective-C header use #class MySwiftClass; or #protocol MySwiftClass;.
You can force some class or property to be accessible in Objective-C by adding the attribute #objc.
Remember that private or fileprivate access level attributes will not be accessible unless you add the previous attribute.
To expose things to ObjC, you need to mark them #objc. In this case you should add this before class and before the static let.
In previous versions of Swift, inheritance from NSObject automatically implied #objc, but that was removed, and it now needs to be explicit.
I've come to Swift from Objective-C and there's a lot of things that Objective-C can do but swift is much more complicated. Such as OOP dynamic initializer.
E.g. I've got this code working in Objective-C:
#interface CommonVC: UIViewController
+ (instancetype)showFrom:(UIViewController *)vc;
#end
#implementation CommonVC
+ (instancetype)showFrom:(UIViewController *)vc {
CommonVC *instance = [self instantiateFrom:vc.nibBundle];
[vc presentViewController:instance animated:YES completion:nil];
return instance;
}
// this is like convenience initializer.
+ (instancetype)instantiateFrom:(NSBundle *)aBundle {
return [self.alloc initWithNibName:NSStringFromClass(self.class) bundle:aBundle];
}
#end
#interface SubClassVC: CommonVC
#end
And then use the subclass or superclass like this:
SubClassVC *subVC = [SubClassVC showFrom:self];
// or in swift:
SubClassVC.show(from: self)
However, in swift, it seems impossible to implement something like that. I've tried a few, but always got compile error. Here's one:
class CommonVC: UIViewController {
class func show(from sender: UIViewController) -> Self {
let vc = self(sender: sender) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
sender.present(vc, animated: true, completion: nil)
return unsafeDowncast(vc, to: self)
}
convenience init(sender: UIViewController) {
self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
loadView()
}
}
So how do I write generic convenience initializer of a viewController from the super class and then call that with the subclass?
Of course, my convenience init has lots of stuff that I just cut down to this simple code, also the function show(from:) has a different presentation instead of this simple present(_:animated:completion:).
Even if I make a function to do the setup after initialize, it still wouldn't work
class CommonVC: UIViewController {
class func show(from sender: UIViewController) -> Self {
let vc = self.init(nibName: type(of: self).className, bundle: sender.nibBundle) // Compiler error: Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer
vc.setupAfterInitialize()
sender.present(vc, animated: true, completion: nil)
return unsafeDowncast(vc, to: self)
}
convenience init(sender: UIViewController) {
self.init(nibName: type(of: self).className, bundle: sender.nibBundle)
setupAfterInitialize()
}
internal func setupAfterInitialize() {
// do stuff
loadView()
}
}
And the code looks stupid, doesn't make convenience init convenience.
For now, I can't use the class function show(from:) but have move the presentation outside and make things like:
CommonVC.show(from: self)
SubClassVC(sender: self).present()
// instead of this simple presentation:
// SubClassVC.show(from: self)
I've even tried this but still not working:
class func show<T: CommonVC>(from sender: UIViewController) -> Self {
T.init(nibName: type(of: self).className, bundle: sender.nibBundle)
....
When you switch from Objective-C to Swift, it's tempting to simply translate your Objective-C style into Swift code. But Swift is fundamentally different in some ways.
It may be possible to implement a generic class which all your controllers are subclass of, but we tend to try and avoid inheritance in Swift where possible (in favour of protocols and extensions).
A good Swift rule of thumb, from Apple, is: "always start with a protocol"...
It's actually very easy to implement what you want using a protocol, and extension:
protocol Showable {
init(className: String, bundle: Bundle?)
static func show(from: UIViewController) -> Self
}
extension Showable where Self: UIViewController {
init(className: String, bundle: Bundle?) {
self.init(nibName: className, bundle: bundle)
}
static func show(from: UIViewController) -> Self {
let nibName = String(describing: self)
let instance = self.init(className: nibName, bundle: from.nibBundle)
from.present(instance, animated: true, completion: nil)
return instance
}
}
In the above code, I've declared a Showable protocol and an extension that provides a default implementation that applies where the adopting class is an instance of UIViewController.
Finally, to provide this functionality to every single view controller in your project, simply declare an empty extension:
extension UIViewController: Showable { }
With these two short snippets of code added you can now do what you describe in your question (as long as an appropriately named nib exists for your view controller instance):
let secondController = SecondViewController.show(from: self)
ThirdController.show(from: secondController)
And that's the beauty of Swift. All your UIViewController subclasses now get this functionality for free; no inheritance required.
I'm attempting to slowly migrate an Objective C app over to Swift and have started to create new classes -
public class MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
Now in my Objective C .m file I've declared #import MyTarget-Swift.h and in my .h I've added #class MapsAPI which all seems fine however I'm not sure what the Objective C initialisation code should look like. I've tried -
MapsAPI *api = [[MapsAPI alloc] initWithManagerWithDelegate: self];
But that errors with -
No visible #interface for 'MapsAPI' declares the selector
'initWithManagerWithDelegate:'
I've tried looking at the definition of my MyTarget-Swift.h but all that shows is -
SWIFT_CLASS("_TtC4What7MapsAPI")
#interface MapsAPI : NSObject
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
#end
Is there something I'm doing wrong here?
You may choose to add #objcMembers to your class declaration:
public class #objcMembers MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
Alternatively (or additionally... who am I to judge) you can mark your initializer as being exposed to Objective-C
public class MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
#objc public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
And if you want to, you can also explicitly define the Objective-C selector used:
public class MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
#objc(initManagerWithDelegate:)
public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
We are trying to reference Swift methods inside an Objective-C implementation.
Swift class:
import Foundation
#objc class MySwiftClass: NSObject {
override init() {
super.init()
}
func sayHello() -> Void {
print("hello");
}
func addX(x:Int, andY y:Int) -> Int {
return x+y
}
}
Objective-C implementation (Objective-c.m):
#import "ProductModuleName-Swift.h"
MySwiftClass* getData = [[MySwiftClass alloc]init];
[getData sayHello] //works
[getData addX:5 addY:5] //No visible #interface for 'MySwiftClass' declares selector 'addX:addY'
The last line of code gives the following error:
No visible #interface for 'MySwiftClass' declares selector 'addX:addY'
If you command-click on "ProductModuleName-Swift.h" in the Xcode
source file editor then you can see how the Swift methods are mapped to Objective-C.
In your case that would be
#interface MySwiftClass : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
- (void)sayHello;
- (NSInteger)addXWithX:(NSInteger)x andY:(NSInteger)y;
#end
which is called as
MySwiftClass* getData = [[MySwiftClass alloc]init];
[getData sayHello];
NSInteger result = [getData addXWithX:5 andY:5];
A better Swift 3 method name might be
func add(x: Int, y:Int) -> Int
because x is already the argument (external) name of the first
parameter. You can also add an #objc() attribute to the Swift definition
to control the Objective-C name. For example, with
#objc(addX:andY:)
func add(x: Int, y: Int) -> Int {
return x+y
}
it would be called from Objective-C as
NSInteger result = [getData addX:5 andY:5];
As #ekscrypto pointed out, in Swift 4 and later you need to annotate individual functions with #objc. Prior to that, a single, class-level #objc was enough.
Of course in Objective-C class you must add import of NAME_PROJECT-swift.h.
If your project name is Sample then you must add:
#import Sample-swift.h
And then:
Swift 4 or Less
#objc class MySwiftClass: NSObject {
func sayHello(){
//function
}
func addX(){
//function
}
}
Swift 4 or Greater
#objc class MySwiftClass: NSObject {
#objc func sayHello(){
//function
}
#objc func addX(){
//function
}
}
In my case I had forgotten to add:
#import "MyProject-Swift.h"
Into the obj c file.
I'm having some trouble accessing a Swift Singleton from Objective-C.
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
swiftSharedInstance can not be reached.
Nicky Goethlis's answer is correct but I just want to add another way of Singleton creation termed as One line Singleton" in Swift which I came across recently and it does not use Struct:
Singleton.swift
#objc class Singleton: NSObject {
static let _singletonInstance = Singleton()
private override init() {
//This prevents others from using the default '()' initializer for this class.
}
// the sharedInstance class method can be reached from ObjC. (From OP's answer.)
class func sharedInstance() -> Singleton {
return Singleton._singletonInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
SomeObjCFile.m
Singleton *singleton = [Singleton sharedInstance];
NSString *testing = [singleton testTheSingleton];
NSLog(#"Testing---> %#",testing);
Swift 5 and above
final class Singleton: NSObject {
#objc static let shared = Singleton()
#objc var string: String = "Hello World"
private override init() {}
}
use in Objective-C
#import <ProjectName-Swift.h> // change ProjectName to actual project name
NSLog("Singleton String = %#", [Singleton shared].string);
For now I have the following solution. Maybe I am overlooking something that would enable me to access "swiftSharedInstance" directly?
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
// the sharedInstance class method can be reached from ObjC
class func sharedInstance() -> SingletonTest {
return SingletonTest.swiftSharedInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
Then in ObjC I can get the sharedInstance class method (after importing the xcode generated swift header bindings)
SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(#"Singleton says: %#", [aTest testTheSingleton]);
To make members of the SingletonTest class accessible (swiftSharedInstance is a member of this class), use #objcMembers modifier on the class, or add #objc modifier directly on the swiftSharedInstance:
#objc #objcMembers class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
Or:
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
#objc class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
After creating the Bridging header, be sure to have the Objective-C Generated Interface Header Name set in your Build Settings from your app target. If the value is empty, add the following value:
$(SWIFT_MODULE_NAME)-Swift.h
You need add #objc property wrapper to your singleton:
#objc final class Singleton: NSObject {
#objc static let sharedInstance = Singleton()
#objc func foo() { }
}
Then, in the Objective-C class, import the following:
// Replace the "App" with your Target name.
#import "App-Swift.h"
Finally, after compiling the project, you will be able to use your singleton from Swift inside your Objective-C class:
[[Singleton sharedInstance]foo];
You pretty much have it. To use Swift classes in Obj-C you both need to #import "SingletonTest-Swift.h the generated header or forward declaration with #class MySwiftClass.
Additionally the class needs to inherit from an Obj-C class like you have don here with NSObject or be marked with #objc to expose it. You don't need to do both though, #objc is there to be a more granular option when choosing things to expose.
Apple has some good documentation on all of this and there are two different WWDC sessions you can watch on the topic of Obj-C interoperability as well.
Don't forget to set sharedInstance as public
public final class TestSwiftMain: NSObject {
#objc public static let sharedInstance = TestSwiftMain()
private override init() {}
#objc public func test() {
print("testing swift framework")
}
}
Using it in Objc
[[testSwiftMain sharedInstance] test];
Update 12 Oct 2022
ObcMember So you won't have to write objC behind every function
Swift Class
#objcMembers class SwiftHelpingExtentions: NSObject {
static let instanceShared = SwiftHelpingExtentions()
func testingMethod() {
print("testing")
}
}
}
On objective C View Controller
import : #import "App-Swift.h"
Call The method:
[SwiftHelpingExtentions.instanceShared testingMethod];