advertising production and investment banking • Campaign management, media booking, time and expenses, FX trading, business activity monitoring and approval systems • ColdFusion, MS SQL, JavaScript, ActionScript 3, Apache Flex
- contains a UIPickerView for selecting from a list of preset colours • An RGBWidget - contains three UISliders for setting the red, green and blue components • A SavedColorsGrid - contains a UICollectionView to display the user’s selected colours • A ColorSwatch to display the current colour
It has properties for its color and name • It has a constant, immutable uuid let uuid = NSUUID().UUIDString • It is equatable via a custom operator func == (lhs: NamedColor, rhs: NamedColor) -> Bool { return lhs.uuid == rhs.uuid }
use Xcode’s Interface Builder and create and layout components in Swift • The advantage is I know what my code is doing and I don’t need to tweak generated code. • The disadvantage is that I need to keep running in the simulator to see how my interface looks.
have an isLandscape() method • I used a Swift extension to add that functionality extension CGRect { func isLandscape() -> Bool { return self.width > self.height } } • Segregates responsibility and helps make code self documenting
code in each of the four components, I created a Panel class which each extends. class Panel: UIControl { override init(frame: CGRect) { super.init(frame: frame) layer.backgroundColor = UIColor.lightGrayColor().CGColor layer.shadowColor = UIColor.blackColor().CGColor layer.shadowOffset = CGSize(width: 0, height: 0) layer.shadowOpacity = 0.5 layer.cornerRadius = 5 layer.borderColor = UIColor.darkGrayColor().CGColor layer.borderWidth = 1 layer.cornerRadius = 5 } }
layer’s background colour via a didSet observer on its currentColor property: class ColorSwatch: Panel { var currentColor:UIColor = UIColor.blackColor() { didSet { layer.backgroundColor = currentColor.CGColor } } }
values of a colour. • These are created and held in an array in init(): redWidget = SliderWidget(frame: CGRectZero) greenWidget = SliderWidget(frame: CGRectZero) blueWidget = SliderWidget(frame: CGRectZero) widgets = [redWidget, greenWidget, blueWidget] • Then added as sub-views by looping over the array: override func didMoveToSuperview() { for (i: Int, widget: SliderWidget) in enumerate(widgets) { addSubview(widget) widget.title = widgetTitles[i] widget.addTarget(self, action: "sliderChangeHandler", forControlEvents: UIControlEvents.ValueChanged) } } • When any slider is changed, the sliderChangeHandler() is invoked.
{ enableObserversOnColorComponents = false currentColor = UIColor.colorFromFloats(redComponent: redWidget.value, greenComponent: greenWidget.value, blueComponent: blueWidget.value) enableObserversOnColorComponents = true } • currentColor uses a didSet observer to set the individual color components and dispatch its own control event: var currentColor: UIColor = UIColor.blackColor() { didSet { redComponent = currentColor.getRGB().redComponent greenComponent = currentColor.getRGB().greenComponent blueComponent = currentColor.getRGB().blueComponent sendActionsForControlEvents(UIControlEvents.ValueChanged) } } • Those individual components set the respective sliders - if the current colour hasn’t been set by a slider: private var greenComponent: Float = 0 { didSet { if enableObserversOnColorComponents { greenWidget.value = greenComponent } } }
UIPickerViewDataSource and UIPickerViewDelegate • It contains an immutable array of preset colours • It has a currentColor property which refers to the selected colour which is set: • From the picker itself • When the user changes the sliders • When the user selects a saved color
externally, it dispatches a control event and attempts to find a match in its array of presets. If there’s no match, it sets the picker to the ‘custom colour’ item. var currentColor : UIColor = UIColor.blackColor() { didSet { sendActionsForControlEvents(.ValueChanged) var matchFound = false if let _colorIndex = findColorInNamedColors(colors, currentColor) { spinner.selectRow(_colorIndex, inComponent: 0, animated: true) matchFound = true } if !matchFound { spinner.selectRow(0, inComponent: 0, animated: true) spinner.reloadComponent(0) } } }
UICollectionViewDataSource and UICollectionViewDelegate • It has a colors array which is a collection of the colours saved by the user • It also supports a ‘tap-hold’ gesture to pop up a little menu to delete saved colours
item renderer, NamedColorItemRenderer, but the collection view’s code is slightly different. • I need to register the renderer class: uiCollectionView.registerClass(NamedColorItemRenderer.self, forCellWithReuseIdentifier: "Cell") • Then use one of the delegate functions to define the cell for a given index: func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as NamedColorItemRenderer cell.hasBackground = true cell.editable = true cell.namedColor = colors[indexPath.item] return cell }
picker and collection view • Contains an editable text input, a readonly label and a colour swatch • When its namedColor property changes, the sub-components are updated using a didSet observer: var namedColor: NamedColor? { didSet { if let _namedColor = namedColor { label.text = _namedColor.name textInput.text = _namedColor.name swatch.backgroundColor = _namedColor.color } } }
text input by a didSet observer on its editable property: var editable: Bool = false { didSet { if editable { label.removeFromSuperview() addSubview(textInput) } else { textInput.removeFromSuperview() addSubview(label) } } }
respond to edits. • Because NamedColor is a class, the renderer holds a reference to it and changes to it are available up in the view controller func textFieldShouldReturn(textField: UITextField) -> Bool { textField.endEditing(true) return true } func textFieldDidEndEditing(textField: UITextField) { if namedColor != nil { namedColor!.name = textInput.text } }
as a sub-view and laid out in the view controller. • The toolbar contains three items • Save • Tweak • About • Which are added in populateToolbar() func populateToolbar() { let saveBarButtonItem = UIBarButtonItem(title: "Save", style: UIBarButtonItemStyle.Plain, target: self, action: "saveCurrentColor") let tweakBarButtonItem = UIBarButtonItem(title: "Tweak", style: UIBarButtonItemStyle.Plain, target: self, action: "showTweakMenu:") let aboutBarButtonItem = UIBarButtonItem(title: "About", style: UIBarButtonItemStyle.Plain, target: self, action: "showAbout") let spacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil) toolbar.setItems([saveBarButtonItem, spacer, tweakBarButtonItem, spacer, aboutBarButtonItem], animated: true) }
sets the currentColor on the control that hasn’t changed: var currentColor: UIColor = UIColor.brownColor() { didSet { if !(rgbWidget.currentColor == currentColor) { rgbWidget.currentColor = currentColor } if !(colorPicker.currentColor == currentColor) { colorPicker.currentColor = currentColor } colorSwatch.currentColor = currentColor } }
which contains all the colours the user has saved • A didSet() observer of this array passes it into the saved colours grid when changed var savedColors: [NamedColor] = [NamedColor]() { didSet { savedColorsGrid.colors = savedColors } }
application to persist data, the saved user defined colours, after restarting. • If you forget to check the ‘use Core Data’ option, create a new project with that checked and copy the guts of AppDelegate into your existing project.
code to get started. • I create a context in the view controller: appDelegate = UIApplication.sharedApplication().delegate as AppDelegate managedObjectContext = appDelegate.managedObjectContext! • To load saved items from a previous session, invoke executeFetchRequest on the context: fetchResults = managedObjectContext.executeFetchRequest(fetchRequest, error: nil)
as an array of NamedColor and convert each retrieved item as an instance of NamedColor func loadSavedColors() { let fetchRequest = NSFetchRequest(entityName: "NamedColorEntity") if let fetchResults = managedObjectContext.executeFetchRequest(fetchRequest, error: nil) as? [NamedColorEntity] { var loadedColors = [NamedColor]() for namedColorEntity in fetchResults { loadedColors.append(NamedColorEntity.createInstanceFromEntity(namedColorEntity)) } savedColors = loadedColors } } class func createInstanceFromEntity(entity: NamedColorEntity) -> NamedColor! { let name = entity.colorName let color = UIColor.colorFromNSNumbers(redComponent: entity.redComponent, greenComponent: entity.greenComponent, blueComponent: entity.blueComponent) var namedColor = NamedColor(name: name, color: color) namedColor.entityRef = entity return namedColor }
NamedColor, I can persist edits inside the domain object itself: var name : String { didSet { if let _entityRef = entityRef { _entityRef.colorName = name let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate appDelegate.saveContext() } } } • This could be considered a little naughty or could be considered “Domain Driven Development”
To delete in Core Data, I compare the saved colours grid’s colors array to the view controller’s and invoke deleteObject() on the missing one: func savedColorsGridDeleteHandler() { for color in savedColors { if find(savedColorsGrid.colors, color) == nil { if let _entityRef = color.entityRef { managedObjectContext.deleteObject(_entityRef) } } } appDelegate.saveContext() savedColors = savedColorsGrid.colors }