Saturday, June 28, 2014

Version 2 of swift UI picker view

This version's Goals

The previous iteration of the picker view was great, but only supported pure text.  The next version supports using an object as well as supplying more utility routines to provide data.

For this version I want to clean up the API and make it more general and add the following capabilities:


  • Be able to use an object to supply the data, and make that object available when it is picked
  • Have an easier interface to provide data to the picker view
  • Be able to have varying widths when implementing displaying the text.

These goals will implement the vast majority of pickers required, the main item missing will be the ability to provide custom views for the picker images,

The previous version supported an object, but that object had to implement a protocol and it was not a very general solution. This version will keep the implementation of the object pure, and let the picker view work with pretty much any object.

Calling Implementation

Die data type

My datatype is a very simple die type.  It contains the ability to define the die in the constructor (initializer) of the data type, as well as a method that generates a random number:
@objc class Die
{
    var dieType:Int
    
    init(_ die:Int)
    {
        dieType=die;
    }

    
    func roll () -> Int
    {
        let result=Int(arc4random_uniform(UInt32(dieType)));
        
        return result + 1;
    }

}
Note that my previous solution had the Die implementing a protocol and the picker provider.

Creating the dice

When creating the dice array, we can use the map function to create the array just like before:
let dieTypes=[4,6,8,10,12,20].map{ (var number) -> Die in
            return Die(number);
         };
In this case since the die type is no longer implementing a protocol, I supply a simple closure that takes an AnyObject, casts it to a Die and supplies the text I want:
 let dieToText={ (value:AnyObject)->String in
            let die = value as Die;
            return "d\(die.dieType)";
        };
This is a more general solution, since I can now decorate any object with a simple provider, and it should scale to supplying views as well as title strings.

Creating the picker view

The big change was that I modified the picker view to have a fluent interface.  Objective C and Swift supply some of these capabilities, but in this case I wanted to support many different objects without writing a ton of constructors. In this case I also supply a 'weight'. The sum of all weights is added up (it defaults to 1), and then that is used as the basic to calculate the width of each component.
I also added methods that create numeric ranges.

delegate=PickerProvider().changeCallback(returnValue)
            .addComponentRange(1, maxValue: 20).setWeight(1.5)
            .addComponent(dieTypes)
            .setTextProvider(dieToText).setWeight(3)
            .addComponent(["+","-"]).setWeight(0.5)
            .addComponentRange(0,maxValue:30).setWeight(1.2);

The callback

The callback for when an item is selected will create a roller object with the selected values. Right now I have to do a lot of casting, I'm not sure of a good solution for that, but since I supply the components at the same location as I supply it, then it should be relatively type safe.
 func getDieText(value:AnyObject[])->String
    {
        var numDice:Int=value[0] as Int;
        var dieType:Die=value[1] as Die;
        var modifier:String=value[2] as String;
        var num:Int=value[3] as Int;
        
        currentRoller = Roller(numDice: numDice, dieType: dieType, modSign: modifier, modifier: num)
        
        return "\(numDice)d\(dieType.dieType)\(modifier)\(num)";
        
    }

The Roller is simple:
@objc class Roller
{
    var myDice=0;
    var myDie:Die;
    var myMod:String;
    var myModifier:Int;
    
    init(numDice:Int, dieType:Die, modSign:String, modifier:Int)
    {
        myDice=numDice;
        myDie=dieType;
        myMod=modSign;
        myModifier=modifier;
    }
    func roll()->String
    {
        var result=0;
        var stringResult="\(myDie.dieType):"
        
        for i in 1...myDice
        {
            if i != 1
            {
                stringResult+=",";
            }
            
            let roll = myDie.roll();
            result += roll;
            stringResult += "\(roll)";
        }
        if myModifier > 0
        {
        if (myMod == "+")
        {
            result += myModifier;
            
            stringResult += "+\(myModifier)"
        }
        else
        {
            stringResult += "\(myModifier)"
            result -= myModifier;
        }
        }
        stringResult += "=\(result)"
        return stringResult;
    
    }
}

Picker Provider implementation


//
//  DieRollDataSource.swift
//  SimpleDieRoller
//
//  Created by Jon Lundy on 6/4/14.
//  Copyright (c) 2014 Jon Lundy. All rights reserved.
//

import Foundation
import UIKit

// Component Info keeps information about each component. 
// It uses an array to track the data, as well suppling
// a text provider that converts an item in the array to
// a string.  Note that I have a placeholder for a view
// provider, but that is not implemented yet.
class ComponentInfo
{
    // The items contained within this component.
    var componentArray:Array<AnyObject>;
    // The default text provider just casts the object to value.
    // Convert an item to a string. The default is
    // the built in Swift conversion.
    var textProvider:((AnyObject)->String)={(value:AnyObject) in return "\(value)"};
    
    // Not implemented yet.
    var viewProvider:((AnyObject)->UIView)?=nil;
    // By default just use a weight of 1.
    var weight:Double=1;
    
    // A simple constructor.
    init(_ array:Array<AnyObject>)
    {
        componentArray=array;
    }
}
//the core class for the picker.
class PickerProvider :NSObject, UIPickerViewDataSource,UIPickerViewDelegate
{
    
    var componentArrays:Array<ComponentInfo>=Array<ComponentInfo>();
    var callback:(AnyObject[])->()  ;
   
    
    // Change the default callback, the default is to do
    // nothing.
    func  changeCallback(selectedCall:(AnyObject[])->()) ->PickerProvider
    {
        println("Entering setCallback");
        
        callback=selectedCall;
        return self;
    }
    // Set the text provider for the last component added.
    func setTextProvider(provider:(AnyObject)->String)->PickerProvider
    {
        componentArrays[componentArrays.count-1].textProvider=provider;

        return self;
    }
    // Set the weight for the last callback.
    func setWeight(weight:Double) ->PickerProvider
    {
        componentArrays[componentArrays.count-1].weight=weight;
        return self;
    }
    // Add a component
    func  addComponent(newArray:Array<AnyObject> )->PickerProvider
    {
        var comp = ComponentInfo(newArray);
        componentArrays.append(comp);
        return self;
    }
    
    // Add a range of numbers.
    func addComponentRange(minValue:Int, maxValue:Int)->PickerProvider
    {
        var newarray=getArray(minValue, maxNum:maxValue)
        addComponent(newarray);
        return self;
    }
    
    // Create an array of numbers.
    func getArray(minNum:Int,maxNum:Int) -> Int[]
    {
        var returnValue=Int[]();
        for i in minNum...maxNum
        {
            returnValue.append(i);
        }
        return returnValue;
    }
    
    // The initial method will take a callback for when a selection si made as well
    // as the array of objects to display.
    init()
    {
        callback = { (selectedRows: AnyObject[])->() in
        };

          
    }
    
    // Standard override return the number of rows for the given component.
    func pickerView(pickerview: UIPickerView!,numberOfRowsInComponent component: Int)
    -> Int
    {
        let array=getArray(component);
        return array.count;
        
    }
    
    // returns the number of 'columns' to display.
    func numberOfComponentsInPickerView(pickerView: UIPickerView!) -> Int
    {
        return componentArrays.count;
    }
    
    // Internal function to retrieve a specific array.
    func getArray( compNum:Int) -> Array<AnyObject>
    {
        return componentArrays[compNum].componentArray;
    }
    
    
    


    // Get the value for a given component.  Just use the standard swift to make it into a string.
    func pickerView(pickerView: UIPickerView!, titleForRow row: Int, forComponent component: Int) -> String
    {
        var comp=componentArrays[component];
      
        let value : AnyObject=comp.componentArray[row];
        return comp.textProvider(value);
    }

    
    // Right now weight all arrays equally for the size.  For a real approach we would need to be
    
    func pickerView(pickerView: UIPickerView!, widthForComponent component: Int) -> CGFloat
    {
        var totalWeight:Double=0.0;
        var myComponent=self.componentArrays[component].weight;
        
        for  component in self.componentArrays
        {
            totalWeight += component.weight;
        }
        let width:Double=pickerView.frame.width-10;
        let value=width / totalWeight * myComponent;
        return value;
    }
    
    func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int)
    {
        var returnValue=AnyObject[]();
        for i in 0..componentArrays.count
        {
            let selected=pickerView.selectedRowInComponent(i)
            let array=getArray(i);
            let value=array[selected]
            returnValue.append(value);
        }
        self.callback(returnValue);

    }


    

}

What's next:

The next version should support:
The ability to have a custom view in addition to pure text in the picker.
The ability to have the contents of some of the components change based upon selections in the previous components (for example the date picker won't allow you to pick Day 31 for February.

Wednesday, June 18, 2014

Cannot downcast from 'AnyObject' to non-@objc protocol type

The next item I wished to do was to pass in an object to my picker and let it be a choice.  I defined a simple object type of Die, which represents a type of die.

I could not find a way to have interpolation work with the object, for "\(value)", so I decided to have a protocol that can be checked for.

I defined the following protocol:
protocol ProvidesTextValue
{
    var textValue:String { get}

}

With the following usage:
 func pickerView(pickerView: UIPickerView!, titleForRow row: Int, forComponent component: Int) -> String
    {
        let array=getArray(component);

        let value : AnyObject=array[row];
        if value is ProvidesTextValue
        {
            let textProvider=value as ProvidesTextValue
            return "\(textProvider.textValue)"
        }
            return "\(value)";
    }

Which resulted in the error:
DieRollDataSource.swift:61:18: Cannot downcast from 'AnyObject' to non-@objc protocol type 'ProvidesTextValue'

The Swift Programming guide provides the following guidance:

NOTE
You can check for protocol conformance only if your protocol is marked with the @objc attribute, as seen for the HasArea protocol above. This attribute indicates that the protocol should be exposed to Objective-C code and is described in Using Swift with Cocoa and Objective-C. Even if you are not interoperating with Objective-C, you need to mark your protocols with the @objc attribute if you want to be able to check for protocol conformance.
Note also that @objc protocols can be adopted only by classes, and not by structures or enumerations. If you mark your protocol as @objc in order to check for conformance, you will be able to apply that protocol only to class types.

So I had to modify the protocol definition to be:
@objc protocol ProvidesTextValue
{
    var textValue:String { get}
}


This resulted in a runtime error with the very 'useful' error message:
** NSForwarding: warning: object 0x110fa0000 of class '_TtC15SimpleDieRoller3Die' does not implement methodSignatureForSelector: -- trouble ahead

I then modified the class definition with @objc as well, which solved the problem:
@objc class Die : ProvidesTextValue
{
    var dieType:Int
    
    init(die:Int)
    {
        dieType=die;
    }

    var textValue:String { return "\(dieType)" }


}

Monday, June 16, 2014

A first cut at a swift library to display a UIPickerView

Below is my first cut at creating a class that manages the data for a picker view.  The standard examples have your view controller managing the rows and data for the picker.  I think that this is a bad connection of concerns, so I wanted a generic class that will manage the data for me.

The example is a simple polyhedral dice roller, which lets you pick a number of dice, of a certain type, and add a modifier.

Below is a screenshot of the picker.

As you can see I just have a simple picker at the top of the screen.  Right now all it does is print out the results to the console:

selected row [1, d8, +, 0]
selected row [3, d8, +, 0]

selected row [3, d8, +, 2]


The view code is just concerned with establishing the data that we wish to display:

 var delegate:PickerProvider? = nil;
  
    init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!)
    {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    
    func getArray(minNum:Int,maxNum:Int) -> Int[]
    {
        var returnValue=Int[]();
        for i in minNum...maxNum
        {
            returnValue.append(i);
        }
        return returnValue;
    }
    
    
    init(coder aDecoder: NSCoder!)
    {
        delegate=nil;
        super.init(coder:aDecoder)
    }
   
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Create an array of numbers for dice.  Use the map function to prepend a 'd' in
        // front of them.
        let dieTypes=[4,6,8,10,12,20].map{ (var number) -> String in
            return "d\(number)";
         };
        
       
        // Note that this is actually a (selectedRows:AnyObject[])->() even though it is
        // declared as (selectedRows:AnyObject[]).  If you declare the paraemter of the method
        // to be (value:AnyObject[]) you will get a compile error.
        let returnValue = { (selectedRows: AnyObject[])->() in
            println("selected row \(selectedRows)");
        };
        
        let numDice=getArray(1,maxNum:20);
        let modifiers=getArray(0,maxNum:30);
        let modRolls=["+","-"];
        
        // Pass the callback to the new delegate, as well as the list of arrays.
         delegate=PickerProvider(selectedCall:returnValue
            ,arrays:numDice,dieTypes,modRolls,modifiers);

        // Set the picker view to use the subclass as both a delgate and dataSource.
        pickerView.dataSource=delegate;
        pickerView.delegate=delegate;
    }

    @IBOutlet var pickerView : UIPickerView
  
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    

I started from a simple 'single view' template, added a picker view, and set the delegate to be 'pickerView'.

The core of the logic is in the PickerProvider class, this class is designed to be a general supplier of picker information.  Right now it only displays text titles, and will subdivide the picker into equal segments.

class PickerProvider :NSObject, UIPickerViewDataSource,UIPickerViewDelegate
{
    
    var myarrays:Array<Array<AnyObject>>;
    var callback:(value:AnyObject[])->();
   
    
    // The initial method will take a callback for when a selection si made as well
    // as the array of objects to display.
    init(selectedCall:(value:AnyObject[])->(), arrays: Array<AnyObject> ... )
    {
        myarrays=arrays;
        callback=selectedCall
    }
    
    // Standard override return the number of rows for the given component.
    func pickerView(pickerview: UIPickerView!,numberOfRowsInComponent component: Int)
    -> Int
    {
        let array=getArray(component);
        return array.count;
        
    }
    
    // returns the number of 'columns' to display.
    func numberOfComponentsInPickerView(pickerView: UIPickerView!) -> Int
    {
        return myarrays.count;
    }
    
    // Internal function to retrieve a specific array.
    func getArray( compNum:Int) -> Array<AnyObject>
    {
        return myarrays[compNum];
    }
    
    
    


    // Get the value for a given component.  Just use the standard swift to make it into a string.
    func pickerView(pickerView: UIPickerView!, titleForRow row: Int, forComponent component: Int) -> String
    {
        let array=getArray(component);

        let value : AnyObject=array[row];
        return "\(value)";
    }
    
    // Right now weight all arrays equally for the size.  For a real approach we would need to be
    
    func pickerView(pickerView: UIPickerView!, widthForComponent component: Int) -> CGFloat
    {
        let numComp:NSNumber=self.numberOfComponentsInPickerView(pickerView);
        let width:Double=pickerView.frame.width;
        let value=width / numComp.doubleValue;
        return value;
    }
    
    func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int)
    {
        var returnValue=AnyObject[]();
        for i in 0..myarrays.count
        {
            let selected=pickerView.selectedRowInComponent(i)
            let array=getArray(i);
            let value=array[selected]
            returnValue.append(value);
        }
        self.callback(value:returnValue);

    }
    

}

The core of the logic is in the PickerProvider class, this class is designed to be a general supplier of picker information.  Right now it only displays text titles, and will subdivide the picker into equal segments.

Overall I would say that Swift makes for better written code.  I've done similiar constructions in Objective-C, and this is far cleaner.  The code is very readable, and barring a few quirks of Swift fairly easy to write.

This examples shows several usages of Swift constructs, the Closures are easier to write than blocks and are first class citizens.  The map function is very neat and shows the ability to transform lists.

List creation and manipulation is far less verbose than in Objective-C, as is displaying primitives as a string.

Sunday, June 15, 2014

Holding closures in a member variable

The next problem that was not obvious was that I wanted to have a callback to my main routine from the picker data source.   After some experimentation I declared the following:

    var callback:(value:AnyObject[]);

And I called the initializer with:

init(selectedCall:(value:AnyObject[]), arrays: Array<AnyObject> ... )


However when I declared the following:
 let returnValue = { (selectedRows: AnyObject[]) in
            println("selected row \(selectedRows)");
        };
        
        // Pass the callback to the new delegate, as well as the list of arrays.
        var delegate=PickerProvider(selectedCall:returnValue

            ,arrays:numDice,dieTypes,modRolls,modifiers);

This resulted in the compile time error of:
'(value: @lvalue AnyObject[]) -> $T3' is not identical to '(value: AnyObject[])'
'(AnyObject[]) -> ()' is not convertible to 'AnyObject[]'

What really happened is explained in Apple's swift manual in a note under functions:

Strictly speaking, the sayGoodbye function does still return a value, even though no return value is defined. Functions without a defined return type return a special value of type Void. This is simply an empty tuple, in effect a tuple with zero elements, which can be written as ().

The Apple manual has a lot of explanations for how to use closures for existing libraries, but not a lot for how to write your own functions to use closures.  The problem is that my return Value is actually

(selectedRows: AnyObject[])->() 

While the compiler will currently let you declare variables and member functions without the ->(), it will not consider those the same as the real function.

The solution to this one is to declare my code as:

    var callback:(value:AnyObject[])->();
With an initializer of:
    init(selectedCall:(value:AnyObject[])->(), arrays: Array<AnyObject> ... )



Swift is Reference counted, NOT garbage collected

The first error I encountered was two fold, the difference between reference counting and garbage collection.  I was attempting to create a class to manage the data for a multi-level picker, and I dislike the standard practice Apple uses to have the view controller manage the pickers (I think it belongs in the subclass).

In my first attempt I tried the following code: (which would work great in Java)
 // Pass the callback to the new delegate, as well as the list of arrays.
        var delegate=PickerProvider(selectedCall:returnValue
            ,arrays:numDice,dieTypes,modRolls,modifiers);

        // Set the picker view to use the subclass as both a delgate and dataSource.
        pickerView.dataSource=delegate;
        pickerView.delegate=delegate;

When I did this, I would end up with a very obscure error:

014-06-15 21:16:11.737 SimpleDieRoller[11717:13412066] -[CALayerArray numberOfComponentsInPickerView:]: unrecognized selector sent to instance 0x10bf05330
2014-06-15 21:16:11.742 SimpleDieRoller[11717:13412066] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CALayerArray numberOfComponentsInPickerView:]: unrecognized selector sent to instance 0x10bf05330'
*** First throw call stack:
(
 0   CoreFoundation                      0x000000010046ee35 __exceptionPreprocess + 165
 1   libobjc.A.dylib                     0x0000000101f1a9a0 objc_exception_throw + 45
 2   CoreFoundation                      0x00000001004751cd -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
 3   CoreFoundation                      0x00000001003cf89c ___forwarding___ + 988
 4   CoreFoundation                      0x00000001003cf438 _CF_forwarding_prep_0 + 120
 5   UIKit                               0x0000000100cd8398 -[UIPickerView _updateSelectedRows] + 69
 6   UIKit                               0x0000000100cd84ce -[UIPickerView didMoveToWindow] + 78
 7   UIKit                               0x0000000100d57052 -[UIView(Internal) _didMoveFromWindow:toWindow:] + 1496
 8   UIKit                               0x0000000100d56d41 -[UIView(Internal) _didMoveFromWindow:toWindow:] + 711
 9   UIKit                               0x0000000100d4f8e5 __45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke + 128
 10  UIKit                               0x0000000100d4f856 -[UIView(Hierarchy) _postMovedFromSuperview:] + 437
 11  UIKit                               0x0000000100d5968e -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1610
 12  UIKit                               0x0000000100d29c3f -[UIWindow addRootViewControllerViewIfPossible] + 452
 13  UIKit                               0x0000000100d29e22 -[UIWindow _setHidden:forced:] + 276
 14  UIKit                               0x0000000100d369ad -[UIWindow makeKeyAndVisible] + 42
 15  UIKit                               0x0000000100ce4db9 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2753
 16  UIKit                               0x0000000100ce7529 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1222
 17  UIKit                               0x0000000100ce6510 -[UIApplication workspaceDidEndTransaction:] + 19
 18  CoreFoundation                      0x00000001003a5bbc __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
 19  CoreFoundation                      0x000000010039b095 __CFRunLoopDoBlocks + 341
 20  CoreFoundation                      0x000000010039a84c __CFRunLoopRun + 844
 21  CoreFoundation                      0x000000010039a296 CFRunLoopRunSpecific + 470
 22  UIKit                               0x0000000100ce5f26 -[UIApplication _run] + 413
 23  UIKit                               0x0000000100ce9308 UIApplicationMain + 2994
 24  SimpleDieRoller                     0x0000000100006c3d top_level_code + 77
 25  SimpleDieRoller                     0x0000000100006c7a main + 42
 26  libdyld.dylib                       0x0000000102478145 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException


It was not obvious from this what was happening.  What was really happening is that the 'var delegate' local variable was being allocated and given a count of 1.  It was then assigned to
'delegate' and datasource' of the picker.

However those properties are declared:
var dataSource: UIPickerViewDataSource! // default is nil. weak reference
    var delegate: UIPickerViewDelegate! // default is nil. weak reference

The fact that they were weak references, means that when the initializer left scope, the count was decremented, the variable was no longer valid, so the picker called an invalid reference.  

This was an easy fix, I just made a member variable that held my picker source, however the error was so obscure it took a while to find it out.



Introduction to swift

Like most developers I found the introduction of swift to be very interesting.  One of the great barriers to iPhone development was the learning curve for Objective-C.  Swift seems like a very interesting development.  Instead of supporting an existing language they have added a new language that has attempted to diverge from C in many fashions.

Swift has an interesting grab bag of features that it looks like the language designers decided to toss in.


  • Explicit methods for having 'no value' instead of using null.  This means that you can bypass the need that Java has for Double vs double, etc..
  • Enhanced for loops that try to support more expressiveness in terms of what to iterate over
  • Support for more advance switch statements, with no default fall through (a cause of bugs for decades), but which supports 

For a greater introduction to swift the best place is of course Apple's Swift introduction.  It covers the basics of the language, but so far in my brief experiences with Swift there are some gotchas that I have found which were not immediately obvious from the book.