user1107173
user1107173

Reputation: 10744

Obj-C to Swift: Block to Closure/Delegate

Keeping ViewControllers thin and using MVVMC has helped me maintain so much easier. Obj.io has a very good tutorial on their site. Unfortunately, the tutorial is only in Objective-C.

I'm trying to switch over to Swift and was moving very swiftly, until I reached the block that's used to configure the cell.

In the tutorial they created a block typedef:

typedef void (^TableViewCellConfigureBlock)(id cell, id item);

that returns the cell in cellForRowAtIndexPath upon creating the cell.

Below is some of the code and here is the entire project: Project

void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
   cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
                        cellIdentifier:PhotoCellIdentifier       
                        configureCellBlock:configureCell];

self.tableView.dataSource = photosArrayDataSource;

cellForRowAtIndexPath:

- (UITableViewCell*)tableView:(UITableView*)tableView 
        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
    id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                              forIndexPath:indexPath];
    id item = [self itemAtIndexPath:indexPath];
    configureCellBlock(cell,item);
    return cell;
}

Question: How can I accomplish the same in Swift 1.2+? Should I use a closure, protocol delegate, etc.? And more importantly, how do I call it in cellForRowAtIndexPath?

I'm new to Swift so solution code would be greatly appreciated. Thanks.

Upvotes: 2

Views: 787

Answers (3)

libec
libec

Reputation: 1554

In Swift 2.0, you can use generics (as seen here - sample github repo), that's easy.

But in Swift 1.2 you need to drop the generics (can't make generic class inherit from objc type). Use protocols instead. This way you don't need to pass the cell configuration closure and use the power of protocols.

You'd go something like this:

protocol CellDescription {
    var title: String { get }
    var image: UIImage { get }
}

Your models which you wanna show in the tableview then need to implement the protocol

class Question: CellDescription {
    var image: Image { 
        return UIImage(named: "whatever")! 
    }
    var title: String {
        return "cell title"
    }
}

You can then have UITableViewController with property items: [CellDescription] and create it like this:

class CustomTableViewController: UITableViewController {
    var items: [CellDescription] = []

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(pickerCell, forIndexPath: indexPath)
        cell.textLabel?.text = items[indexPath.item].title
        cell.imageView?.image = items[indexPath.item].image
        return cell
    }
}

Upvotes: 1

Anusha Kottiyal
Anusha Kottiyal

Reputation: 3905

You can use the swift closures here. Define closureType as,

typealias TableViewCellConfigureBlock = (cell : AnyObject , item : AnyObject) -> Void

Then define the closure like,

let configureCellBlock : TableViewCellConfigureBlock = { (cell : PhotoCell, photo : Photo) in

        cell.label.text = photo.name
    } as TableViewCellConfigureBlock

Then in cellForRowAtIndexPath: you can use it like as before,

configureCellBlock(cell: yourCell, item: yourPhotoItem)

Upvotes: 4

Rikki Gibson
Rikki Gibson

Reputation: 4337

Closures are what you use instead of blocks. You implement the protocol in Swift just as you would in Objective-C. It's necessary to inherit from NSObject to conform to this protocol. Generics would be nice here, but as of Swift 1.2 they can't be used for interop with Objective-C.

class ArrayDataSource : NSObject, UITableViewDataSource {
    let array: [AnyObject]
    let identifier: String
    let configureCell: (UITableViewCell, AnyObject) -> ()

    init(array: [AnyObject], identifier: String, configureCell: (UITableViewCell, AnyObject) -> ()) {
        self.array = array
        self.identifier = identifier
        self.configureCell = configureCell
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let item: AnyObject = array[indexPath.row]
        let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as! UITableViewCell
        configureCell(cell, item)
        return cell
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return array.count
    }
}

let tableView = UITableView()
tableView.dataSource = ArrayDataSource(array: ["foo", "bar", "baz"], identifier: "foo", configureCell: { (cell, obj) -> () in
    cell.textLabel?.text = "bar"
})

Upvotes: 1

Related Questions