Commit ebfbb737 by Daniel Dahan

update

parents
#OS X
.DS_Store
# jetbrains
.idea
# Xcode
build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
xcuserdata
profile
*.moved-aside
*.playground
Pod::Spec.new do |s|
s.name = 'GK'
s.version = '3.15.0'
s.license = { :type => "AGPLv3+", :file => "LICENSE" }
s.summary = 'A powerful iOS / OSX framework for data-driven design.'
s.homepage = 'https://github.com/GraphKit/GraphKit'
s.social_media_url = 'https://www.facebook.com/graphkit'
s.authors = { 'GraphKit Inc.' => 'daniel@graphkit.io' }
s.source = { :git => 'https://github.com/GraphKit/GraphKit.git', :tag => s.version }
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.10'
s.source_files = 'Source/*.swift'
s.requires_arc = true
end
This diff is collapsed. Click to expand it.
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
import AVFoundation
@objc(PreviewDelegate)
public protocol PreviewDelegate {
optional func preview(preview: Preview!, tappedToFocusAt point: CGPoint)
optional func preview(preview: Preview!, tappedToExposeAt point: CGPoint)
optional func preview(preview: Preview!, tappedToReset focus: UIView!, exposure: UIView!)
}
public class Preview: UIView {
/**
* boxBounds
* A static property that sets the initial size of the focusBox and exposureBox properties.
*/
static public var boxBounds: CGRect = CGRectMake(0, 0, 150, 150)
/**
* delegate
* An optional instance of PreviewDelegate to handle events that are triggered during various
* stages of engagement.
*/
public weak var delegate: PreviewDelegate?
/**
* tapToFocusEnabled
* A mutator and accessor that enables and disables tap to focus gesture.
*/
public var tapToFocusEnabled: Bool {
get {
return singleTapRecognizer!.enabled
}
set(value) {
singleTapRecognizer!.enabled = value
}
}
/**
* tapToExposeEnabled
* A mutator and accessor that enables and disables tap to expose gesture.
*/
public var tapToExposeEnabled: Bool {
get {
return doubleTapRecognizer!.enabled
}
set(value) {
doubleTapRecognizer!.enabled = value
}
}
/**
* override for layerClass
*/
override public class func layerClass() -> AnyClass {
return AVCaptureVideoPreviewLayer.self
}
/**
* session
* A mutator and accessor for the preview AVCaptureSession value.
*/
public var session: AVCaptureSession {
get {
return (layer as! AVCaptureVideoPreviewLayer).session
}
set(value) {
(layer as! AVCaptureVideoPreviewLayer).session = value
}
}
/**
* focusBox
* An optional UIView for the focusBox animation. This is used when the
* tapToFocusEnabled property is set to true.
*/
public var focusBox: UIView?
/**
* exposureBox
* An optional UIView for the exposureBox animation. This is used when the
* tapToExposeEnabled property is set to true.
*/
public var exposureBox: UIView?
/**
* singleTapRecognizer
* Gesture recognizer for single tap.
*/
private var singleTapRecognizer: UITapGestureRecognizer?
/**
* doubleTapRecognizer
* Gesture recognizer for double tap.
*/
private var doubleTapRecognizer: UITapGestureRecognizer?
/**
* doubleDoubleTapRecognizer
* Gesture recognizer for double/double tap.
*/
private var doubleDoubleTapRecognizer: UITapGestureRecognizer?
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override public init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
public init() {
super.init(frame: CGRectZero)
setTranslatesAutoresizingMaskIntoConstraints(false)
setupView()
}
/**
* handleSingleTap
* @param recognizer: UIGestureRecognizer
* @delegate Calls preview(preview: Preview!, tappedToFocusAt point: CGPoint)
*/
internal func handleSingleTap(recognizer: UIGestureRecognizer) {
let point: CGPoint = recognizer.locationInView(self)
runBoxAnimationOnView(focusBox, point: point)
delegate?.preview?(self, tappedToFocusAt: captureDevicePointForPoint(point))
}
/**
* handleDoubleTap
* @param recognizer: UIGestureRecognizer
* @delegate Calls preview(preview: Preview!, tappedToExposeAt point: CGPoint)
*/
internal func handleDoubleTap(recognizer: UIGestureRecognizer) {
let point: CGPoint = recognizer.locationInView(self)
runBoxAnimationOnView(exposureBox, point: point)
delegate?.preview?(self, tappedToExposeAt: captureDevicePointForPoint(point))
}
/**
* handleDoubleDoubleTap
* @param recognizer: UIGestureRecognizer
*/
internal func handleDoubleDoubleTap(recognizer: UIGestureRecognizer) {
runResetAnimation()
}
/**
* setupView
* Common setup for view.
*/
private func setupView() {
let captureLayer: AVCaptureVideoPreviewLayer = layer as! AVCaptureVideoPreviewLayer
captureLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
singleTapRecognizer = UITapGestureRecognizer(target: self, action: "handleSingleTap:")
singleTapRecognizer!.numberOfTapsRequired = 1
doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "handleDoubleTap:")
doubleTapRecognizer!.numberOfTapsRequired = 2
doubleDoubleTapRecognizer = UITapGestureRecognizer(target: self, action: "handleDoubleDoubleTap:")
doubleDoubleTapRecognizer!.numberOfTapsRequired = 2
doubleDoubleTapRecognizer!.numberOfTouchesRequired = 2
addGestureRecognizer(singleTapRecognizer!)
addGestureRecognizer(doubleTapRecognizer!)
addGestureRecognizer(doubleDoubleTapRecognizer!)
singleTapRecognizer!.requireGestureRecognizerToFail(doubleTapRecognizer!)
focusBox = viewWithColor(.redColor())
exposureBox = viewWithColor(.blueColor())
addSubview(focusBox!)
addSubview(exposureBox!)
}
/**
* viewWithColor
* Initializes a UIView with a set UIColor.
* @param color: UIColor
* @return Initialized UIView
*/
private func viewWithColor(color: UIColor) -> UIView {
let view: UIView = UIView(frame: Preview.boxBounds)
view.backgroundColor = .clearColor()
view.layer.borderColor = color.CGColor
view.layer.borderWidth = 5
view.hidden = true
return view
}
/**
* runBoxAnimationOnView
* Runs the animation used for focusBox and exposureBox on single and double
* taps respectively at a given point.
* @param view: UIView!
* @param point: CGPoint
*/
private func runBoxAnimationOnView(view: UIView!, point: CGPoint) {
view.center = point
view.hidden = false
UIView.animateWithDuration(0.15, delay: 0, options: .CurveEaseInOut, animations: { _ in
view.layer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0)
}) { _ in
let delayInSeconds: Double = 0.5
let popTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(popTime, dispatch_get_main_queue()) {
view.hidden = true
view.transform = CGAffineTransformIdentity
}
}
}
/**
* captureDevicePointForPoint
* Interprets the correct point from touch to preview layer.
* @param point: CGPoint
* @return A translated CGPoint value.
*/
private func captureDevicePointForPoint(point: CGPoint) -> CGPoint {
let previewLayer: AVCaptureVideoPreviewLayer = layer as! AVCaptureVideoPreviewLayer
return previewLayer.captureDevicePointOfInterestForPoint(point)
}
/**
* runResetAnimation
* Executes the reset animation for focus and exposure.
* @delegate Calls preview(preview: Preview!, tappedToReset focus: UIView!, exposure: UIView!)
*/
private func runResetAnimation() {
if !tapToFocusEnabled && !tapToExposeEnabled {
return
}
let previewLayer: AVCaptureVideoPreviewLayer = layer as! AVCaptureVideoPreviewLayer
let centerPoint: CGPoint = previewLayer.pointForCaptureDevicePointOfInterest(CGPointMake(0.5, 0.5))
focusBox!.center = centerPoint
exposureBox!.center = centerPoint
exposureBox!.transform = CGAffineTransformMakeScale(1.2, 1.2)
focusBox!.hidden = false
exposureBox!.hidden = false
UIView.animateWithDuration(0.15, delay: 0, options: .CurveEaseInOut, animations: { _ in
self.focusBox!.layer.transform = CATransform3DMakeScale(0.5, 0.5, 1.0)
self.exposureBox!.layer.transform = CATransform3DMakeScale(0.7, 0.7, 1.0)
}) { _ in
let delayInSeconds: Double = 0.5
let popTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(popTime, dispatch_get_main_queue()) {
self.focusBox!.hidden = true
self.exposureBox!.hidden = true
self.focusBox!.transform = CGAffineTransformIdentity
self.exposureBox!.transform = CGAffineTransformIdentity
self.delegate?.preview?(self, tappedToReset: self.focusBox!, exposure: self.exposureBox!)
}
}
}
}
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
class FabButton : UIButton {
var lineWidth: CGFloat = 2.0
var color: UIColor?
var pulseColor: UIColor?
private var vLine: UIView?
private var hLine: UIView?
private var backgroundColorView: UIView?
private var pulseView: UIView?
override func drawRect(rect: CGRect) {
setupContext(rect)
setupBackgroundColorView()
setupPlus()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
applyShadow()
}
required override init(frame: CGRect) {
super.init(frame: frame)
initialize()
applyShadow()
}
func initialize() {
color = UIColor.redColor()
pulseColor = UIColor.whiteColor()
setTranslatesAutoresizingMaskIntoConstraints(false)
}
func setupContext(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context);
CGContextAddEllipseInRect(context, rect)
CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
CGContextFillPath(context)
CGContextRestoreGState(context);
}
// We need this view so we can use the masksToBounds
// so the pulse doesn't animate off the button
func setupBackgroundColorView() {
backgroundColorView = UIView()
backgroundColorView!.frame = self.bounds
backgroundColorView!.layer.cornerRadius = boundsW() / 2.0
backgroundColorView!.backgroundColor = color
backgroundColorView!.layer.masksToBounds = true
self.insertSubview(backgroundColorView!, atIndex: 0)
}
// I make the + with two views because
// The label is not actually vertically and horizontally aligned
// Quick hack instead of subclassing UILabel and override drawTextInRect
func setupPlus() {
setupVerticalLine()
setupHorizontalLine()
}
func setupVerticalLine() {
vLine = UIView(frame: CGRectMake(0, 0, lineWidth, CGRectGetHeight(backgroundColorView!.frame) / 3.0))
vLine!.backgroundColor = UIColor.whiteColor()
vLine!.center = backgroundColorView!.center
backgroundColorView!.addSubview(vLine!)
}
func setupHorizontalLine() {
hLine = UIView(frame: CGRectMake(0, 0, CGRectGetWidth(backgroundColorView!.frame) / 3.0, lineWidth))
hLine!.backgroundColor = UIColor.whiteColor()
hLine!.center = backgroundColorView!.center
backgroundColorView!.addSubview(hLine!)
}
func applyShadow() {
layer.shadowOffset = CGSizeMake(1, 1)
layer.shadowColor = UIColor.blackColor().CGColor
layer.shadowOpacity = 0.5
layer.shadowRadius = 5
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
pulseTouches(touches)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
shrink()
removePulse()
}
func pulseTouches(touches: NSSet) {
let touch = touches.allObjects.last as! UITouch
let touchLocation = touch.locationInView(self)
pulseView = UIView()
pulseView!.frame = CGRectMake(0, 0, boundsW(), boundsH())
pulseView!.layer.cornerRadius = boundsW() / 2.0
pulseView!.center = touchLocation
pulseView!.backgroundColor = pulseColor!.colorWithAlphaComponent(0.5)
backgroundColorView!.addSubview(pulseView!)
UIView.animateWithDuration(0.3, animations: {
self.pulseView!.transform = CGAffineTransformMakeScale(3, 3)
self.transform = CGAffineTransformMakeScale(1.1, 1.1)
}, completion: nil)
}
func shrink() {
UIView.animateWithDuration(0.3, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: nil, animations: {
self.transform = CGAffineTransformIdentity
}, completion: nil)
}
func removePulse() {
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.pulseView?.alpha = 0.0
}) { (finished) -> Void in
self.pulseView?.removeFromSuperview()
self.pulseView = nil
}
}
}
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
class FlatButton : UIButton {
var textColor: UIColor?
var pulseColor: UIColor?
private var vLine: UIView = UIView()
private var hLine: UIView = UIView()
private var backgroundColorView: UIView = UIView()
private var pulseView: UIView?
override func drawRect(rect: CGRect) {
setupContext(rect)
setupBackgroundColorView()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
applyShadow()
}
required override init(frame: CGRect) {
super.init(frame: frame)
initialize()
applyShadow()
}
func initialize() {
pulseColor = UIColor.whiteColor()
setTranslatesAutoresizingMaskIntoConstraints(false)
}
func setupContext(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
CGContextFillPath(context)
CGContextRestoreGState(context);
}
// We need this view so we can use the masksToBounds
// so the pulse doesn't animate off the button
func setupBackgroundColorView() {
backgroundColorView.frame = self.bounds
backgroundColorView.layer.cornerRadius = 3.0
backgroundColorView.backgroundColor = UIColor.clearColor()
backgroundColorView.layer.masksToBounds = true
self.insertSubview(backgroundColorView, atIndex: 0)
}
func applyShadow() {
layer.shadowOffset = CGSizeMake(1, 1)
layer.shadowColor = UIColor.blackColor().CGColor
layer.shadowOpacity = 0.5
layer.shadowRadius = 5
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
pulseTouches(touches)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
shrink()
removePulse()
}
func pulseTouches(touches: NSSet) {
let touch = touches.allObjects.last as! UITouch
let touchLocation = touch.locationInView(self)
pulseView = UIView()
pulseView!.frame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height)
pulseView!.layer.cornerRadius = boundsH() / 2.0
pulseView!.center = touchLocation
pulseView!.backgroundColor = pulseColor!.colorWithAlphaComponent(0.5)
backgroundColorView.addSubview(pulseView!)
textColor = self.titleLabel?.textColor
UIView.animateWithDuration(0.3, animations: {
self.pulseView!.transform = CGAffineTransformMakeScale(10, 10)
self.transform = CGAffineTransformMakeScale(1.05, 1.1)
self.setTitleColor(UIColor.whiteColor(), forState: .Normal)
}, completion: nil)
}
func shrink() {
UIView.animateWithDuration(0.3, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: nil, animations: {
self.transform = CGAffineTransformIdentity
}, completion: nil)
}
func removePulse() {
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.pulseView!.alpha = 0.0
self.setTitleColor(self.textColor, forState: .Normal)
}) { (finished) -> Void in
self.pulseView!.removeFromSuperview()
self.pulseView = nil
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>io.graphkit.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
This diff is collapsed. Click to expand it.
//
// Copyright (C) 2015 Adam Dahan <https://github.com/adamdahan>.
// Copyright (C) 2015 Daniel Dahan <https://github.com/danieldahan>.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
extension UIView {
func frameW() -> CGFloat {
return self.frame.size.width
}
func frameH() -> CGFloat {
return self.frame.size.height
}
func frameX() -> CGFloat {
return self.frame.origin.x
}
func frameY() -> CGFloat {
return self.frame.origin.y
}
func boundsW() -> CGFloat {
return self.bounds.size.width
}
func boundsH() -> CGFloat {
return self.bounds.size.height
}
func boundsX() -> CGFloat {
return self.bounds.origin.x
}
func boundsY() -> CGFloat {
return self.bounds.origin.y
}
}
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
class RaisedButton : UIButton {
var color: UIColor?
var pulseColor: UIColor?
private var vLine: UIView = UIView()
private var hLine: UIView = UIView()
private var backgroundColorView: UIView = UIView()
private var pulseView: UIView?
override func drawRect(rect: CGRect) {
setupContext(rect)
setupBackgroundColorView()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
applyShadow()
}
required override init(frame: CGRect) {
super.init(frame: frame)
initialize()
applyShadow()
}
func initialize() {
color = UIColor.redColor()
pulseColor = UIColor.whiteColor()
setTranslatesAutoresizingMaskIntoConstraints(false)
}
func setupContext(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
CGContextFillPath(context)
CGContextRestoreGState(context);
}
// We need this view so we can use the masksToBounds
// so the pulse doesn't animate off the button
func setupBackgroundColorView() {
backgroundColorView.frame = self.bounds
backgroundColorView.layer.cornerRadius = 3.0
backgroundColorView.backgroundColor = color
backgroundColorView.layer.masksToBounds = true
self.insertSubview(backgroundColorView, atIndex: 0)
}
func applyShadow() {
layer.shadowOffset = CGSizeMake(1, 1)
layer.shadowColor = UIColor.blackColor().CGColor
layer.shadowOpacity = 0.5
layer.shadowRadius = 5
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
pulseTouches(touches)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
shrink()
removePulse()
}
func pulseTouches(touches: NSSet) {
let touch = touches.allObjects.last as! UITouch
let touchLocation = touch.locationInView(self)
pulseView = UIView()
pulseView!.frame = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height)
pulseView!.layer.cornerRadius = boundsH() / 2.0
pulseView!.center = touchLocation
pulseView!.backgroundColor = pulseColor!.colorWithAlphaComponent(0.5)
backgroundColorView.addSubview(pulseView!)
UIView.animateWithDuration(0.3, animations: {
self.pulseView!.transform = CGAffineTransformMakeScale(10, 10)
self.transform = CGAffineTransformMakeScale(1.05, 1.1)
}, completion: nil)
}
func shrink() {
UIView.animateWithDuration(0.3, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: nil, animations: {
self.transform = CGAffineTransformIdentity
}, completion: nil)
}
func removePulse() {
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.pulseView!.alpha = 0.0
}) { (finished) -> Void in
self.pulseView!.removeFromSuperview()
self.pulseView = nil
}
}
}
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
@objc(TextDelegate)
public protocol TextDelegate {
optional func textStorageWillProcessEdit(text: Text!, textStorage: TextStorage!, string: String!, range: NSRange)
optional func textStorageDidProcessEdit(text: Text!, textStorage: TextStorage!, string: String!, result: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer<ObjCBool>)
}
public class Text: NSObject {
/**
* searchPattern
* A string representation of the regular expression that matches text within
* the TextStorage string property. By default, the search pattern recognizes
* words and emoji characters beginning with hashtags.
*/
public var searchPattern: String = "(^|\\s)#[\\d\\w_\u{203C}\u{2049}\u{20E3}\u{2122}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{23E9}-\u{23EC}\u{23F0}\u{23F3}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2601}\u{260E}\u{2611}\u{2614}-\u{2615}\u{261D}\u{263A}\u{2648}-\u{2653}\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267F}\u{2693}\u{26A0}-\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270C}\u{270F}\u{2712}\u{2714}\u{2716}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{1F004}\u{1F0CF}\u{1F170}-\u{1F171}\u{1F17E}-\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E7}-\u{1F1EC}\u{1F1EE}-\u{1F1F0}\u{1F1F3}\u{1F1F5}\u{1F1F7}-\u{1F1FA}\u{1F201}-\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}-\u{1F251}\u{1F300}-\u{1F320}\u{1F330}-\u{1F335}\u{1F337}-\u{1F37C}\u{1F380}-\u{1F393}\u{1F3A0}-\u{1F3C4}\u{1F3C6}-\u{1F3CA}\u{1F3E0}-\u{1F3F0}\u{1F400}-\u{1F43E}\u{1F440}\u{1F442}-\u{1F4F7}\u{1F4F9}-\u{1F4FC}\u{1F500}-\u{1F507}\u{1F509}-\u{1F53D}\u{1F550}-\u{1F567}\u{1F5FB}-\u{1F640}\u{1F645}-\u{1F64F}\u{1F680}-\u{1F68A}]+" {
didSet {
textStorage.searchExpression = NSRegularExpression(pattern: searchPattern, options: nil, error: nil)
}
}
/**
* textStorage
* Reference to wrapped NSTextStorage
*/
public let textStorage: TextStorage
/**
* delegate
* An optional instance of TextDelegate to handle text processing events
*
*/
public weak var delegate: TextDelegate?
override public init() {
textStorage = TextStorage()
super.init()
textStorage.searchExpression = NSRegularExpression(pattern: searchPattern, options: nil, error: nil)
textStorage.textStorageWillProcessEdit = { (textStorage: TextStorage!, string: String!, range: NSRange) -> Void in
self.delegate?.textStorageWillProcessEdit?(self, textStorage: textStorage, string: string, range: range)
}
textStorage.textStorageDidProcessEdit = { (textStorage: TextStorage!, result: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
self.delegate?.textStorageDidProcessEdit?(self, textStorage: textStorage, string: textStorage.string, result: result, flags: flags, stop: stop)
}
}
/**
* string
* Managed string value.
*/
public var string: String {
get {
return textStorage.string
}
}
/**
* matches
* An array of matches found in the TextStorage string property based on the
* searchPattern property.
*/
public var matches: Array<String> {
get {
let results: Array<NSTextCheckingResult> = textStorage.searchExpression!.matchesInString(string, options: nil, range: NSMakeRange(0, count(string.utf16))) as! Array<NSTextCheckingResult>
return unique(map(results) {(self.string as NSString).substringWithRange($0.range)})
}
}
/**
* unique
* Ensures a unique value in the matches return array.
*/
private func unique<S: SequenceType, E: Hashable where E == S.Generator.Element>(source: S) -> [E] {
var seen: [E:Bool] = [:]
return filter(source) {nil == seen.updateValue(true, forKey: $0)}
}
}
\ No newline at end of file
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
internal typealias TextStorageWillProcessEdit = (TextStorage!, String!, NSRange) -> Void
internal typealias TextStorageDidProcessEdit = (TextStorage!, NSTextCheckingResult!, NSMatchingFlags, UnsafeMutablePointer<ObjCBool>) -> Void
public class TextStorage: NSTextStorage {
/**
* store
* Acts as the model, storing the string value.
*/
private lazy var store: NSMutableAttributedString = NSMutableAttributedString()
/**
* searchExpression
* Matches text within the TextStorage string property.
*/
internal var searchExpression: NSRegularExpression?
/**
* textStorageWillProcessEdit
* If set, this block callback executes when there is a change in the TextStorage
* string value.
*/
internal var textStorageWillProcessEdit: TextStorageWillProcessEdit?
/**
* textStorageDidProcessEdit
* If set, this block callback executes when a match is detected after a change in
* the TextStorage string value.
*/
internal var textStorageDidProcessEdit: TextStorageDidProcessEdit?
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override public init() {
super.init()
}
/**
* string
* Managed string value.
*/
override public var string: String {
get {
return store.string
}
}
override public func processEditing() {
let range: NSRange = (string as NSString).paragraphRangeForRange(editedRange)
textStorageWillProcessEdit?(self, string, range)
searchExpression!.enumerateMatchesInString(string, options: nil, range: range) { (result: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) in
self.textStorageDidProcessEdit?(self, result, flags, stop)
}
super.processEditing()
}
override public func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [NSObject : AnyObject] {
return store.attributesAtIndex(location, effectiveRange: range)
}
override public func replaceCharactersInRange(range: NSRange, withString str: String) {
store.replaceCharactersInRange(range, withString: str)
edited(NSTextStorageEditActions.EditedCharacters, range: range, changeInLength: count(str.utf16) - range.length)
}
override public func setAttributes(attrs: [NSObject : AnyObject]?, range: NSRange) {
store.setAttributes(attrs, range: range)
edited(NSTextStorageEditActions.EditedAttributes, range: range, changeInLength: 0)
}
}
\ No newline at end of file
//
// Copyright (C) 2015 GraphKit, Inc. <http://graphkit.io> and other GraphKit contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program located at the root of the software package
// in a file called LICENSE. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
private var defaultTextColor: UIColor = UIColor(red: 33/255, green: 33/255, blue: 33/255, alpha: 1)
private var defaultPlaceholderColor: UIColor = UIColor(red: 159/255, green: 160/255, blue: 164/255, alpha: 1)
public class TextView: UITextView {
/**
* label
* Placeholder label.
*/
private lazy var label: UILabel = UILabel()
/**
* labelConstraints
* Autoresize constraints for the placeholder label.
*/
private var labelConstraints: Array<NSLayoutConstraint>?
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
if CGRectZero == frame {
setTranslatesAutoresizingMaskIntoConstraints(false)
}
setupView()
}
/**
* deinit
* Notification observer removed from UITextView.
*/
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextViewTextDidChangeNotification, object: nil)
}
/**
* placeholder
* The placeholder label string.
*/
public var placeholder: String = "" {
didSet {
label.text = placeholder
}
}
/**
* placeholderColor
* The placeholder color.
*/
public var placeholderColor: UIColor = defaultPlaceholderColor {
didSet {
label.textColor = placeholderColor
}
}
/**
* font
* Font to use for placeholder based on UITextView font.
*/
override public var font: UIFont! {
didSet {
label.font = font
}
}
/**
* textAlignment
* Sets placeholder textAlignment based on UITextView textAlignment.
*/
override public var textAlignment: NSTextAlignment {
didSet {
label.textAlignment = textAlignment
}
}
/**
* text
* When set, updates the placeholder text.
*/
override public var text: String! {
didSet {
textViewTextDidChange()
}
}
/**
* attributedText
* When set, updates the placeholder attributedText.
*/
override public var attributedText: NSAttributedString! {
didSet {
textViewTextDidChange()
}
}
/**
* textContainerInset
* When set, updates the placeholder constraints.
*/
override public var textContainerInset: UIEdgeInsets {
didSet {
updateLabelConstraints()
}
}
override public func layoutSubviews() {
super.layoutSubviews()
label.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}
/**
* textViewTextDidChange
* Updates the label visibility when text is empty or not.
*/
internal func textViewTextDidChange() {
label.hidden = !text.isEmpty
}
/**
* setupView
* Sets up the common initilized values.
*/
private func setupView() {
backgroundColor = .clearColor()
textColor = defaultTextColor
label.font = font
label.textColor = defaultPlaceholderColor
label.textAlignment = textAlignment
label.text = placeholder
label.numberOfLines = 0
label.backgroundColor = UIColor.clearColor()
label.setTranslatesAutoresizingMaskIntoConstraints(false)
addSubview(label)
// label needs to be added to the view
// hierarchy before setting insets
textContainerInset = UIEdgeInsetsMake(16, 16, 16, 16)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textViewTextDidChange", name: UITextViewTextDidChangeNotification, object: nil)
updateLabelConstraints()
}
/**
* updateLabelConstraints
* Updates the placeholder constraints.
*/
private func updateLabelConstraints() {
if nil != labelConstraints {
removeConstraints(labelConstraints!)
}
var constraints: Array<NSLayoutConstraint> = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(left)-[placeholder]-(right)-|", options: nil, metrics: ["left": textContainerInset.left + textContainer.lineFragmentPadding, "right": textContainerInset.right + textContainer.lineFragmentPadding], views: ["placeholder": label]) as! Array<NSLayoutConstraint>
constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(top)-[placeholder]-(>=bottom)-|", options: nil, metrics: ["top": textContainerInset.top, "bottom": textContainerInset.bottom], views: ["placeholder": label]) as! Array<NSLayoutConstraint>
labelConstraints = constraints
addConstraints(constraints)
}
}
//
// FocusKitTests.swift
// FocusKitTests
//
// Created by Daniel Dahan on 2015-08-22.
// Copyright (c) 2015 GraphKit Inc. All rights reserved.
//
import UIKit
import XCTest
class FocusKitTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
XCTAssert(true, "Pass")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock() {
// Put the code you want to measure the time of here.
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>io.graphkit.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment