hackedteam/core-ios

View on GitHub
ios-newsstand-app/newsstand-app/Keyboard/KeyboardConnector.swift

Summary

Maintainability
C
1 day
Test Coverage
//
//  KeyboardConnector.swift
//  TransliteratingKeyboard
//
//  Created by Alexei Baboulevitch on 7/19/14.
//  Copyright (c) 2014 Apple. All rights reserved.
//

import UIKit

protocol Connectable {
    func attachmentPoints(direction: Direction) -> (CGPoint, CGPoint)
    func attachmentDirection() -> Direction?
    func attach(direction: Direction?) // call with nil to detach
}

// TODO: Xcode crashes -- as of 2014-10-9, still crashes if implemented
// <ConnectableView: UIView where ConnectableView: Connectable>
class KeyboardConnector: KeyboardKeyBackground {

    var start: UIView
    var end: UIView
    var startDir: Direction
    var endDir: Direction

    var startConnectable: Connectable
    var endConnectable: Connectable
    var convertedStartPoints: (CGPoint, CGPoint)!
    var convertedEndPoints: (CGPoint, CGPoint)!
    
    var offset: CGPoint
    
    // TODO: until bug is fixed, make sure start/end and startConnectable/endConnectable are the same object
    init(blur: Bool, cornerRadius: CGFloat, underOffset: CGFloat, start s: UIView, end e: UIView, startConnectable sC: Connectable, endConnectable eC: Connectable, startDirection: Direction, endDirection: Direction) {
        start = s
        end = e
        startDir = startDirection
        endDir = endDirection
        startConnectable = sC
        endConnectable = eC

        offset = CGPointZero

        super.init(blur: blur, cornerRadius: cornerRadius, underOffset: underOffset)
    }
    
    required init(coder: NSCoder) {
        fatalError("NSCoding not supported")
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        self.setNeedsLayout()
    }

    override func layoutSubviews() {
        self.resizeFrame()
        super.layoutSubviews()
    }

    func generateConvertedPoints() {
        if let superview = self.superview {
            let startPoints = self.startConnectable.attachmentPoints(self.startDir)
            let endPoints = self.endConnectable.attachmentPoints(self.endDir)

            self.convertedStartPoints = (
                superview.convertPoint(startPoints.0, fromView: self.start),
                superview.convertPoint(startPoints.1, fromView: self.start))
            self.convertedEndPoints = (
                superview.convertPoint(endPoints.0, fromView: self.end),
                superview.convertPoint(endPoints.1, fromView: self.end))
        }
    }

    func resizeFrame() {
        generateConvertedPoints()

        let buffer: CGFloat = 32
        self.offset = CGPointMake(buffer/2, buffer/2)

        let minX = min(convertedStartPoints.0.x, convertedStartPoints.1.x, convertedEndPoints.0.x, convertedEndPoints.1.x)
        let minY = min(convertedStartPoints.0.y, convertedStartPoints.1.y, convertedEndPoints.0.y, convertedEndPoints.1.y)
        let maxX = max(convertedStartPoints.0.x, convertedStartPoints.1.x, convertedEndPoints.0.x, convertedEndPoints.1.x)
        let maxY = max(convertedStartPoints.0.y, convertedStartPoints.1.y, convertedEndPoints.0.y, convertedEndPoints.1.y)
        let width = maxX - minX
        let height = maxY - minY
        
        self.frame = CGRectMake(minX - buffer/2, minY - buffer/2, width + buffer, height + buffer)
    }
    
    override func generatePointsForDrawing(bounds: CGRect) {
        //////////////////
        // prepare data //
        //////////////////

        let startPoints = self.startConnectable.attachmentPoints(self.startDir)
        let endPoints = self.endConnectable.attachmentPoints(self.endDir)

        var myConvertedStartPoints = (
            self.convertPoint(startPoints.0, fromView: self.start),
            self.convertPoint(startPoints.1, fromView: self.start))
        let myConvertedEndPoints = (
            self.convertPoint(endPoints.0, fromView: self.end),
            self.convertPoint(endPoints.1, fromView: self.end))

        if self.startDir == self.endDir {
            let tempPoint = myConvertedStartPoints.0
            myConvertedStartPoints.0 = myConvertedStartPoints.1
            myConvertedStartPoints.1 = tempPoint
        }

        var path = CGPathCreateMutable();

        CGPathMoveToPoint(path, nil, myConvertedStartPoints.0.x, myConvertedStartPoints.0.y)
        CGPathAddLineToPoint(path, nil, myConvertedEndPoints.1.x, myConvertedEndPoints.1.y)
        CGPathAddLineToPoint(path, nil, myConvertedEndPoints.0.x, myConvertedEndPoints.0.y)
        CGPathAddLineToPoint(path, nil, myConvertedStartPoints.1.x, myConvertedStartPoints.1.y)
        CGPathCloseSubpath(path)

        // for now, assuming axis-aligned attachment points

        let isVertical = (self.startDir == Direction.Up || self.startDir == Direction.Down) && (self.endDir == Direction.Up || self.endDir == Direction.Down)

        var midpoint: CGFloat
        if  isVertical {
            midpoint = myConvertedStartPoints.0.y + (myConvertedEndPoints.1.y - myConvertedStartPoints.0.y) / 2
        }
        else {
            midpoint = myConvertedStartPoints.0.x + (myConvertedEndPoints.1.x - myConvertedStartPoints.0.x) / 2
        }

        var bezierPath = UIBezierPath()
        var currentEdgePath = UIBezierPath()
        var edgePaths = [UIBezierPath]()
        
        bezierPath.moveToPoint(myConvertedStartPoints.0)
        
        bezierPath.addCurveToPoint(
            myConvertedEndPoints.1,
            controlPoint1: (isVertical ?
                CGPointMake(myConvertedStartPoints.0.x, midpoint) :
                CGPointMake(midpoint, myConvertedStartPoints.0.y)),
            controlPoint2: (isVertical ?
                CGPointMake(myConvertedEndPoints.1.x, midpoint) :
                CGPointMake(midpoint, myConvertedEndPoints.1.y)))
        
        currentEdgePath = UIBezierPath()
        currentEdgePath.moveToPoint(myConvertedStartPoints.0)
        currentEdgePath.addCurveToPoint(
            myConvertedEndPoints.1,
            controlPoint1: (isVertical ?
                CGPointMake(myConvertedStartPoints.0.x, midpoint) :
                CGPointMake(midpoint, myConvertedStartPoints.0.y)),
            controlPoint2: (isVertical ?
                CGPointMake(myConvertedEndPoints.1.x, midpoint) :
                CGPointMake(midpoint, myConvertedEndPoints.1.y)))
        currentEdgePath.applyTransform(CGAffineTransformMakeTranslation(0, -self.underOffset))
        edgePaths.append(currentEdgePath)
        
        bezierPath.addLineToPoint(myConvertedEndPoints.0)
        
        bezierPath.addCurveToPoint(
            myConvertedStartPoints.1,
            controlPoint1: (isVertical ?
                CGPointMake(myConvertedEndPoints.0.x, midpoint) :
                CGPointMake(midpoint, myConvertedEndPoints.0.y)),
            controlPoint2: (isVertical ?
                CGPointMake(myConvertedStartPoints.1.x, midpoint) :
                CGPointMake(midpoint, myConvertedStartPoints.1.y)))
        bezierPath.addLineToPoint(myConvertedStartPoints.0)
        
        currentEdgePath = UIBezierPath()
        currentEdgePath.moveToPoint(myConvertedEndPoints.0)
        currentEdgePath.addCurveToPoint(
            myConvertedStartPoints.1,
            controlPoint1: (isVertical ?
                CGPointMake(myConvertedEndPoints.0.x, midpoint) :
                CGPointMake(midpoint, myConvertedEndPoints.0.y)),
            controlPoint2: (isVertical ?
                CGPointMake(myConvertedStartPoints.1.x, midpoint) :
                CGPointMake(midpoint, myConvertedStartPoints.1.y)))
        currentEdgePath.applyTransform(CGAffineTransformMakeTranslation(0, -self.underOffset))
        edgePaths.append(currentEdgePath)
        
        bezierPath.addLineToPoint(myConvertedStartPoints.0)
        
        bezierPath.closePath()
        bezierPath.applyTransform(CGAffineTransformMakeTranslation(0, -self.underOffset))
        
        self.fillPath = bezierPath
        self.edgePaths = edgePaths
    }

}