34

I have this code that creates a view and applies a gradient to it.

import UIKit
import QuartzCore


let rect : CGRect = CGRectMake(0,0,320,100)

var vista : UIView = UIView(frame: rect)

let gradient : CAGradientLayer = CAGradientLayer()
gradient.frame = vista.bounds

let cor1 = UIColor.blackColor()
let cor2 = UIColor.whiteColor()

let arrayColors = [cor1.CGColor, cor2.CGColor]

gradient.colors = arrayColors

view.layer.insertSublayer(gradient, atIndex:0)

Xcode is giving me no compile error, but the code is crashing on the line

let arrayColors = [cor1.CGColor, cor2.CGColor]

with the message array element cannot be bridged to Objective-C

In fact I was expecting it to crash there, because I am not sure how I can create an array of CGColors on Swift. The surprise here is Xcode mentioning Objective-C. In my mind I was creating a CGColorRef in swift...

Any clues? Why is it mentioning Objective-C and how do I solve this?

jww
  • 97,681
  • 90
  • 411
  • 885
Duck
  • 34,902
  • 47
  • 248
  • 470

5 Answers5

29

The reason Objective-C is mentioned is because UIKit and QuartzCore are Objective-C frameworks. In particular, gradient.colors = arrayColors is calling an Objective-C method that expects an NSArray.

This seems like a bug, as Apple's documentation makes it sound like that the array should auto-bridge to an NSArray so long as the items in the array can be considered AnyObject:

When you bridge from a Swift array to an NSArray object, the elements in the Swift array must be AnyObject compatible. For example, a Swift array of type Int[] contains Int structure elements. The Int type is not an instance of a class, but because the Int type bridges to the NSNumber class, the Int type is AnyObject compatible. Therefore, you can bridge a Swift array of type Int[] to an NSArray object. If an element in a Swift array is not AnyObject compatible, a runtime error occurs when you bridge to an NSArray object.

You can also create an NSArray object directly from a Swift array literal, following the same bridging rules outlined above. When you explicitly type a constant or variable as an NSArray object and assign it an array literal, Swift creates an NSArray object instead of a Swift array.

For now, a work around would be either to declare arrayColors as an NSArray:

let arrayColors: NSArray = [cor1.CGColor, cor2.CGColor]

Or to declare it as taking AnyObject:

let arrayColors: Array <AnyObject> = [cor1.CGColor, cor2.CGColor]

BergQuester
  • 6,167
  • 27
  • 39
  • ah, I see. I did not understand the second line of code. What are you saying there? make me an array where every element is ? but this is not the default behavior? – Duck Jun 09 '14 at 04:07
  • 1
    The default behavior is that the array items are whatever you place into the array. e.g. only strings, integers, etc. In this case it only takes `CGColorRef`, which is what the `CGColor` method returns. `AnyObject` would be any mix of types, so long as they are `AnyObject` compatible, rather than a single type. As the bridging requires the ability to move the array type to `AnyObject`, declaring it as such from the get go gets us over the Objective-C bridging issue. – BergQuester Jun 09 '14 at 04:15
  • Yeah, there's a lot to absorb with Swift and it's going to evolve over the next several months. I'm guessing the Objective-C bridge is going to have a fair number of gotchas for the time being... I'd suggest to try playing with arrays by creating a variable array of strings and then trying to add an integer. Then, declare the array as taking `AnyObject` and see what happens. Also, checkout the Integrating Swift with Objective-C and the Swift Interoperability In Depth talks at https://developer.apple.com/videos/wwdc/2014/ The system frameworks are still Objective-C frameworks.... – BergQuester Jun 09 '14 at 04:25
  • thanks but later I realized that the gradient function I was using is pretty strange. Even it being a ObjC object the gradient color is defined in CGColorRef what means that the gradient drawing methos is, as expected, written in C as most of Quartz so, the gradient itself appears to be just a ObjC wrapper to C. I feel that this swift change is the first step to merge iOS and OSX in terms of frameworks. It is unproductive having NSColor/UIColor, NSRect/CGRect and stuff like that. – Duck Jun 09 '14 at 17:56
19

This runtime error can also be triggered if it tries to bridge an array of type [MySwiftProtocol] over to Objective-C.

The solution is to mark your protocol with @objc:

@objc protocol MySwiftProtocol {
   // ...
}
itsji10dra
  • 4,603
  • 3
  • 39
  • 59
Andrew Ebling
  • 10,175
  • 10
  • 58
  • 75
  • 2
    I extended an Objective-C class to conform to a Swift protocol, and was unable to bridge an array of that class type to the protocol type until doing this. The error I had was slightly different: "fatal error: array cannot be bridged from Objective-C". – Andrew Watt May 06 '15 at 15:13
4

I found that I could fix the problem by explicitly using CGColorRef rather than CGColor for my colours, e.g.:

    var bottomColour:CGColorRef = UIColor.redColor().CGColor
    var topColour:CGColorRef = UIColor(red: 255.0/255.0, green: 123.0/255.0, blue: 37.0/255.0, alpha: 1.0).CGColor

    gradientLayer.colors = [bottomColour, topColour]

...worked fine, without any NSArray or AnyObject casting. If I take out the explicit CGColorRef in the type declarations for the colours, I get the "array element cannot be bridged to Objective-C" error.

Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
3

With Swift arrays, you can call bridgeToObjectiveC() on them, and they'll turn into NSArrays. The same is true of Swift dictionaries.

MaddTheSane
  • 2,981
  • 24
  • 27
2

For anyone trying to get this to work in Swift, you can do the following:

let arrayColors: NSArray = [UIColor.blackColor().CGColor as AnyObject, UIColor.clearColor().CGColor as AnyObject]

I noticed that in my Objective-C code that I was casting the CGColorRef types to id. Not sure why though. If anyone has a reason why that would be great!

Stephan Leroux
  • 771
  • 5
  • 8
  • 1
    The reason why `CGColorRef` can be bridged to `id` is because `CGColorRef` can use Objective C/Cocoa retain-release functions. Under Objective C/ARC, this would need to be bridged, but Swift can handle the memory of a CoreFoundation object automatically. – MaddTheSane Oct 09 '14 at 20:15