Tuesday, 14 October 2014

Lighter view controllers in Swift

A classic problem in Objective-C is the one of avoiding MVC . Massive View Controller that is.  Code gets copy-pasta'd into multiple view controllers and all looks a bit like this.




objc.io does a great job of explaining how to slim down your view controllers but what Swift has done is make this process even easier.

The core of the process is to shift the boiler plate of creating datasources/delegates out to an object which owns that flow and inject that datasource into each of the relevant view controllers





Swift functions are full class objects which can be flung around the place with great ease. Its true that Objective-C allows you to store and copy blocks to much the same effect but it never sat well and always felt a bit funky especially with the need to draw out the block signatures and  the need to do weak self reference dances .

In Swift this changes and feels very natural.

So where in Objective-C you might do this.

typedef void (^FreakyBlock)(UICollectionViewCell *cellToConfigure, DataThing *thing); 

@class MagicDivorcedCollectionViewDatasource <UICollectionViewDataSource>
@end 

@implementation MagicDivorcedCollectionViewDatasource
@property (copy) FreakyBlock cellConfigurationBlock

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

     UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@”Cell” forIndexPath:(NSIndexPath *)indexPath];
    //common setup
    //pass the cell to the block for view specific decoration
    if(cellConfigurationBlock) {
       cellConfigurationBlock(cell)
    }
}

and then within the view controller

-(void)setupDatasource {

   //here might be where you inject a managed object context at init
   self.collectionDatasource = [[MagicDivorcedCollectionViewDatasource alloc] init];

   __block typeof(self) blockself = self;

   //view specific routine for decorating generic cell
   self.collectionDatasource.cellConfigurationBlock = ^(UICollectionViewCell *cellTo  Configure, DataThing *thing) {

   cell.label = thing.name;
   cell.otherLabel = thing.subname;
   cell.backgroundColor = [blockself backgroundColorForThing:thing];

   }
}



two files and so much boiler plate later, so freaking bored now.

This bunch of code burns down to this in Swift


class MagicDivorcedCollectionViewDatasource: UICollectionViewDataSource {

   var cellConfigurator:((UICollectionViewCell,DataThing)->Void)?

   func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

   let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath:indexPath) as UICollectionViewCell;
   
   let dataObject = self.dataObjectForIndexPath(indexPath) as DataThing
   
   cellConfigurator?.(cell,dataObject)

   return cell
 
}


and in your view controller


func configureCell(cell:UICollectionViewCell,thing:DataThing) {

   cell.label.text = thing.name
   cell.otherLabel.text = thing.subname
   cell.backgroundColor = self.backgroundColorForThing(thing)

}

func setupDatasource() {

   collectionDatasource = MagicDivorcedCollectionViewDatasource()

   //simply pass the function to the class. its an object.
   collectionDatasource.cellConfigurator = configureCell

}

It feels a lot less clanky and more amenable to use.

No comments:

Post a Comment