Interface Builder, @IBOutlet and protocols for delegate and dataSource in Swift
Asked Answered
V

6

30

Can't connect delegate property of CustomView declared as @IBOutlet toViewController in Interface Builder – simply can't establish a connection.

Here's the code

class CustomView: UIView {
     @IBOutlet var delegate: CustomViewDelegate?
}

@objc protocol CustomViewDelegate {
     ...
}


class ViewController: UIViewController, CustomViewDelegate {
     ...
}

@objc is used because of swift protocol, IBOutlet property cannot have non-object type, don't know why protocol CustomViewDelegate: class {} doesn't work.

Anyone else came across something like that?

Viccora answered 3/10, 2014 at 13:52 Comment(1)
Does your UIViewController in Interface Builder have its class specifically set to ViewController? Also, it's normally a requirement (last I checked) that @IBOutlet properties be defined as implicitly unwrapped types, like so: CustomViewDelegate!. This allows them to be nil at instantiation, while allowing you to use them without optional binding after they've been wired up.Frilling
C
58

From the Xcode release notes:

Interface Builder does not support connecting to an outlet in a Swift file when the outlet’s type is a protocol.

Workaround: Declare the outlet's type as AnyObject or NSObject, connect objects to the outlet using Interface Builder, then change the outlet's type back to the protocol.

EDIT: Xcode 9 beta 3 release notes say that this workaround should no longer be necessary.

Coolth answered 3/10, 2014 at 14:5 Comment(8)
Wowzer! This worked, thanks. Apple... please get it together!Blacken
Nope. "Class is not key-value compliant" after the change back to Protocol type.Benny
This only partially worked for me. In one case, where I defined my own delegate on a UIView to be hooked up to a UIViewController, it worked. In another, where I define my delegate on a UIViewController to be hooked up to another UIViewController, it does not. I have tried so many configurations. Any ideas @Coolth what may be going on here?Blacken
"In another, where I define my delegate on a UIViewController to be hooked up to another UIViewController, it does not" That makes no sense to me, @josephap. You can never draw an outlet connection of any kind from one view controller to another view controller in a storyboard, so I can't imagine what you were trying to do in the first place. This has nothing do with Swift at all. It's just a fact about how nibs work. - See my book: apeth.com/iOSBook/ch07.html#SECconnectionBetweenNibsCoolth
Yes, Matt, I realize now. I haven't used Storyboards all that much, and just found out that you can only connect outlets within view controllers, not between them. I did, separately, have a Swift related issue which your answer helped with. Thanks.Blacken
Now I'm torn as to whether I should switch back to Objective-C and/or ditch storyboards in favour of pure code. The main reason I'm using storyboards at the moment is because I'm writing a Cocoa app and I only have experience with iOS. Also it's nice to develop subviews with @IBDesignable.Uproar
It's February 2016 and still not fixed. Luckily there is this workaround.Jovian
Should be fixed in Xcode 9 beta 3, according to the release notes.Coolth
J
16

Adam Waite provides a nice workaround. I however prefer the following solution as it emphasizes the workaround and the extra property can also easily be removed once Xcode gets fixed.

class CustomView: UIView {
    @IBOutlet
    public var delegate: CustomViewDelegate?

    /// Workaround for Xcode bug that prevents you from connecting the delegate in the storyboard.
    /// Remove this extra property once Xcode gets fixed.
    @IBOutlet
    public var ibDelegate: AnyObject? {
        get { return delegate }
        set { delegate = newValue as? CustomViewDelegate }
    }

    func someMethod() {
        // Here we always refer to `delegate`, not `ibDelegate`
        delegate?.onSomethingHappened()
    }
}

@objc protocol CustomViewDelegate {
    ...
}

Hey, is this bug already one and a half years old?

Jovian answered 2/2, 2016 at 14:1 Comment(0)
M
10

An elegant workaround:

#if TARGET_INTERFACE_BUILDER
@IBOutlet open weak var delegate: AnyObject?
#else
open weak var delegate: CustomViewDelegate?
#endif

See: https://github.com/WenchaoD/FSPagerView/blob/master/Sources/FSPagerView.swift#L88

Materiality answered 14/2, 2017 at 13:46 Comment(0)
I
9

Another that's not pretty but:

@IBOutlet weak var ibDelegate: NSObject?
@IBOutlet weak var ibDataSource: NSObject?
var delegate: MultipleButtonViewDelegate? { return ibDelegate as? MultipleButtonViewDelegate }
var dataSource: MultipleButtonViewDataSource? { return ibDataSource as? MultipleButtonViewDataSource }
Isidroisinglass answered 26/9, 2015 at 10:20 Comment(1)
Good answer! Use this way to prevent change type usually the Outlet.Footwork
P
3

This is an old thread, but I thought I'd point out that as of Xcode 9 beta 3, it is now possible to connect a custom delegate written in swift to interface builder.

According to the release notes

Interface Builder now recognizes outlets, actions, and inspectable properties declared on classes which have a Swift protocol extension. (22201035)

// Can connect this to interface builder now    
class MyViewController: UIViewController {
    @IBOutlet weak var myDelegate: TheNewDelegate?
}
Presage answered 15/7, 2017 at 14:0 Comment(0)
F
1

For me, the reason was the table view was nil at the point I attempted to set it's datasource and delegate. This was due to the designated initializer calling initWithNibName:bundle: which does not guarantee initialized connections. Deferring my delegate and datasource setting to viewDidload worked like a charm.

Farina answered 18/8, 2016 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.