hackedteam/core-ios

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

Summary

Maintainability
D
2 days
Test Coverage
//
//  KeyboardKey.swift
//  TransliteratingKeyboard
//
//  Created by Alexei Baboulevitch on 6/9/14.
//  Copyright (c) 2014 Apple. All rights reserved.
//

import UIKit

// TODO: animation on color set
// TODO: correct corner radius
// TODO: correct colors
// TODO: refactor

// popup constraints have to be setup with the topmost view in mind; hence these callbacks
protocol KeyboardKeyProtocol {
    func frameForPopup(key: KeyboardKey, direction: Direction) -> CGRect
    func willShowPopup(key: KeyboardKey, direction: Direction) //may be called multiple times during layout
    func willHidePopup(key: KeyboardKey)
}

enum VibrancyType {
    case LightSpecial
    case DarkSpecial
    case DarkRegular
}

//extension UIView {
//    func blurredSnapshot() -> UIImage {
//        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, (self.window != nil ? self.window!.screen.scale : 1))
//        self.drawViewHierarchyInRect(self.frame, afterScreenUpdates: false)
//        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
//        var blurredImage = snapshotImage.applyLightEffect()
//        UIGraphicsEndImageContext()
//        return blurredImage
//    }
//}

class KeyboardKey: UIControl {
    
    var delegate: KeyboardKeyProtocol?
    
    var vibrancy: VibrancyType?
    
    var text: String {
        didSet {
            self.label.text = text
            self.label.frame = CGRectMake((self.bounds.width - self.bounds.width * CGFloat(0.8)) / CGFloat(2), 0, self.bounds.width * CGFloat(0.8), self.bounds.height)
            self.redrawText()
        }
    }
    
    var color: UIColor { didSet { updateColors() }}
    var underColor: UIColor { didSet { updateColors() }}
    var borderColor: UIColor { didSet { updateColors() }}
    var drawUnder: Bool { didSet { updateColors() }}
    var drawOver: Bool { didSet { updateColors() }}
    var drawBorder: Bool { didSet { updateColors() }}
    var underOffset: CGFloat { didSet { updateColors() }}
    
    var textColor: UIColor { didSet { updateColors() }}
    var downColor: UIColor? { didSet { updateColors() }}
    var downUnderColor: UIColor? { didSet { updateColors() }}
    var downBorderColor: UIColor? { didSet { updateColors() }}
    var downTextColor: UIColor? { didSet { updateColors() }}
    
    var popupDirection: Direction?
    
    override var enabled: Bool { didSet { updateColors() }}
    override var selected: Bool {
        didSet {
            updateColors()
        }
    }
    override var highlighted: Bool {
        didSet {
            updateColors()
        }
    }
    
    override var frame: CGRect {
        didSet {
            self.redrawText()
        }
    }
    
    var label: UILabel
    var popupLabel: UILabel?
    var shape: Shape? {
        didSet {
            self.redrawShape()
            updateColors()
        }
    }
    
    var withBlur: Bool
    
    var background: KeyboardKeyBackground
    var popup: KeyboardKeyBackground?
    var connector: KeyboardConnector?
    
    var displayView: UIView
    var displayViewContentView: UIView
    var displayMaskView: UIView?
    var maskLayer: CAShapeLayer
    var borderView: UIView
    var borderLayer: CAShapeLayer
    var underLayer: CAShapeLayer
    var underView: UIView
    var shadowView: UIView
    var shadowLayer: CAShapeLayer
    
    class DumbLayer: CALayer {
        override func addAnimation(anim: CAAnimation!, forKey key: String!) {
        }
    }
    
    override class func layerClass() -> AnyClass {
        return DumbLayer.self
    }
    
    init(vibrancy optionalVibrancy: VibrancyType?) {
        self.withBlur = (optionalVibrancy != nil)
        
        self.displayView = {
            if let vibrancy = optionalVibrancy {
                let blurEffect = { () -> UIVisualEffect? in
                    switch vibrancy {
                    case .LightSpecial:
                        //return UIVibrancyEffect(forBlurEffect: UIBlurEffect(style: UIBlurEffectStyle.ExtraLight)) -- goes with grey 0.25
                        return UIBlurEffect(style: UIBlurEffectStyle.Light)
                    case .DarkSpecial:
                        return UIBlurEffect(style: UIBlurEffectStyle.Light)
                    case .DarkRegular:
                        return UIBlurEffect(style: UIBlurEffectStyle.Light)
                    }
                }()
                return (blurEffect != nil ? UIVisualEffectView(effect: blurEffect!) : UIView())
            }
            else {
                return UIView()
            }
        }()
        
        self.borderLayer = CAShapeLayer()
        self.borderView = UIView()
        self.underLayer = CAShapeLayer()
        self.underView = UIView()
        self.shadowLayer = CAShapeLayer()
        self.shadowView = UIView()
        self.maskLayer = CAShapeLayer()
        
        if let effectView = self.displayView as? UIVisualEffectView {
            self.displayViewContentView = effectView.contentView
        }
        else {
            self.displayViewContentView = self.displayView
        }
        
        self.label = UILabel()
        self.text = ""
        
        self.color = UIColor.whiteColor()
        self.underColor = UIColor.grayColor()
        self.borderColor = UIColor.blackColor()
        self.drawUnder = true
        self.drawOver = true
        self.drawBorder = false
        self.underOffset = 1
        
        self.background = KeyboardKeyBackground(blur: withBlur, cornerRadius: 4, underOffset: self.underOffset)
        
        self.textColor = UIColor.blackColor()
        self.popupDirection = nil
        
        super.init(frame: CGRectZero)
        
        self.addSubview(self.shadowView)
        self.shadowView.layer.addSublayer(self.shadowLayer)
        
        if self.withBlur {
            // use it as a mask
            self.displayMaskView = UIView()
            self.displayMaskView?.layer.mask = self.maskLayer
            self.displayMaskView?.addSubview(self.displayView)
            self.addSubview(self.displayMaskView!)
        }
        else {
            // use it to draw directly
            self.displayViewContentView.layer.addSublayer(self.maskLayer)
            self.addSubview(self.displayView)
        }
        
        self.underView.layer.addSublayer(self.underLayer)
        self.addSubview(self.underView)
        
        self.borderView.layer.addSublayer(self.borderLayer)
        self.addSubview(self.borderView)
        
        self.addSubview(self.background)
        self.background.addSubview(self.label)
        
        let setupViews: Void = {
            self.label.textAlignment = NSTextAlignment.Center
            self.label.baselineAdjustment = UIBaselineAdjustment.AlignCenters
            self.label.font = self.label.font.fontWithSize(22)
            self.label.adjustsFontSizeToFitWidth = false
            self.label.minimumScaleFactor = CGFloat(0.1)
            self.label.userInteractionEnabled = false
            self.label.numberOfLines = 1
            
            self.shadowLayer.shadowOpacity = Float(0.2)
            self.shadowLayer.shadowRadius = 4
            self.shadowLayer.shadowOffset = CGSizeMake(0, 3)
            
            self.borderLayer.lineWidth = CGFloat(0.5)
            self.borderLayer.fillColor = UIColor.clearColor().CGColor
        }()
    }
    
    required init(coder: NSCoder) {
        fatalError("NSCoding not supported")
    }
    
    override func setNeedsLayout() {
        return super.setNeedsLayout()
    }
    
    var oldBounds: CGRect?
    override func layoutSubviews() {
        self.layoutPopupIfNeeded()
        
        var boundingBox = (self.popup != nil ? CGRectUnion(self.bounds, self.popup!.frame) : self.bounds)
        
        if self.bounds.width == 0 || self.bounds.height == 0 {
            return
        }
        if oldBounds != nil && CGRectEqualToRect(boundingBox, oldBounds!) {
            return
        }
        oldBounds = boundingBox

        super.layoutSubviews()
        
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        self.background.frame = self.bounds
        self.label.frame = CGRectMake((self.bounds.width - self.bounds.width * CGFloat(0.8)) / CGFloat(2), 0, self.bounds.width * CGFloat(0.8), self.bounds.height)
        
        if let maskView = self.displayMaskView {
            maskView.frame = boundingBox
            self.displayView.frame = maskView.bounds
        }
        else {
            self.displayView.frame = boundingBox
        }

        self.shadowView.frame = boundingBox
        self.borderView.frame = boundingBox
        self.underView.frame = boundingBox
        
        CATransaction.commit()
        
        self.refreshViews()

//        if self.text == "a" || self.text == "A" {
//            self.background.trackMe = true
//            NSLog("relayingout a: \(self.bounds)")
//        }
    }
    
    func refreshViews() {
        self.refreshShapes()
        self.redrawText()
        self.redrawShape()
        self.updateColors()
    }
    
//   TODO:  UIView mask
    
    func refreshShapes() {
        // TODO: dunno why this is necessary
        self.background.setNeedsLayout()
        
        self.background.layoutIfNeeded()
        self.popup?.layoutIfNeeded()
        self.connector?.layoutIfNeeded()
        
        var testPath = UIBezierPath()
        var edgePath = UIBezierPath()
        
        let unitSquare = CGRectMake(0, 0, 1, 1)
        
        // TODO: withUnder
        let addCurves = { (fromShape: KeyboardKeyBackground?, toPath: UIBezierPath, toEdgePaths: UIBezierPath) -> Void in
            if let shape = fromShape {
                var path = shape.fillPath
                var translatedUnitSquare = self.displayView.convertRect(unitSquare, fromView: shape)
                let transformFromShapeToView = CGAffineTransformMakeTranslation(translatedUnitSquare.origin.x, translatedUnitSquare.origin.y)
                path?.applyTransform(transformFromShapeToView)
                if path != nil { toPath.appendPath(path!) }
                if let edgePaths = shape.edgePaths {
                    for (e, anEdgePath) in enumerate(edgePaths) {
                        var editablePath = anEdgePath
                        editablePath.applyTransform(transformFromShapeToView)
                        toEdgePaths.appendPath(editablePath)
                    }
                }
            }
        }
        
        addCurves(self.popup, testPath, edgePath)
        addCurves(self.connector, testPath, edgePath)
        
        var shadowPath = UIBezierPath(CGPath: testPath.CGPath)
        
        addCurves(self.background, testPath, edgePath)
        
        var underPath = self.background.underPath
        var translatedUnitSquare = self.displayView.convertRect(unitSquare, fromView: self.background)
        let transformFromShapeToView = CGAffineTransformMakeTranslation(translatedUnitSquare.origin.x, translatedUnitSquare.origin.y)
        underPath?.applyTransform(transformFromShapeToView)
        
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        if let popup = self.popup {
            self.shadowLayer.shadowPath = shadowPath.CGPath
        }
        
        self.underLayer.path = underPath?.CGPath
        self.maskLayer.path = testPath.CGPath
        self.borderLayer.path = edgePath.CGPath
        
        CATransaction.commit()
    }
    
    func layoutPopupIfNeeded() {
        if self.popup != nil && self.popupDirection == nil {
            self.shadowView.hidden = false
            self.borderView.hidden = (self.withBlur ? true : false)
            
            self.popupDirection = Direction.Up
            
            self.layoutPopup(self.popupDirection!)
            self.configurePopup(self.popupDirection!)
            
            self.delegate?.willShowPopup(self, direction: self.popupDirection!)
        }
        else {
            self.shadowView.hidden = true
            self.borderView.hidden = true
        }
    }
    
    func redrawText() {
//        self.keyView.frame = self.bounds
//        self.button.frame = self.bounds
//        
//        self.button.setTitle(self.text, forState: UIControlState.Normal)
    }
    
    func redrawShape() {
        if let shape = self.shape {
            self.text = ""
            shape.removeFromSuperview()
            self.addSubview(shape)
            
            let sizeRatio = CGFloat(1)
            let size = CGSizeMake(self.bounds.width * sizeRatio, self.bounds.height * sizeRatio)
            shape.frame = CGRectMake(
                CGFloat((self.bounds.width - size.width) / 2.0),
                CGFloat((self.bounds.height - size.height) / 2.0),
                size.width,
                size.height)
            
            shape.setNeedsDisplay()
        }
    }
    
    func updateColors() {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        let switchColors = self.highlighted || self.selected
        
        if switchColors {
            if let downColor = self.downColor {
                if self.withBlur {
                    self.displayViewContentView.backgroundColor = downColor
                }
                else {
                    self.maskLayer.fillColor = downColor.CGColor
                }
            }
            else {
                if self.withBlur {
                    self.displayViewContentView.backgroundColor = self.color
                }
                else {
                    self.maskLayer.fillColor = self.color.CGColor
                }
            }
            
            if let downUnderColor = self.downUnderColor {
                self.underLayer.fillColor = downUnderColor.CGColor
            }
            else {
                self.underLayer.fillColor = self.underColor.CGColor
            }
            
            if let downBorderColor = self.downBorderColor {
                self.borderLayer.strokeColor = downBorderColor.CGColor
            }
            else {
                self.borderLayer.strokeColor = self.borderColor.CGColor
            }
            
            if let downTextColor = self.downTextColor {
                self.label.textColor = downTextColor
                self.popupLabel?.textColor = downTextColor
                self.shape?.color = downTextColor
            }
            else {
                self.label.textColor = self.textColor
                self.popupLabel?.textColor = self.textColor
                self.shape?.color = self.textColor
            }
        }
        else {
            if self.withBlur {
                self.displayViewContentView.backgroundColor = self.color
            }
            else {
                self.maskLayer.fillColor = self.color.CGColor
            }
            
            self.underLayer.fillColor = self.underColor.CGColor
            
            self.borderLayer.strokeColor = self.borderColor.CGColor
            
            self.label.textColor = self.textColor
            self.popupLabel?.textColor = self.textColor
            self.shape?.color = self.textColor
        }
        
        CATransaction.commit()
    }
    
    func layoutPopup(dir: Direction) {
        assert(self.popup != nil, "popup not found")
        
        if let popup = self.popup {
            if let delegate = self.delegate {
                let frame = delegate.frameForPopup(self, direction: dir)
                popup.frame = frame
                popupLabel?.frame = popup.bounds
            }
            else {
                popup.frame = CGRectZero
                popup.center = self.center
            }
        }
    }
    
    func configurePopup(direction: Direction) {
        assert(self.popup != nil, "popup not found")
        
        self.background.attach(direction)
        self.popup!.attach(direction.opposite())
        
        let kv = self.background
        let p = self.popup!
        
        self.connector?.removeFromSuperview()
        self.connector = KeyboardConnector(blur: withBlur, cornerRadius: 4, underOffset: self.underOffset, start: kv, end: p, startConnectable: kv, endConnectable: p, startDirection: direction, endDirection: direction.opposite())
        self.connector!.layer.zPosition = -1
        self.addSubview(self.connector!)
        
//        self.drawBorder = true
        
        if direction == Direction.Up {
//            self.popup!.drawUnder = false
//            self.connector!.drawUnder = false
        }
    }
    
    func showPopup() {
        if self.popup == nil {
            self.layer.zPosition = 1000
            
            var popup = KeyboardKeyBackground(blur: withBlur, cornerRadius: 9.0, underOffset: self.underOffset)
            self.popup = popup
            self.addSubview(popup)
            
            var popupLabel = UILabel()
            popupLabel.textAlignment = self.label.textAlignment
            popupLabel.font = self.label.font.fontWithSize(22 * 2)
            popupLabel.frame = popup.bounds
            popupLabel.text = self.label.text
            popup.addSubview(popupLabel)
            self.popupLabel = popupLabel
            
            self.label.hidden = true
        }
    }
    
    func hidePopup() {
        if self.popup != nil {
            self.delegate?.willHidePopup(self)
            
            self.popupLabel?.removeFromSuperview()
            self.popupLabel = nil
            
            self.connector?.removeFromSuperview()
            self.connector = nil
            
            self.popup?.removeFromSuperview()
            self.popup = nil
            
            self.label.hidden = false
            self.background.attach(nil)
            
            self.layer.zPosition = 0
            
            self.popupDirection = nil
        }
    }
}