How do I access my Objective-C constants in Swift? - objective-c

My project currently has a file containing many constants that I use in a variety of places throughout my codebase. I'm writing a class extension in swift and need to access some of these constants.
I have something like this defining my constants in Objective-C:
//
// AppConstants.h
//
#import <Foundation/Foundation.h>
#interface AppConstants : NSObject
extern NSString *const SOME_CONSTANT_IN_APP_CONSTANTS;
#end
I've added my AppConstants.h to the bridging header, but when I try and do:
someDictionary.objectForKey(PROPERTY_CALL_CAMPUS_SECURITY_TITLE)
I get:
Use of unresolved identifier 'PROPERTY_CALL_CAMPUS_SECURITY_TITLE'
Is there a way to access constants defined in Objective-C in my Swift code?

Related

Why is a specific Swift class not visible to Objective-C?

All
Xcode 10.3 and Swift 5.0
I have a complex project with 3 targets ("Name", "Name Test" and "Name Local")
I have set all interoperability headers between Swift and Objective-C, including the Name-Bridging-Header.h, the Objc Preprocessor Macros to import the Name-Swift.h, Name_Test-Swift.h or Name_Local-Swift.h selectively
#ifdef TEST
#import "Name_Test-Swift.h"
#elif LOCAL
#import "Name_Local-Swift.h"
#else
#import "Name-Swift.h"
#endif
I have imported several Swift classes that I use frequently and everything works fine.
Now....
I added a new Swift class. This new class is subclass of NSObject
class NewClass: UIView { }
But using the new class in a Objective-C class, Xcode can't find this new class, with these errors:
# import "Name-Swift.h" is not recognized.
NewClass *item = [NewClass alloc]; - Unknown type name 'NewClass'
I checked the following:
- The new class is added to all Targets and is verified in the Build-Phases->Compile-Sources section
My other Swift classes don't have public or #objc identifiers, and everything works OK..... So I still I tried using them in the new class (#objc and public in the class definition), with no success
With only adding NewClass *item = [NewClass alloc]; in any Objective-C class makes Xcode to stop recognizing "Name-Swift.h"
Any idea what is missing?
After some testing, I found that I need to import the Name-Swift.h file in the .h file where I want to declare my object.
My project defines the #import Name-Swift.h in the Name-Prefix.pch
#ifdef TEST
#import "Name_Test-Swift.h"
#elif LOCAL
#import "Name_Local-Swift.h"
#else
#import "Name-Swift.h"
#endif
The #import declaration in the .pch file allows the Swift class to be accesible anywhere from the .m file of my Objective-C class, but not from the .h
I am not sure if that is the expected behavior... I will do a deep dive to the Documentation.

Resolving Swift.h and Bridging-Header.h circular references involving enums

I have an Objective-C header that has to be used by a Swift class. However, this header has to use the Swift.h file for an enum declared in a Swift file. In other words, the setup is as follows:
MPViewController.h
#import "MyProject-Swift.h"
#interface MPViewController: UIViewController
#property (nonatomic, assign) MPSomeEnum theEnum;
...
#end
MyProject-Bridging-Header.h
...
#import "MPViewController.h"
...
SomeEnum.swift
#objc enum MPSomeEnum: Int {
...
}
When compiling the code, I get three errors:
'MyProject-Swift.h' file not found
Failed to emit precompiled header [Xcode DerivedData folder]/[...]/MyProject-Bridging-Header-swift_[...].pch for bridging header [Project folder]/MyProject-Bridging-Header.h
Unknown type name 'MPSomeEnum'
Am I correct to assume that this stems from the circular reference between MyProject-Swift.h and the bridging header MyProject-Bridging-Header.h? From looking at a similar question one solution is to use forward declaration. However, it doesn't seem possible to forward declare an enum, so perhaps the only way to do this is to move the enum definition to an Objective-C file altogether?
TL&DR; As you suspected, you need to either move the enum declaration to Objective-C, or migrate the class to Swift.
Forward declarations of enums is possible in Objective-C:
#property SomeEnum someProperty;
- (void)doSomethingWithEnum:(enum SomeEnum)enumValue;
However correct Cocoa enums are typedefs to NSInteger: typedef NS_ENUM(NSInteger, MyEnum), and the enum keyword doesn't hold enough information for how much space to allocate when using it, so you'll get into all kind of compiler error when you want to use declarations like this. Thus an enum declared in Swift is not forward declarable in Objective-C.
Now, if you really want to keep the enum definition in Swift, you could use a workaround, and declare it as NSInteger in Objective-C, while providing a specialized property in Swift:
// NS_REFINED_FOR_SWIFT imports this in Swift as __theEnum
#property(nonatomic, assign) NSInteger theEnum NS_REFINED_FOR_SWIFT;
extension MPViewController {
// we provide a wrapper around the Objective-C property
var theEnum: MPSomeEnum {
// this uses a forced unwrap, beware :)
return MPSomeEnum(rawValue: theEnum)!
}
}

getting Swift Object from Objective C class using another Swift File

I have been trying with no success the following structure:
ClassA.swift
class ClassA:NSObject{
var varA = ""
}
then I have a ClassB.h and ClassB.m (Objective c) and I am not able to define "Project-Swift.h" in the .h file so i import it into the .m
#interface ClassB()
#property ClassA *myClassA;
#end
and
#synthesize myClassA = theAClass;
The problem comes when I try
class ClassC:NSObject{
let theClassAFromC = ClassB.myAClass
}
I get an error message Value of type 'ClassB' has no member 'theClassA'
Add your objc header to bridging header
In your objc header use #class notation for your swift class, e.g. #class ClassA;, instead of import "Project-Swift.h" file. You can import that one in your objc implementation file.
Don't forget that you cannot access non-static property of objc class the way you're declaring. Instead, initialize your ClassB object in your swift class and access it's property when needed. For instantiating the ClassA object you can use dependency injection in ClassB
As part of the convenience, use #objc declaration for your Swift classes accesible to objc runtime
try to search with keyword "Bridging-Header" and then add
#import "ClassB.h"
That does not work in my case.
adding ClassB.h in my Bridge File makes properties from .h Visible But not the the properties in my .m file. If i add Class.m In my header file then Module-Swift.h is not found

Class with defined static constant values in Objective-C

I want to create a class that will contains static values accessable from all project.
Pseudocode:
class Constants:
constant String API_URL : "http://api.service.com"
constant Integer SOME_VALUE : 7
How can I do this with Objective-C ?
Answer for your question is extern keyword . I will explain it to you using an example . Add objective c classes your project and name them Common , Now in Common.h
#interface Common : NSObject
extern NSString *SiteApiURL;
#end
After you defined an instance of NSString Class using the extern keyword what you need to do is switch to Common.m class and initialize the value for NSString (SiteApiURL)
#import "Common.h"
#implementation Common
NSString *SiteApiURL = #"http://api.service.com";
#end
Import the Common.h class within the project-Prefix.pch file like this
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "Common.h"
#endif
All done , now you can use the object "SiteApiURL" anywhere in the whole project and you need not to import any class anywhere i.e. You can use this variable anywhere in the project directly.
You could do it using preprocessors:
#define API_URL #"http://api.service.com"
#define SOME_VALUE (7)
Accessing defines would be simple: [object do:API_URL];
Or you could use constants
NSString * const apiURL = #"http://api.service.com";
NSNumber * const someValue = #7;
Accessing consts would be like accessing variables, So the string would just be a simple call. The NSNumber is an object wrapper for primitives so you'd need to access it like: someValue.intValue
You can create a Singleton with all necessary constants Here is a sample
If you do not want to create the class than you can use static private variables and static getters.
#interface
+(NSString*) getValue;
#end
#implementation
static NSString *_value = #"....";
+(NSString*) getValue {
return _value;
}
#end

declare obj-c class interface that contain c++ class type ivar

Currently I am working on a cocos2d+Box2D project so I have deal with some Objective-C++ code.
And I am facing to such situation:
#import "cocos2d.h"
#import "Box2D.h"
#interface BasicNode : CCNode {
#private
ccColor3B _color;
b2Body *_body;
b2Fixture *_shape;
}
b2Body and b2Fixture are C++ class that defined in Box2D.h
It works if the implementation of BasicNode is named BasicNode.mm.
But if I have another file named Game.m that is using BasicNode and import BasicNode.h, it won't compile because .m file is Obj-C file and does not know about C++ code.
So I decided to move #import "Box2D.h" into implementation file and only keep type declaration in head file (this is exactly what header file should contain).
But how do I do it? They are C++ class type but they are actually just a pointer so I wrote some helper macro
#ifdef __cplusplus
#define CLS_DEF(clsname) class clsname
#else
#define CLS_DEF(clsname) struct clsname; typedef struct clsname clsname
#endif
CLS_DEF(b2Body);
CLS_DEF(b2Fixture);
It works, only if CLS_DEF(b2Body) is appear once only. Otherwise compiler will find multiple type declaration for a same name even they are the same. Than I have to change to
#ifdef __cplusplus
#define CLS_DEF(clsname) class clsname
#else
#define CLS_DEF(clsname) #class clsname
#endif
And it is working now.
But I don't think it is a great idea that I declare a C++ class type as an Obj-C class especially I am using ARC.
Is any better way do deal with it? And I don't really want to make something like this
#interface BasicNode : CCNode {
#private
ccColor3B _color;
#ifdef __cplusplus
b2Body *_body;
b2Fixture *_shape;
#else
void *_body;
void *_shape;
#endif
}
Edit: Also please tell me will my tweak way introduce any problem?? by making C++ class ivar looks like Obj-C class for other pure Obj-C code.
One simple solution is to rename Game.m to Game.mm.
There are a couple of ways. If you can rely on using the Objective-C 2.2 runtime's features, you can add ivars in class (category) extensions. This means you can add ivars in your class's .mm file, and keep the .h file clean of any C++ stuff.
If you need to support older versions of the runtime, there are a few ways to do it which are better than #ifdefing. In my opinion, the best way is to use the 'pimpl' idiom which is common in C++ - you forward declare an implementation struct in your header, and add an ivar which is a pointer to such a struct. In your class's implementation (.mm), you actually define that struct with all its C++ members. You then just need to allocate that implementation object in your init... method(s) with new and delete it in dealloc.
I've written up the pimpl idiom as it applies to cleanly mixing Objective-C and C++ in this article - it also shows some other potential solutions which you could consider.
With Xcode 5, you don't have to declare instance variables in the header file, you can just declare them in the implementation file. So your BasicNode header file is not "contaminated" with C++.
You can use "struct" instead of "class" in C++. The only difference is that in a class all members are private by default, while in a struct they are public by default. But you can do everything with a struct that you can do with a class. That way you can write for example
struct b2Body;
struct b2Fixture;
outside your interface, and
{ ...
struct b2Body* _body;
...
}
in your interface.