I am migrating a UIViewController class to train a bit with Swift. I am successfully using Objective-C code via the bridging header but I have the need of importing a constants file that contains #define directives.
I have seen in Using Swift with Cocoa and Objective-C (Simple macros) the following:
Simple Macros
Where you typically used the #define directive to define a primitive constant in C and Objective-C, in Swift you use a global constant instead. For example, the constant definition #define FADE_ANIMATION_DURATION 0.35 can be better expressed in Swift with let FADE_ANIMATION_DURATION = 0.35. Because simple constant-like macros map directly to Swift global variables, the compiler automatically imports simple macros defined in C and Objective-C source files.
So, it seems it's possible. I have imported the file containing my constants into the bridging header, but I have no visibility from my .swift file, cannot be resolved.
What should I do to make my constants visible to Swift?
UPDATE:
It seems working with NSString constants, but not with booleans:
#define kSTRING_CONSTANT #"a_string_constant" // resolved from swift
#define kBOOL_CONSTANT YES // unresolved from swift
At the moment, some #defines are converted and some aren't. More specifically:
#define A 1
...becomes:
var A: CInt { get }
Or:
#define B #"b"
...becomes:
var B: String { get }
Unfortunately, YES and NO aren't recognized and converted on the fly by the Swift compiler.
I suggest you convert your #defines to actual constants, which is better than #defines anyway.
.h:
extern NSString* const kSTRING_CONSTANT;
extern const BOOL kBOOL_CONSTANT;
.m
NSString* const kSTRING_CONSTANT = #"a_string_constant";
const BOOL kBOOL_CONSTANT = YES;
And then Swift will see:
var kSTRING_CONSTANT: NSString!
var kBOOL_CONSTANT: ObjCBool
Another option would be to change your BOOL defines to
#define kBOOL_CONSTANT 1
Faster. But not as good as actual constants.
Just a quick clarification on a few things from above.
Swift Constant are expressed using the keywordlet
For Example:
let kStringConstant:String = "a_string_constant"
Also, only in a protocol definition can you use { get }, example:
protocol MyExampleProtocol {
var B:String { get }
}
In swift you can declare an enum, variable or function outside of any class or function and it will be available in all your classes (globally)(without the need to import a specific file).
import Foundation
import MapKit
let kStringConstant:String = "monitoredRegions"
class UserLocationData : NSObject {
class func getAllMonitoredRegions()->[String]{
defaults.dictionaryForKey(kStringConstant)
}
simple swift language don't need an macros
all #define directives.
will be let
and complex macros should convert to be func
The alternative for macro can be global variable . We can declare global variable outside the class and access those without using class. Please find example below
import Foundation
let BASE_URL = "www.google.com"
class test {
}
Related
I have written a Objective C++ wrapper so that I will be able to access the C++ methods in Swift.
The wrapper has a method which returns a C++ class reference.
IECMServices.h (C++ class)
namespace ECMServices
{
class IECMServices
{
public:
virtual DataServices::IParameters& getDeviceInformation(ECMServicesTypes::UINT_16 moduleID) = 0;
};
}
IECMServicesWrapper.mm (Objective C++ class)
I am getting the above error while writing the Wrapper code.
IParameters is a C++ class
OParameters is Objective C++ equivalent class for IParameters
I want to return reference of OParameters to Swift.
For that I want to pass reference of C++ class to my oParametersRef
oParametersRef = oCECMServicesProviderObj.getECMServices().getDeviceInformation(0);
"oCECMServicesProviderObj.getECMServices().getDeviceInformation(0);"
this returns reference of IParameters which is a C++ class.
So that i can access methods of IParameters(C++) in swift via OParameters(Objective C++)
I want to pass reference of C++ to Objective C++ to Swift.
Search this community site, there are many answers that address this problem. See, for example, this: How to use Superpowered lib in Swift project
The specific error message in the image is due to the fact that getDeviceInformation() returns a reference to IParameters, which is assigned to an incompatible type OParameters*, i.e. a pointer to OParameters. The code of OParameters is not given, but I would guess that converting an IParameters reference to an OParameters pointer would be tricky and unnecessary. Anyway, your OParameters class evidently does not provide such a conversion.
As you can see in the aforementioned answer, it is impossible to expose C++ code to Swift directly, and IParameters is a C++ class. Objective-C++ allows you to mix Objective-C and C++, but only the Objective-C portion of the code can be exposed to Swift through headers. So, you will need to create an Objective-C class that is populated based on the contents of an IParameters instance and returned to Swift. In addition, if you want changes made in Swift to be visible in C++, you would need to keep a pointer or a reference to IParameters in the wrapper .mm file, but NOT in any headers visible to Swift, and have setters, visible to Swift, that can be used to modify the IParameters state.
Here is a quick and dirty example. The focus here is only on sharing IParams between C++ and Swift. Things like memory management are out of scope here. For simplicity, let us say our IParams and EMC C++ classes look as follows:
class IParams {
public:
int32_t param1;
int32_t param2;
};
class ECM
{
public:
static ECM * getInstance();
// It's very important that a reference is returned here. This
// allows us to store the pointer to the referenced IParams in a
// member of OParams, and the OParams' getters and setters can then
// directly access the IParams object returned here by reference.
IParams& getDeviceInfo();
...
};
Here are their wrapper implementations in Objective-C++ (.mm) code, which refer to C++ code directly, but it's OK as this is hidden from Swift:
#implementation OParams
{
IParams * pIParams;
}
-(id)init:(IParams*)pip
{
pIParams = pip;
return self;
}
// These getters and setters directly access the memory of the IParams
// object pointed to by pIParams.
-(int32_t)getParam1 { return pIParams->param1; }
-(int32_t)getParam2 { return pIParams->param2; }
-(void)setParam1:(int32_t)p { pIParams->param1 = p; }
-(void)setParam2:(int32_t)p { pIParams->param2 = p; }
#end
#implementation ECMWrapper
-(OParams*)getDeviceInfo
{
// We're returning a pointer to an OParams instance whose pIParams
// member points to the IParams object the reference to which is
// returned from ECM::getDeviceInfo()
return [[OParams alloc] init:(&ECM::getInstance()->getDeviceInfo())];
}
#end
Finally, here are the declarations that are visible to Swift, directly or indirectly, via the bridging header:
#interface OParams : NSObject
-(int32_t)getParam1;
-(int32_t)getParam2;
-(void)setParam1:(int32_t)p;
-(void)setParam2:(int32_t)p;
#end
#interface ECMWrapper : NSObject
-(OParams*)getDeviceInfo;
#end
Note that no C++ types or methods are mentioned here, so this code can be used in Swift.
Using Swift again, am I missing something?
I have this line:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: UILayoutConstraintAxis.Vertical);
But Xcode is giving me an error:
Undefined symbols for architecture i386: "_UILayoutPriorityRequired"
Another one of Swift's quirks?
Solution: replace UILayoutPriorityRequired with 1000
self.itemDescription?.setContentCompressionResistancePriority(1000, forAxis: UILayoutConstraintAxis.Vertical);
This is not a bug. It is a misunderstanding of how Objective-C libraries are imported into Swift. It should be understood how Swift imports code (even from the Apple UIKIt Libraries) from Objective-C into Swift.
UILayoutPriority is a float. In Objective-C, a couple of values have been pre-defined for us. The pre-defined values appear to be an enum. We might expect that the same enums would be available to us in Swift.
The documentation does seem to imply an enum:
SWIFT (Documentation)
typealias UILayoutPriority = Float
OBJECTIVE-C (Documentation)
enum {
UILayoutPriorityRequired = 1000,
UILayoutPriorityDefaultHigh = 750,
UILayoutPriorityDefaultLow = 250,
UILayoutPriorityFittingSizeLevel = 50,
};
typedef float UILayoutPriority;
But in Xcode, if you ask to see the defintion of one of these enum values (UILayoutPriorityRequired, for example), you will see that they are actually defined in the header file as constant floats.
OBJECTIVE-C (Header file)
typedef float UILayoutPriority;
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint. Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
So although we may like to think of the pre-defined layout priorities as enum values (as the documentation suggests) the layout priorities are not really defined as enums; they are defined as constant floats.
A hint that there is a mis-match -- for anyone that knows the C programming language -- is that a C enum may only contain int values. The following is legal and will compile:
enum myEnum {
JGCEnum_one = 1, // this is O.K.
JGCEnum_two,
JGCEnum_three
} JGCEnum;
But we can not define floats as values for C enums. The following will not compile:
enum myEnum {
JGCEnum_one = 1.5, // compile error
JGCEnum_two,
JGCEnum_three
} JGCEnum;
Objective-C enums are the same as C enums (Swift enums are different). It is important to know if we are dealing with actual integers or floats. Because integers can be defined using the NS_ENUM macro, which can then be imported to Swift as a Swift enum.
The iBook says
Swift imports as a Swift enumeration any C-style enumeration marked with the NS_ENUM macro. This means that the prefixes to enumeration value names are truncated when they are imported into Swift, whether they’re defined in system frameworks or in custom code.
Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks
That means that if UILayoutPriority had been defined as an integer using the NS_ENUM macro, it would have been imported into Swift as Swift enum. This is the case for UILayoutConstraintAxis.
SWIFT (Documentation)
enum UILayoutConstraintAxis : Int {
case Horizontal
case Vertical
}
OBJECTIVE-C (Documentation)
enum {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
typedef NSInteger UILayoutConstraintAxis;
Looking at the Objective-C header file confirms what the documentation says.
OBJECTIVE-C (Header File)
//
// UIView Constraint-based Layout Support
//
typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
UILayoutConstraintAxisHorizontal = 0,
UILayoutConstraintAxisVertical = 1
};
So there are at least two ways to know if a pre-defined value you are used to using in Objective-C is available in Swift:
check the documentation
check the header file in Objective-C (found by right-clicking the value and then selecting "Jump to Definition")
There is one more way to see if a typedef you are used to using is a constant or an enum. In code, test to see if the address of the constant exists. Constants have a memory address, while enums do not. See the code below.
// this line will compile and run just fine.
// UILayoutPriorityDefaultHigh is a constant and has a memory address
// the value will be true if the device is running iOS 6.0 or later
// and false otherwise
BOOL predefinedValueIsAvailable = (NULL != &UILayoutPriorityDefaultHigh);
// this line will not compile
// UILayoutConstraintAxisHorizontal is an enum (NOT a constant)
// and does not have a memory address
predefinedValueIsAvailable = (NULL != &UILayoutConstraintAxisHorizontal);
References
Using Layout Priority in Swift
Xcode Documentation (iOS 8.2)
Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks
One thing to keep in mind is that, even though UILayoutPriority is an alias to Float, you're using Swift and you can use an extension to create semantic constants for these values:
extension UILayoutPriority {
static var Low: Float { return 250.0 }
static var High: Float { return 750.0 }
static var Required: Float { return 1000.0 }
}
Then you're code can read as:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority.Required, forAxis: .Vertical);
I've done this myself in one of my projects and thought it may be useful.
You can also write this:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(<float number>), forAxis: .Vertical)
Example:
self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(750), forAxis: .Vertical)
I'm trying to continue with Swift in an existing project I had started in Objective-C. I followed the Apple documentation and I managed to access a Swift class from an Objective-C file, via including the "ProductModuleName-Swift.h" header in my Obj-C file.
At this point here's my code:
#include "Pianoconcert_App-Swift.h"
#interface ModelsVC ()
#end
#implementation ModelsVC
// And all that kind of stuff
// ...
-(IBAction)comanda:(UIButton *)sender {
ComandaTableVC *controller = (ComandaTableVC *)self.tabBarController.viewControllers[2];
// Here goes the problematic code
[self.tabBarController setSelectedIndex:2];
}
This piece of code has no problems. But now I just want to set one of the variables in the Swift class like this: controller.selectedModel = sender.tag, but Xcode just tells me the variable does not exist.
Here's an extract of my Swift class:
import UIKit
class ComandaTableVC: UITableViewController, UIPickerViewDataSource, UIPickerViewDelegate {
// Declaration of some constants and variables
// And here goes the one
var selectedModel: Int = 0
// And a bunch more of variables and functions
// ...
}
I don't know what I'm doing wrong. The class seems fully included and, actually, I can access that variable and all the others if I use the class from another Swift file, like this:
var controller: ComandaTableVC = self.tabBarController.viewControllers[2] as ComandaTableVC
controller.selectedModel = 2
It's been resolved finally, but I can't explain how.
Suddenly the compiler threw a build error telling me that the file Pianoconcert_App-Swift.h could not be found.
I changed it for PianoconcertApp-Swift.h, deleting the underscore that replaced the blank space, and now I can access all the variables and constants correctly.
I have problems to understand, why some member functions from an imported (and complicated) set of Objective-C interface are not available in Swift.
I have a Bridging-Header File:
#import "EvernoteSDK.h"
and I can't use some member functions in my ViewController
let userStore = EvernoteUserStore()
userStore.initWithSession(session)
initWithSession is not available for the swift code, but why?
The objective-C header shows:
#interface EvernoteUserStore : ENAPI
+ (instancetype)userStore;
- (id)initWithSession:(EvernoteSession *)session;
If I could view the exposed Objective-C header, I may understand, how the mangling works
In Swift the initializer call is combined with the constructor. In other words, Objective-C's
EvernoteUserStore *userStore = [[EvernoteUserStore alloc] initWithSession:session];
becomes
let userStore = EvernoteUserStore(session:session);
The tool recognizes the initWithSomething: name of Objective-C, and converts it to
init(something something : SomeType)
In case of EvernoteUserStore the corresponding init method looks like this:
init(session session: EvernoteSession!)
I'm trying to use a third-party Objective-C library in a Swift project of mine. I have the library successfully imported into Xcode, and I've made a <Project>-Bridging-Header.h file that's allowing me to use my Objective-C classes in Swift.
I seem to be running into one issue however: the Objective-C code includes a Constants.h file with the macro #define AD_SIZE CGSizeMake(320, 50). Importing Constants.h into my <Project>-Bridging-Header.h doesn't result in a global constant AD_SIZE that my Swift app can use.
I did some research and saw that the Apple documentation here under "Complex Macros" says that
“In Swift, you can use functions and generics to achieve the same
results [as complex macros] without any compromises. Therefore, the
complex macros that are in C and Objective-C source files are not made
available to your Swift code.”
After reading that, I got it to work fine by specifying let AD_SIZE = CGSizeMake(320, 50) in Swift, but I want to maintain future compatibility with the library in the event that these values change without me knowing.
Is there an easy fix for this in Swift or my bridging header? If not, is there a way to replace the #define AD_SIZE CGSizeMake(320, 50) in Constants.h and keep things backwards-compatible with any existing Objective-C apps that use the old AD_SIZE macro?
What I did is to create a class method that returns the #define.
Example:
.h file:
#define AD_SIZE CGSizeMake(320, 50)
+ (CGSize)adSize;
.m file:
+ (CGSize)adSize { return AD_SIZE; }
And in Swift:
Since this is a class method you can now use it almost as you would the #define.
If you change your #define macro - it will be reflected in the new method you created
In Swift:
let size = YourClass.adSize()
I resolved this by replacing
#define AD_SIZE CGSizeMake(320, 50)
in the library's Constants.h with
extern CGSize const AD_SIZE;
and adding
CGSize const AD_SIZE = { .width = 320.0f, .height = 50.0f };
in the library's Constants.m file.
write your constants after Class declaration. like this...
class ForgotPasswrdViewController: UIViewController {
let IS_IPHONE5 = fabs(UIScreen.mainScreen().bounds.size.height-568) < 1;
let Tag_iamTxtf = 101