Zhang
Zhang

Reputation: 11607

Swift set content compression resistance

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?

Upvotes: 35

Views: 25792

Answers (3)

Ayush Gupta
Ayush Gupta

Reputation: 1077

You can also write this:

self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(<float number>), forAxis: .Vertical)

Example:

self.itemDescription?.setContentCompressionResistancePriority(UILayoutPriority(750), forAxis: .Vertical)  

Upvotes: 0

Jason Cross
Jason Cross

Reputation: 1751

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:

  1. check the documentation
  2. 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

Upvotes: 38

user3163025
user3163025

Reputation:

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.

Upvotes: 11

Related Questions