         return (row.count >= 3 && !row[0].isCharacter && row[1].isCharacter)
     }
     
-    func layoutCharacterRow(row: [Key], modelToView optModelToView: [Key:KeyboardKey]?, keyWidth: CGFloat, gapWidth: CGFloat, frame: CGRect) {
-        if let modelToView = optModelToView {
-            let keySpace = CGFloat(row.count) * keyWidth + CGFloat(row.count - 1) * gapWidth
-            var actualGapWidth = gapWidth
-            var sideSpace = (frame.width - keySpace) / CGFloat(2)
-            
-            // TODO: port this to the other layout functions
+    func layoutCharacterRow(row: [Key], keyWidth: CGFloat, gapWidth: CGFloat, frame: CGRect) -> [CGRect] {
+        var frames = [CGRect]()
+        
+        let keySpace = CGFloat(row.count) * keyWidth + CGFloat(row.count - 1) * gapWidth
+        var actualGapWidth = gapWidth
+        var sideSpace = (frame.width - keySpace) / CGFloat(2)
+        
+        // TODO: port this to the other layout functions
+        // avoiding rounding errors
+        if sideSpace < 0 {
+            sideSpace = 0
+            actualGapWidth = (frame.width - (CGFloat(row.count) * keyWidth)) / CGFloat(row.count - 1)
+        }
+        
+        var currentOrigin = frame.origin.x + sideSpace
+        
+        for (k, key) in enumerate(row) {
+            let roundedOrigin = rounded(currentOrigin)
+            
             // avoiding rounding errors
-            if sideSpace < 0 {
-                sideSpace = 0
-                actualGapWidth = (frame.width - (CGFloat(row.count) * keyWidth)) / CGFloat(row.count - 1)
-            }
-            
-            var currentOrigin = frame.origin.x + sideSpace
-            
-            for (k, key) in enumerate(row) {
-                if let view = modelToView[key] {
-                    let roundedOrigin = rounded(currentOrigin)
-                    
-                    // avoiding rounding errors
-                    if roundedOrigin + keyWidth > frame.origin.x + frame.width {
-                        view.frame = CGRectMake(rounded(frame.origin.x + frame.width - keyWidth), frame.origin.y, keyWidth, frame.height)
-                    }
-                    else {
-                        view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, keyWidth, frame.height)
-                    }
-                    
-                    currentOrigin += (keyWidth + actualGapWidth)
+            if roundedOrigin + keyWidth > frame.origin.x + frame.width {
+                frames.append(CGRectMake(rounded(frame.origin.x + frame.width - keyWidth), frame.origin.y, keyWidth, frame.height))
+            }
+            else {
+                frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, keyWidth, frame.height))
+            }
+            
+            currentOrigin += (keyWidth + actualGapWidth)
+        }
+        
+        return frames
+    }
+    
+    // TODO: pass in actual widths instead
+    func layoutCharacterWithSidesRow(row: [Key], frame: CGRect, isLandscape: Bool, keyWidth: CGFloat, keyGap: CGFloat) -> [CGRect] {
+        var frames = [CGRect]()
+
+        let standardFullKeyCount = Int(self.layoutConstants.keyCompressedThreshhold) - 1
+        let standardGap = (isLandscape ? self.layoutConstants.keyGapLandscape : self.layoutConstants.keyGapPortrait)(frame.width, rowCharacterCount: standardFullKeyCount)
+        let sideEdges = (isLandscape ? self.layoutConstants.sideEdgesLandscape : self.layoutConstants.sideEdgesPortrait(frame.width))
+        var standardKeyWidth = (frame.width - sideEdges - (standardGap * CGFloat(standardFullKeyCount - 1)) - sideEdges)
+        standardKeyWidth /= CGFloat(standardFullKeyCount)
+        let standardKeyCount = self.layoutConstants.flexibleEndRowMinimumStandardCharacterWidth
+        
+        let standardWidth = CGFloat(standardKeyWidth * standardKeyCount + standardGap * (standardKeyCount - 1))
+        let currentWidth = CGFloat(row.count - 2) * keyWidth + CGFloat(row.count - 3) * keyGap
+        
+        let isStandardWidth = (currentWidth < standardWidth)
+        let actualWidth = (isStandardWidth ? standardWidth : currentWidth)
+        let actualGap = (isStandardWidth ? standardGap : keyGap)
+        let actualKeyWidth = (actualWidth - CGFloat(row.count - 3) * actualGap) / CGFloat(row.count - 2)
+        
+        let sideSpace = (frame.width - actualWidth) / CGFloat(2)
+        
+        let m = (isLandscape ? self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthMLandscape : self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthMPortrait)
+        let c = (isLandscape ? self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthCLandscape : self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthCPortrait)
+        
+        var specialCharacterWidth = sideSpace * m + c
+        specialCharacterWidth = max(specialCharacterWidth, keyWidth)
+        specialCharacterWidth = rounded(specialCharacterWidth)
+        let specialCharacterGap = sideSpace - specialCharacterWidth
+        
+        var currentOrigin = frame.origin.x
+        for (k, key) in enumerate(row) {
+            if k == 0 {
+                frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, specialCharacterWidth, frame.height))
+                currentOrigin += (specialCharacterWidth + specialCharacterGap)
+            }
+            else if k == row.count - 1 {
+                currentOrigin += specialCharacterGap
+                frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, specialCharacterWidth, frame.height))
+                currentOrigin += specialCharacterWidth
+            }
+            else {
+                frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, actualKeyWidth, frame.height))
+                if k == row.count - 2 {
+                    currentOrigin += (actualKeyWidth)
                 }
                 else {
-                    assert(false, "view missing for model")
-                }
-            }
-        }
-    }
-    
-    // TODO: pass in actual widths instead
-    func layoutCharacterWithSidesRow(row: [Key], frame: CGRect, isLandscape: Bool, keyWidth: CGFloat, keyGap: CGFloat) {
-        if let modelToView = self.modelToView {
-            let standardFullKeyCount = Int(self.layoutConstants.keyCompressedThreshhold) - 1
-            let standardGap = (isLandscape ? self.layoutConstants.keyGapLandscape : self.layoutConstants.keyGapPortrait)(frame.width, rowCharacterCount: standardFullKeyCount)
-            let sideEdges = (isLandscape ? self.layoutConstants.sideEdgesLandscape : self.layoutConstants.sideEdgesPortrait(frame.width))
-            var standardKeyWidth = (frame.width - sideEdges - (standardGap * CGFloat(standardFullKeyCount - 1)) - sideEdges)
-            standardKeyWidth /= CGFloat(standardFullKeyCount)
-            let standardKeyCount = self.layoutConstants.flexibleEndRowMinimumStandardCharacterWidth
-            
-            let standardWidth = CGFloat(standardKeyWidth * standardKeyCount + standardGap * (standardKeyCount - 1))
-            let currentWidth = CGFloat(row.count - 2) * keyWidth + CGFloat(row.count - 3) * keyGap
-            
-            let isStandardWidth = (currentWidth < standardWidth)
-            let actualWidth = (isStandardWidth ? standardWidth : currentWidth)
-            let actualGap = (isStandardWidth ? standardGap : keyGap)
-            let actualKeyWidth = (actualWidth - CGFloat(row.count - 3) * actualGap) / CGFloat(row.count - 2)
-            
-            let sideSpace = (frame.width - actualWidth) / CGFloat(2)
-            
-            let m = (isLandscape ? self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthMLandscape : self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthMPortrait)
-            let c = (isLandscape ? self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthCLandscape : self.layoutConstants.flexibleEndRowTotalWidthToKeyWidthCPortrait)
-            
-            var specialCharacterWidth = sideSpace * m + c
-            specialCharacterWidth = max(specialCharacterWidth, keyWidth)
-            specialCharacterWidth = rounded(specialCharacterWidth)
-            let specialCharacterGap = sideSpace - specialCharacterWidth
-            
-            var currentOrigin = frame.origin.x
-            for (k, key) in enumerate(row) {
-                if let view = modelToView[key] {
-                    if k == 0 {
-                        view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, specialCharacterWidth, frame.height)
-                        currentOrigin += (specialCharacterWidth + specialCharacterGap)
-                    }
-                    else if k == row.count - 1 {
-                        currentOrigin += specialCharacterGap
-                        view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, specialCharacterWidth, frame.height)
-                        currentOrigin += specialCharacterWidth
-                    }
-                    else {
-                        view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, actualKeyWidth, frame.height)
-                        if k == row.count - 2 {
-                            currentOrigin += (actualKeyWidth)
-                        }
-                        else {
-                            currentOrigin += (actualKeyWidth + keyGap)
-                        }
-                    }
+                    currentOrigin += (actualKeyWidth + keyGap)
+                }
+            }
+        }
+
+        return frames
+    }
+    
+    func layoutSpecialKeysRow(row: [Key], keyWidth: CGFloat, gapWidth: CGFloat, leftSideRatio: CGFloat, rightSideRatio: CGFloat, micButtonRatio: CGFloat, isLandscape: Bool, frame: CGRect) -> [CGRect] {
+        var frames = [CGRect]()
+        
+        var keysBeforeSpace = 0
+        var keysAfterSpace = 0
+        var reachedSpace = false
+        for (k, key) in enumerate(row) {
+            if key.type == Key.KeyType.Space {
+                reachedSpace = true
+            }
+            else {
+                if !reachedSpace {
+                    keysBeforeSpace += 1
                 }
                 else {
-                    assert(false, "view missing for model")
-                }
-            }
-        }
-    }
-    
-    func layoutSpecialKeysRow(row: [Key], modelToView optModelToView: [Key:KeyboardKey]?, keyWidth: CGFloat, gapWidth: CGFloat, leftSideRatio: CGFloat, rightSideRatio: CGFloat, micButtonRatio: CGFloat, isLandscape: Bool, frame: CGRect) {
-        if let modelToView = optModelToView {
-            var keysBeforeSpace = 0
-            var keysAfterSpace = 0
-            var reachedSpace = false
-            for (k, key) in enumerate(row) {
-                if key.type == Key.KeyType.Space {
-                    reachedSpace = true
+                    keysAfterSpace += 1
+                }
+            }
+        }
+        
+        assert(keysBeforeSpace <= 3, "invalid number of keys before space (only max 3 currently supported)")
+        assert(keysAfterSpace == 1, "invalid number of keys after space (only default 1 currently supported)")
+        
+        let hasButtonInMicButtonPosition = (keysBeforeSpace == 3)
+        
+        var leftSideAreaWidth = frame.width * leftSideRatio
+        let rightSideAreaWidth = frame.width * rightSideRatio
+        var leftButtonWidth = (leftSideAreaWidth - (gapWidth * CGFloat(2 - 1))) / CGFloat(2)
+        leftButtonWidth = rounded(leftButtonWidth)
+        var rightButtonWidth = (rightSideAreaWidth - (gapWidth * CGFloat(keysAfterSpace - 1))) / CGFloat(keysAfterSpace)
+        rightButtonWidth = rounded(rightButtonWidth)
+        
+        let micButtonWidth = (isLandscape ? leftButtonWidth : leftButtonWidth * micButtonRatio)
+        
+        // special case for mic button
+        if hasButtonInMicButtonPosition {
+            leftSideAreaWidth = leftSideAreaWidth + gapWidth + micButtonWidth
+        }
+        
+        var spaceWidth = frame.width - leftSideAreaWidth - rightSideAreaWidth - gapWidth * CGFloat(2)
+        spaceWidth = rounded(spaceWidth)
+        
+        var currentOrigin = frame.origin.x
+        var beforeSpace: Bool = true
+        for (k, key) in enumerate(row) {
+            if key.type == Key.KeyType.Space {
+                frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, spaceWidth, frame.height))
+                currentOrigin += (spaceWidth + gapWidth)
+                beforeSpace = false
+            }
+            else if beforeSpace {
+                if hasButtonInMicButtonPosition && k == 2 { //mic button position
+                    frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, micButtonWidth, frame.height))
+                    currentOrigin += (micButtonWidth + gapWidth)
                 }
                 else {
-                    if !reachedSpace {
-                        keysBeforeSpace += 1
-                    }
-                    else {
-                        keysAfterSpace += 1
-                    }
-                }
-            }
-            
-            assert(keysBeforeSpace <= 3, "invalid number of keys before space (only max 3 currently supported)")
-            assert(keysAfterSpace == 1, "invalid number of keys after space (only default 1 currently supported)")
-            
-            let hasButtonInMicButtonPosition = (keysBeforeSpace == 3)
-            
-            var leftSideAreaWidth = frame.width * leftSideRatio
-            let rightSideAreaWidth = frame.width * rightSideRatio
-            var leftButtonWidth = (leftSideAreaWidth - (gapWidth * CGFloat(2 - 1))) / CGFloat(2)
-            leftButtonWidth = rounded(leftButtonWidth)
-            var rightButtonWidth = (rightSideAreaWidth - (gapWidth * CGFloat(keysAfterSpace - 1))) / CGFloat(keysAfterSpace)
-            rightButtonWidth = rounded(rightButtonWidth)
-            
-            let micButtonWidth = (isLandscape ? leftButtonWidth : leftButtonWidth * micButtonRatio)
-            
-            // special case for mic button
-            if hasButtonInMicButtonPosition {
-                leftSideAreaWidth = leftSideAreaWidth + gapWidth + micButtonWidth
-            }
-            
-            var spaceWidth = frame.width - leftSideAreaWidth - rightSideAreaWidth - gapWidth * CGFloat(2)
-            spaceWidth = rounded(spaceWidth)
-            
-            var currentOrigin = frame.origin.x
-            var beforeSpace: Bool = true
-            for (k, key) in enumerate(row) {
-                if let view = modelToView[key] {
-                    if key.type == Key.KeyType.Space {
-                        view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, spaceWidth, frame.height)
-                        currentOrigin += (spaceWidth + gapWidth)
-                        beforeSpace = false
-                    }
-                    else if beforeSpace {
-                        if hasButtonInMicButtonPosition && k == 2 { //mic button position
-                            view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, micButtonWidth, frame.height)
-                            currentOrigin += (micButtonWidth + gapWidth)
-                        }
-                        else {
-                            view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, leftButtonWidth, frame.height)
-                            currentOrigin += (leftButtonWidth + gapWidth)
-                        }
-                    }
-                    else {
-                        view.frame = CGRectMake(rounded(currentOrigin), frame.origin.y, rightButtonWidth, frame.height)
-                        currentOrigin += (rightButtonWidth + gapWidth)
-                    }
-                }
-                else {
-                    assert(false, "view missing for model")
-                }
-            }
-        }
+                    frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, leftButtonWidth, frame.height))
+                    currentOrigin += (leftButtonWidth + gapWidth)
+                }
+            }
+            else {
+                frames.append(CGRectMake(rounded(currentOrigin), frame.origin.y, rightButtonWidth, frame.height))
+                currentOrigin += (rightButtonWidth + gapWidth)
+            }
+        }
+
+        return frames
     }
     
     ////////////////

                return self.rounded(returnWidth)
                }()
            
            for (r, row) in enumerate(page.rows) {
                let rowGapCurrentTotal = (r == page.rows.count - 1 ? rowGapTotal : CGFloat(r) * rowGap)
                let frame = CGRectMake(rounded(sideEdges), rounded(topEdge + (CGFloat(r) * keyHeight) + rowGapCurrentTotal), rounded(bounds.width - CGFloat(2) * sideEdges), rounded(keyHeight))
                
                // basic character row: only typable characters
                if self.characterRowHeuristic(row) {
                    self.layoutCharacterRow(row, modelToView: self.modelToView, keyWidth: letterKeyWidth, gapWidth: keyGap, frame: frame)
                }
                    
                    // character row with side buttons: shift, backspace, etc.
                else if self.doubleSidedRowHeuristic(row) {
                    self.layoutCharacterWithSidesRow(row, frame: frame, isLandscape: isLandscape, keyWidth: letterKeyWidth, keyGap: keyGap)
                }
                    
                    // bottom row with things like space, return, etc.
                else {
                    self.layoutSpecialKeysRow(row, modelToView: self.modelToView, keyWidth: letterKeyWidth, gapWidth: lastRowKeyGap, leftSideRatio: lastRowLeftSideRatio, rightSideRatio: lastRowRightSideRatio, micButtonRatio: self.layoutConstants.micButtonPortraitWidthRatioToOtherSpecialButtons, isLandscape: isLandscape, frame: frame)
                }
            }
        }
    }
    
    func characterRowHeuristic(row: [Key]) -> Bool {

//"blueColor": UIColor(hue: (211/360.0), saturation: 1.0, brightness: 1.0, alpha: 1),
//"blueShadowColor": UIColor(hue: (216/360.0), saturation: 0.05, brightness: 0.43, alpha: 1),

extension CGRect: Hashable {
    public var hashValue: Int {
        get {
            return (origin.x.hashValue ^ origin.y.hashValue ^ size.width.hashValue ^ size.height.hashValue)
        }
    }
}

extension CGSize: Hashable {
    public var hashValue: Int {
        get {
            return (width.hashValue ^ height.hashValue)
        }
    }
}

// handles the layout for the keyboard, including key spacing and arrangement
class KeyboardLayout: NSObject, KeyboardKeyProtocol {
    
