edwardvalentini/EVContactsPicker

View on GitHub
Pod/Classes/EVPickedContactsView.swift

Summary

Maintainability
D
2 days
Test Coverage
//
//  EVPickedContactsView.swift
//  Pods
//
//  Created by Edward Valentini on 8/29/15.
//
//

import UIKit

class EVPickedContactsView: UIView, EVContactBubbleDelegate, UITextViewDelegate, UIScrollViewDelegate {
    // MARK: - Private Variables
    
    fileprivate var _shouldSelectTextView = false
    fileprivate var scrollView : UIScrollView?
    fileprivate var contacts : [AnyHashable: EVContactBubble]?
    fileprivate var keyContacts : [AnyHashable : EVContactProtocol]?
    fileprivate var contactKeys : [AnyHashable]?
    fileprivate var placeholderLabel : UILabel?
    fileprivate var lineHeight : CGFloat?
    fileprivate var textView : UITextView?
    
    fileprivate var bubbleColor : EVBubbleColor?
    fileprivate var bubbleSelectedColor : EVBubbleColor?
    
    fileprivate let kViewPadding = CGFloat(5.0)
    fileprivate let kHorizontalPadding = CGFloat(2.0)
    fileprivate let kVerticalPadding = CGFloat(4.0)
    fileprivate let kTextViewMinWidth = CGFloat(130.0)
    
    // MARK: - Properties
    
    var selectedContactBubble : EVContactBubble?
    var delegate : EVPickedContactsViewDelegate?
    var limitToOne : Bool = false
    var viewPadding : CGFloat?
    var font : UIFont?
        
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setup()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setup()
    }
    
    // MARK: - Public Methods
    
    func disableDropShadow() -> Void {
        let layer = self.layer
        layer.shadowRadius = 0.0
        layer.shadowOpacity = 0.0
    }
    
    func addContact(_ contact : EVContactProtocol, name: String) -> Void {

        guard let contactKeys = self.contactKeys else {
            return
        }
        
        let contactKey = contact.identifier

        if contactKeys.contains(contactKey) {
            return
        }
        
        self.textView?.text = ""
        
        let contactBubble = EVContactBubble(name: name, color: self.bubbleColor, selectedColor: self.bubbleSelectedColor)
        
        if let font = self.font {
            contactBubble.setFont(font)
        }
        
        contactBubble.delegate = self
        
        self.contacts?[contactKey] = contactBubble
        
        self.contactKeys?.append(contactKey)
        self.keyContacts?[contactKey] = contact
        
        self.layoutView()
        
        self.textView?.isHidden = false
        self.textView?.text = ""
        
        self.scrollToBottomWithAnimation(false)
        
    }
    
    func selectTextView() -> Void {
        self.textView?.isHidden = false
    }
    
    func removeAllContacts() -> Void {
        for ( _ , kValue) in self.contacts! {
            kValue.removeFromSuperview()
        }
        
        self.contacts?.removeAll()
        self.contactKeys?.removeAll()
        
        self.layoutView()
        
        self.textView?.isHidden = false
        self.textView?.text = ""
    }
    
    func removeContact(_ contact : EVContactProtocol) -> Void {
        // let contactKey = NSValue(nonretainedObject: contact)
            let contactKey = contact.identifier

            if let contacts = self.contacts {
                if let contactBubble = contacts[contactKey] {
                    contactBubble.removeFromSuperview()
                    _ = self.contacts?.removeValue(forKey: contactKey)
                    _ = self.keyContacts?.removeValue(forKey: contactKey)
                    
                    if let foundIndex = self.contactKeys?.index(of: contactKey) {
                        self.contactKeys?.remove(at: foundIndex)
                    }
                    
                    self.layoutView()
                    
                    self.textView?.isHidden = false
                    self.textView?.text = ""
                    
                    self.scrollToBottomWithAnimation(false)
                }
            }
    }
    
    
    func setPlaceHolderString(_ placeHolderString: String) -> Void {
        self.placeholderLabel?.text = placeHolderString
        self.layoutView()
    }
    
    func resignKeyboard() -> Void {
        self.textView?.resignFirstResponder()
    }
    
    func setBubbleColor(_ color: EVBubbleColor, selectedColor:EVBubbleColor) -> Void {
        self.bubbleColor = color
        self.bubbleSelectedColor = selectedColor
        
        for contactKey in self.contactKeys! {
            if let contacts = contacts {
                if let contactBubble = contacts[contactKey] {
                    contactBubble.color = color
                    contactBubble.selectedColor = selectedColor
                    if(contactBubble.isSelected) {
                        contactBubble.select()
                    } else {
                        contactBubble.unSelect()
                    }
                }
            }
        }
    }
    
    

    
    

    // MARK: - Private Methods
    
    fileprivate func setup() -> Void {
        self.viewPadding = kViewPadding
        
        self.contacts = [:]
        self.contactKeys = []
        self.keyContacts = [:]
        
        let contactBubble = EVContactBubble(name: "Sample")
        self.lineHeight = contactBubble.frame.size.height + 2 + kVerticalPadding
        
        self.scrollView = UIScrollView(frame: self.bounds)
        self.scrollView?.scrollsToTop = false
        self.scrollView?.delegate = self
        self.addSubview(self.scrollView!)
        
        self.textView = UITextView()
        self.textView?.delegate = self
        self.textView?.font = contactBubble.label?.font
        self.textView?.backgroundColor = UIColor.clear
        self.textView?.contentInset = UIEdgeInsetsMake(-4.0, -2.0, 0, 0)
        self.textView?.isScrollEnabled = false
        self.textView?.scrollsToTop = false
        self.textView?.clipsToBounds = false
        self.textView?.autocorrectionType = UITextAutocorrectionType.no
        
        self.backgroundColor = UIColor.white
        let layer = self.layer
        layer.shadowColor = UIColor(red: 225.0/255.0, green: 226.0/255.0, blue: 228.0/255.0, alpha: 1.0).cgColor
        layer.shadowOffset = CGSize(width: 0, height: 2)
        layer.shadowOpacity = 1.0
        layer.shadowRadius = 1.0
        
        self.placeholderLabel = UILabel(frame: CGRect(x: 8, y: self.viewPadding!, width: self.frame.size.width, height: self.lineHeight!))
        self.placeholderLabel?.font = contactBubble.label?.font
        self.placeholderLabel?.backgroundColor = UIColor.clear
        self.placeholderLabel?.textColor = UIColor.gray
        self.addSubview(self.placeholderLabel!)
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(EVPickedContactsView.handleTapGesture))
        tapGesture.numberOfTapsRequired = 1
        tapGesture.numberOfTouchesRequired = 1
        self.addGestureRecognizer(tapGesture)
    }
    
    func scrollToBottomWithAnimation(_ animated : Bool) -> Void {
        if(animated) {
            let size = self.scrollView?.contentSize
            let frame = CGRect(x: 0, y: size!.height - (self.scrollView?.frame.size.height)!, width: size!.width, height: (self.scrollView?.frame.size.height)!)
            
            self.scrollView?.scrollRectToVisible(frame, animated: animated)
        } else {
            var offset = self.scrollView?.contentOffset
            offset?.y = (self.scrollView?.contentSize.height)! - (self.scrollView?.frame.size.height)!
            self.scrollView?.contentOffset = offset!
        }
    }
    
    func removeContactBubble(_ contactBubble : EVContactBubble) -> Void {
        if let contactKey = self.contactForContactBubble(contactBubble), let keyContacts = self.keyContacts, let contact = keyContacts[contactKey] {
            self.delegate?.contactPickerDidRemoveContact(contact)
            self.removeContactByKey(contactKey)
        }
    }
    
    func removeContactByKey(_ contactKey : AnyHashable) -> Void {
        
        guard let contacts = self.contacts else {
            return
        }
        
        if let contactBubble = contacts[contactKey] {
            contactBubble.removeFromSuperview()
            
            let _ = self.contacts?.removeValue(forKey: contactKey)
            
            if let foundIndex = self.contactKeys?.index(of: contactKey) {
                self.contactKeys?.remove(at: foundIndex)
            }
            
            self.layoutView()
            
            self.textView?.isHidden = false
            self.textView?.text = ""
            
            self.scrollToBottomWithAnimation(false)
        }
    }
    
    func contactForContactBubble(_ contactBubble : EVContactBubble) -> AnyHashable? {
        
        guard let contacts = self.contacts else {
            return nil
        }
        
        var returnKey : AnyHashable? = nil
        
        contacts.forEach { (key: AnyHashable, value: EVContactBubble) in
            if let valueForKey = contacts[key], valueForKey.isEqual(contactBubble) {
                returnKey = key
            }
        }
    
        return returnKey
    }
    
    
    func layoutView() -> Void {
        var frameOfLastBubble = CGRect.null
        var lineCount = 0
        
        for contactKey in self.contactKeys! {
            if let contacts = self.contacts, let contactBubble = contacts[contactKey] { // as EVContactBubble {
            
                var bubbleFrame = contactBubble.frame
                
                if(frameOfLastBubble.isNull) {
                    bubbleFrame.origin.x = kHorizontalPadding
                    bubbleFrame.origin.y = kVerticalPadding + self.viewPadding!
                } else {
                    let width = bubbleFrame.size.width + 2 * kHorizontalPadding
                    if( self.frame.size.width - frameOfLastBubble.origin.x - frameOfLastBubble.size.width - width >= 0) {
                        
                        bubbleFrame.origin.x = frameOfLastBubble.origin.x + frameOfLastBubble.size.width + kHorizontalPadding * 2
                        bubbleFrame.origin.y = frameOfLastBubble.origin.y
                    } else {
                        lineCount += 1
                        bubbleFrame.origin.x = kHorizontalPadding
                        bubbleFrame.origin.y = (CGFloat(lineCount) * self.lineHeight!) + kVerticalPadding +     self.viewPadding!
                    }
                }
                
                frameOfLastBubble = bubbleFrame
                contactBubble.frame = bubbleFrame
                
                if (contactBubble.superview == nil){
                    self.scrollView?.addSubview(contactBubble)
                }
            }
        }
        
        let minWidth = kTextViewMinWidth + 2 * kHorizontalPadding
        var textViewFrame = CGRect(x: 0, y: 0, width: (self.textView?.frame.size.width)!, height: self.lineHeight!)
        
        if (self.frame.size.width - frameOfLastBubble.origin.x - frameOfLastBubble.size.width - minWidth >= 0){ // add to the same line
            textViewFrame.origin.x = frameOfLastBubble.origin.x + frameOfLastBubble.size.width + kHorizontalPadding
            textViewFrame.size.width = self.frame.size.width - textViewFrame.origin.x
        } else { // place text view on the next line
            lineCount += 1
            
            textViewFrame.origin.x = kHorizontalPadding
            textViewFrame.size.width = self.frame.size.width - 2 * kHorizontalPadding
            
            if (self.contacts?.count == 0){
                lineCount = 0
                textViewFrame.origin.x = kHorizontalPadding
            }
        }
        textViewFrame.origin.y = CGFloat(lineCount) * self.lineHeight! + kVerticalPadding + self.viewPadding!
        self.textView?.frame = textViewFrame

        if (self.textView?.superview == nil){
            self.scrollView?.addSubview(self.textView!)
        }
        
        if (self.limitToOne && (self.contacts?.count)! >= 1){
            self.textView?.isHidden = true;
            lineCount = 0;
        }
        
        var frame = self.bounds
        let maxFrameHeight = 2 * self.lineHeight! + 2 * self.viewPadding!
        var newHeight = (CGFloat(lineCount) + 1.0) * self.lineHeight! + 2 * self.viewPadding!
        
        self.scrollView?.contentSize = CGSize(width: (self.scrollView?.frame.size.width)!, height: newHeight)
        
        newHeight = (newHeight > maxFrameHeight) ? maxFrameHeight : newHeight;
        if (self.frame.size.height != newHeight){
            // Adjust self height
            var selfFrame = self.frame
            selfFrame.size.height = newHeight
            self.frame = selfFrame
            
            // Adjust scroll view height
            frame.size.height = newHeight
            self.scrollView?.frame = frame
            
            self.delegate?.contactPickerDidResize(self)

        }
        
        // Show placeholder if no there are no contacts
        if (self.contacts?.count == 0){
            self.placeholderLabel?.isHidden = false
        } else {
            self.placeholderLabel?.isHidden = true
        }
    }
    
    
    // MARK: - UITextViewDelegate
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        
        self.textView?.isHidden = false
        if( text == "\n" ) {
            return false
        }
        
        if( textView.text == "" && text == "" ) {
            if let contactKey = self.contactKeys?.last {
                if let contacts = self.contacts {
                    self.selectedContactBubble = (contacts[contactKey])
                    self.selectedContactBubble?.select()
                }
            }
        }
        
        return true
    }
    
    func textViewDidChange(_ textView: UITextView) {
        self.delegate?.contactPickerTextViewDidChange(textView.text)
        
        if( textView.text == "" && self.contacts?.count == 0) {
            self.placeholderLabel?.isHidden = false
        } else {
            self.placeholderLabel?.isHidden = true
        }
        
    }
    
    // MARK: - EVContactBubbleDelegate Methods
    
    func contactBubbleWasSelected(_ contactBubble: EVContactBubble) -> Void {
        if( self.selectedContactBubble != nil ) {
            self.selectedContactBubble?.unSelect()
        }
        
        self.selectedContactBubble = contactBubble
        self.textView?.resignFirstResponder()
        self.textView?.text = ""
        self.textView?.isHidden = true
    }
    
    func contactBubbleWasUnSelected(_ contactBubble: EVContactBubble) -> Void {
        self.textView?.becomeFirstResponder()
        self.textView?.text = ""
        self.textView?.isHidden = false
    }
    
    func contactBubbleShouldBeRemoved(_ contactBubble: EVContactBubble) -> Void {
        self.removeContactBubble(contactBubble)
    }
    
    // MARK: - Gesture Recognizer
    
    @objc func handleTapGesture() -> Void {
        if(self.limitToOne && self.contactKeys?.count == 1) {
            return
        }
        
        self.scrollToBottomWithAnimation(true)
        
        self.textView?.isHidden = false
        self.textView?.becomeFirstResponder()
        
        self.selectedContactBubble?.unSelect()
        self.selectedContactBubble = nil
        
    }
    
    // MARK: - UIScrollViewDelegate
    
    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        if(_shouldSelectTextView) {
            _shouldSelectTextView = false
            self.selectTextView()
        }
    }
    
}