Advertisement
Guest User

Untitled

a guest
Oct 25th, 2017
128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 16.51 KB | None | 0 0
  1. import Foundation
  2. import UIKit
  3.  
  4. class CurveText: UIView, UIGestureRecognizerDelegate, UITextViewDelegate {
  5.     var tf = UITextView()
  6.     private let shadowLayer = CALayer()
  7.     private let textLayer = CALayer()
  8.     private let defaultTextViewLeftInset = CGFloat(5)
  9.     private let defaultTextViewRightInset = CGFloat(5)
  10.    
  11.     var curvePattern = (CGPoint(x: 0, y: 0), CGPoint(x: 90, y: 50), CGPoint(x: 180, y: 0)) {
  12.         didSet {
  13.             curve = curvePattern
  14.             setNeedsLayout()
  15.         }
  16.     }
  17.     var curve = (CGPoint(x: 0, y: 0), CGPoint(x: 90, y: 50), CGPoint(x: 180, y: 0))
  18.    
  19.     var min = CGFloat(20) { didSet { setNeedsLayout() } }
  20.     var max = CGFloat(40) { didSet { setNeedsLayout() } }
  21.     var maxPosition: CGFloat = 0 { didSet { setNeedsLayout() } } // 0...1
  22.    
  23.     let minCharsForStartCalculating = 3
  24.    
  25.     var textColor = UIColor.white { didSet { setNeedsDisplay() } }
  26.     var shadowOffset = CGPoint(x: 1, y: 0) { didSet { setNeedsDisplay() } }
  27.     var shadowBlur = CGFloat(0) { didSet { setNeedsDisplay() } }
  28.     var shadowWeight = CGFloat(1) { didSet { setNeedsDisplay() } }
  29.     var shadowColor = UIColor.black { didSet { setNeedsDisplay() } }
  30.    
  31.     var text: String = "Psssh" { didSet { setNeedsLayout() } }
  32.    
  33.     var font: UIFont = UIFont.systemFont(ofSize: 1) { didSet { setNeedsLayout() } }
  34.    
  35.     func update() {
  36.         let newString = self.text
  37.         let attrStr = NSMutableAttributedString(string: newString)
  38.        
  39.         attrStr.addAttributes([
  40.             NSForegroundColorAttributeName: UIColor.red.withAlphaComponent(0),
  41.             NSKernAttributeName: 0
  42.         ], range: NSRange(location: 0, length: attrStr.length))
  43.        
  44.         for (index, _) in newString.characters.enumerated() {
  45.             let base = CGFloat([newString.characters.count, minCharsForStartCalculating].max()!) - 1
  46.            
  47.             var radius: CGFloat
  48.             radius = [1 - maxPosition, maxPosition][base * maxPosition - CGFloat(index) < 0 ? 0 : 1]
  49.             //radius = [1 - maxPosition, maxPosition].max()!
  50.             radius = [1, radius * base].max()!
  51.             let koef = abs(base * maxPosition - CGFloat(index)) / radius
  52.             let fontSize = CGFloat(Int(CGFloat(max - min) * (1 - koef) + CGFloat(min) * 5) / 5)
  53.            
  54.             //let fontSize = CGFloat((1 - CGFloat(index) / CGFloat(newValue.characters.count)) * (CGFloat(max) - CGFloat(min)) + CGFloat(min))
  55.             let font = self.font.withSize(fontSize)
  56.            
  57.             attrStr.addAttribute(NSFontAttributeName, value: font, range: NSRange(location: index, length: 1))
  58.         }
  59.        
  60.         let l = length(CGPoint(x: curve.0.x, y: 0), CGPoint(x: curve.2.x, y: 0))
  61.         if newString.count >= minCharsForStartCalculating || l <= 10 {
  62.             curve.2 = interpolate(curve.0, curve.2, attrStr.size().width / l)
  63.             curve.1 = interpolate(curve.0, curve.1, attrStr.size().width / l)
  64.         }
  65.        
  66.         var baselineGlobalOffset: CGFloat = 0
  67.         var currentXPosition = CGFloat(0)
  68.         for (index, char) in newString.characters.enumerated() {
  69.             let charAttrs = attrStr.attributes(at: index, effectiveRange: nil)
  70.             let font = charAttrs[NSFontAttributeName] as! UIFont
  71.            
  72.             let charSize = NSString(string: String(char)).size(attributes: charAttrs)
  73.             let baselineOffset = -(quadraticBezier(curve, rootsQuadraticBezier(curve, currentXPosition + charSize.width / 2).0).y)
  74.            
  75.             baselineGlobalOffset = [font.ascender + [baselineOffset, 0].max()!, baselineGlobalOffset].max()!
  76.            
  77.             attrStr.addAttribute(NSBaselineOffsetAttributeName, value: baselineOffset, range: NSRange(location: index, length: 1))
  78.             currentXPosition += charSize.width
  79.            
  80.             var unichars = [UniChar](String(char).utf16)
  81.             var glyphs = [CGGlyph](repeating: 0, count: unichars.count)
  82.             var boundingRects = [CGRect](repeating: .zero, count: unichars.count)
  83.             var advances = [CGSize](repeating: .zero, count: unichars.count)
  84.            
  85.             let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
  86.             let _ = CTFontGetBoundingRectsForGlyphs(font as CTFont, .horizontal, glyphs, &boundingRects, unichars.count)
  87.             let _ = CTFontGetAdvancesForGlyphs(font as CTFont, .horizontal, glyphs, &advances, unichars.count)
  88.            
  89.             attrStr.addAttribute("advance", value: advances.first!, range: NSRange(location: index, length: 1))
  90.             attrStr.addAttribute("boundingRect", value: boundingRects.first!, range: NSRange(location: index, length: 1))
  91.             attrStr.addAttribute("glyph", value: glyphs.first!, range: NSRange(location: index, length: 1))
  92.            
  93.             if gotGlyphs {
  94.                 for glyph in glyphs {
  95.                     var matrix = CGAffineTransform.identity
  96.                     if let path = CTFontCreatePathForGlyph(font as CTFont, glyph, &matrix) {
  97.                         attrStr.addAttribute("path", value: path, range: NSRange(location: index, length: 1))
  98.                     }
  99.                 }
  100.             }
  101.         }
  102.        
  103.         let selection = tf.selectedTextRange
  104.         tf.attributedText = attrStr
  105.         tf.selectedTextRange = selection
  106.        
  107.         tf.bounds.size = attrStr.size()
  108.         tf.frame.origin.y = -baselineGlobalOffset
  109.         tf.frame.origin.x = 0
  110.         tf.frame = tf.frame.insetBy(dx: -(defaultTextViewLeftInset + defaultTextViewRightInset) / 2, dy: 0)
  111.     }
  112.    
  113.     private func updateVector(_ attrStr: NSMutableAttributedString) {
  114.         let glyphsPath = CGMutablePath()
  115.        
  116.         var advancesSum = CGFloat(0)
  117.        
  118.         for (index, _) in self.text.enumerated() {
  119. //            let font = attrStr.attribute(NSFontAttributeName, at: index, effectiveRange: nil) as! UIFont
  120.             let baselineOffset = attrStr.attribute(NSBaselineOffsetAttributeName, at: index, effectiveRange: nil) as! CGFloat
  121.             let kern = attrStr.attribute(NSKernAttributeName, at: index, effectiveRange: nil) as? CGFloat ?? 0
  122.            
  123.             let boundingRect = attrStr.attribute("boundingRect", at: index, effectiveRange: nil) as! CGRect
  124.             let advance = attrStr.attribute("advance", at: index, effectiveRange: nil) as! CGSize
  125.             //let glyph = attrStr.attribute("glyph", at: index, effectiveRange: nil) as! CGGlyph
  126.            
  127.             let path = attrStr.attribute("path", at: index, effectiveRange: nil) as! CGPath?
  128.            
  129.             var prevBaseline: CGFloat? = nil
  130.             var prevAdvance: CGSize? = nil
  131.             if 0..<self.text.count ~= (index - 1) {
  132.                 prevBaseline = attrStr.attribute(NSBaselineOffsetAttributeName, at: index - 1, effectiveRange: nil) as? CGFloat
  133.                 prevAdvance = attrStr.attribute("advance", at: index - 1, effectiveRange: nil) as? CGSize
  134.             }
  135.             var nextBaseline: CGFloat? = nil
  136.             if 0..<self.text.count ~= (index + 1) {
  137.                 prevBaseline = attrStr.attribute(NSBaselineOffsetAttributeName, at: index + 1, effectiveRange: nil) as? CGFloat
  138.             }
  139.            
  140.             var angle = CGFloat(0)
  141.             let point1: CGPoint
  142.             let point2: CGPoint
  143.             if let prevBaseline = prevBaseline, let prevAdvance = prevAdvance {
  144.                 point1 = CGPoint(x: advancesSum - prevAdvance.width / 2, y: prevBaseline)
  145.                 point2 = CGPoint(x: advancesSum + advance.width / 2, y: -baselineOffset)
  146.             } else {
  147.                 point1 = CGPoint(x: curve.0.x, y: curve.0.y)
  148.                 point2 = CGPoint(x: advancesSum + advance.width / 2, y: -baselineOffset)
  149.             }
  150.             angle = CGFloat(atan2(Double(point2.y - point1.y), Double(point2.x - point1.x))) + CGFloat.pi / 2
  151.            
  152.             if let path = path {
  153.                 let matrix = CGAffineTransform.identity.translatedBy(x: advancesSum, y: -baselineOffset).scaledBy(x: 1, y: -1).rotated(by: angle)
  154.                 glyphsPath.addPath(path, transform: matrix)
  155.                 let rect = CGMutablePath()
  156.                 var copyMatrix = matrix.rotated(by: angle * 2)
  157.                 let copyPath = path.copy(using: &copyMatrix)!
  158.                 rect.addRect(copyPath.boundingBoxOfPath)
  159.                 glyphsPath.addPath(rect)
  160.             }
  161.             advancesSum += advance.width + kern
  162.         }
  163.        
  164.         shadowLayer.sublayers?.forEach{ $0.removeFromSuperlayer() }
  165.         if shadowWeight > 0 {
  166.             let shadowSprite = CALayer()
  167.             let strokeLayer = CAShapeLayer()
  168.             strokeLayer.strokeColor = UIColor.black.cgColor
  169.             strokeLayer.lineWidth = shadowWeight * 2
  170.             strokeLayer.lineCap = kCALineCapRound
  171.             strokeLayer.lineJoin = kCALineJoinRound
  172.             strokeLayer.path = glyphsPath
  173.             strokeLayer.lineDashPattern = [NSNumber(floatLiteral: Double.infinity), 0]
  174.             shadowSprite.addSublayer(strokeLayer)
  175.            
  176.             let fillLayer = CAShapeLayer()
  177.             fillLayer.fillColor = UIColor.black.cgColor
  178.             fillLayer.path = glyphsPath
  179.             shadowSprite.addSublayer(fillLayer)
  180.            
  181.             shadowSprite.shadowColor = shadowColor.cgColor
  182.             let dropCoord = CGFloat(1000)
  183.             shadowSprite.shadowOffset = CGSize(width: self.shadowOffset.x + dropCoord, height: self.shadowOffset.y)
  184.             shadowSprite.shadowRadius = self.shadowBlur
  185.             shadowSprite.shadowOpacity = 1
  186.             shadowSprite.frame.origin.x -= dropCoord
  187.            
  188.             shadowLayer.addSublayer(shadowSprite)
  189.         }
  190.        
  191.         let textShape = CAShapeLayer()
  192.        
  193.         textShape.fillColor = textColor.cgColor
  194.         textShape.path = glyphsPath
  195.         textLayer.sublayers?.forEach{ $0.removeFromSuperlayer() }
  196.         textLayer.addSublayer(textShape)
  197.        
  198.         let lineShape = CAShapeLayer()
  199.         lineShape.strokeColor = UIColor.black.cgColor
  200.         lineShape.lineWidth = 1
  201.         lineShape.fillColor = nil
  202.         let linePath = CGMutablePath()
  203.         linePath.move(to: curve.0)
  204.         linePath.addQuadCurve(to: curve.2, control: curve.1)
  205.         lineShape.path = linePath
  206.         textLayer.addSublayer(lineShape)
  207.        
  208.         self.textLayer.removeAllAnimations()
  209.         self.shadowLayer.removeAllAnimations()
  210.     }
  211.    
  212.     override init(frame: CGRect) {
  213.         super.init(frame: frame)
  214.     }
  215.    
  216.     override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
  217.         return self.frame.contains(point) || self.tf.frame.contains(point)
  218.     }
  219.    
  220.     convenience init() {
  221.         self.init(frame: .zero)
  222.         self.backgroundColor = .white
  223.        
  224.         tf.delegate = self
  225.         tf.backgroundColor = nil
  226.         tf.textContainerInset = .zero
  227.         tf.isScrollEnabled = false
  228.         tf.scrollIndicatorInsets = .zero
  229.         tf.showsVerticalScrollIndicator = false
  230.         tf.showsHorizontalScrollIndicator = false
  231.         tf.clipsToBounds = false
  232.         self.addSubview(tf)
  233.        
  234.        
  235.         self.layer.insertSublayer(shadowLayer, at: 0)
  236.         self.layer.insertSublayer(textLayer, at: 1)
  237.        
  238.        
  239. //        let shape = CAShapeLayer()
  240. //
  241. //        shape.lineWidth = 1
  242. //        shape.lineCap = kCALineCapRound
  243. //        shape.fillColor = nil
  244. //        shape.strokeColor = UIColor.black.cgColor
  245. //        self.layer.insertSublayer(shape, at: 2)
  246. //
  247. //        var angle = CGFloat(1);
  248. //        Timer.scheduledTimer(withTimeInterval: 1 / 60, repeats: true, block: {
  249. //            _ in
  250. //
  251. //            angle -= 0.04
  252. //            if angle < -0.5 {
  253. //                //return
  254. //            }
  255. //            let path = CGMutablePath()
  256. //            path.move(to: self.curve.0)
  257. //            path.addQuadCurve(to: self.curve.2, control: self.curve.1)
  258. //            self.curve.1.y += sin(angle) * 3
  259. //            //self.curve.2.y -= sin(angle) / 4
  260. //            self.curve.0.y -= sin(angle) / 4
  261. //            self.text = (self.text + "")
  262. //            self.setNeedsDisplay()
  263. //
  264. //            shape.path = path
  265. //        })
  266.     }
  267.    
  268.     override func draw(_ rect: CGRect) {
  269.         updateVector(NSMutableAttributedString(attributedString: tf.attributedText))
  270.     }
  271.    
  272.     override func layoutSubviews() {
  273.         update()
  274.         setNeedsDisplay()
  275.     }
  276.    
  277.     func textViewDidChange(_ textView: UITextView) {
  278.         self.text = textView.text
  279.     }
  280.    
  281.     required init?(coder aDecoder: NSCoder) {
  282.         fatalError("init(coder:) has not been implemented")
  283.     }
  284. }
  285.  
  286. func pointAtBrokenLine(_ points: [CGPoint], position: CGFloat) -> (CGPoint, CGPoint)? {
  287.     var sumLength: CGFloat = 0
  288.    
  289.     guard points.count > 1 else {
  290.         return nil
  291.     }
  292.     guard position > 0 else {
  293.         return (points[0], halfVec(points[1], points[0], points[1]))
  294.     }
  295.    
  296.     var prevPoint: CGPoint = points.first!
  297.    
  298.     for i in 1..<points.count {
  299.         let point = points[i]
  300.         let len = length(prevPoint, point)
  301.        
  302.         if sumLength + len == position || i == points.count - 1 && (position - sumLength - len < CGFloat.ulpOfOne * CGFloat(points.count)) {
  303.             let nextPoint = i < points.count - 1 ? points[i + 1] : prevPoint
  304.            
  305.             if (nextPoint != prevPoint) {
  306.                 return (point, CGPoint(x: (point.x - prevPoint.x) / len, y: (point.y - prevPoint.y) / len))
  307.             }
  308.            
  309.             return (point, halfVec(prevPoint, point, nextPoint))
  310.         }
  311.        
  312.         if sumLength + len > position {
  313.             return (interpolate(prevPoint, point, (position - sumLength) / len), CGPoint(x: -(prevPoint.y - point.y) / len, y: (prevPoint.x - point.x) / len ))
  314.         }
  315.        
  316.         sumLength += len
  317.         prevPoint = point
  318.     }
  319.    
  320.     return nil
  321. }
  322.  
  323. func halfVec(_ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint) -> CGPoint {
  324.     var halfPoint = CGPoint(
  325.         x: (p1.x - p2.x) + (p1.x - p3.x),
  326.         y: (p1.y - p2.y) + (p1.y - p3.y)
  327.     )
  328.    
  329.     if skewProduct(sub(p1, p2), sub(p2, p3)) >= 0 {
  330.         halfPoint.x *= -1
  331.         halfPoint.y *= -1
  332.     }
  333.     let len = length(halfPoint, p2)
  334.     halfPoint.x /= len
  335.     halfPoint.y /= len
  336.     return halfPoint
  337. }
  338.  
  339. func quadraticBezierToBrokenLine(_ points: (CGPoint, CGPoint, CGPoint), quality: CGFloat = 1) -> [CGPoint] {
  340.     guard CGFloat.ulpOfOne...1 ~= quality else {
  341.         return [points.0, points.1, points.2]
  342.     }
  343.     let step = (1 / quality) / (length(points.0, points.1) + length(points.1, points.2))
  344.    
  345.     return stride(from: 0, to: 1, by: step).map{
  346.         return quadraticBezier(points, $0)
  347.         } + [points.2]
  348. }
  349.  
  350. func rootsQuadraticBezier(_ points: (CGPoint, CGPoint, CGPoint), _ x: CGFloat) -> (CGFloat, CGFloat) {
  351.     let a = [points.2.x - points.1.x * 2 + points.0.x, 1].max()!
  352.     let b = 2 * (points.1.x - points.0.x)
  353.     let c = points.0.x - x
  354.    
  355.     let d = b * b - 4 * a * c
  356.    
  357.     return (
  358.         (-b + sqrt(d)) / (2 * a),
  359.         (-b - sqrt(d)) / (2 * a)
  360.     )
  361. }
  362.  
  363. func rootLine(_ points: (CGPoint, CGPoint), _ x: CGFloat) -> CGFloat {
  364.     return (x - points.0.x) / (points.1.x - points.0.x)
  365. }
  366.  
  367. func quadraticBezier(_ points: (CGPoint, CGPoint, CGPoint), _ t: CGFloat) -> CGPoint {
  368.     return CGPoint(x: quadraticBezier((points.0.x, points.1.x, points.2.x), t), y: quadraticBezier((points.0.y, points.1.y, points.2.y), t))
  369. }
  370.  
  371. func quadraticBezier(_ points: (CGFloat, CGFloat, CGFloat), _ t: CGFloat) -> CGFloat {
  372.     return points.0 + t * (t * (points.2 - points.1 * 2 + points.0) + 2 * (points.1 - points.0));
  373. }
  374.  
  375. func skewProduct(_ p1: CGPoint, _ p2: CGPoint) -> CGFloat {
  376.     return p1.x * p2.y - p2.x * p1.y
  377. }
  378.  
  379. func sub(_ p1: CGPoint, _ p2: CGPoint) -> CGPoint {
  380.     return CGPoint(x: p1.x - p2.x, y: p1.y - p2.y)
  381. }
  382.  
  383. func add(_ p1: CGPoint, _ p2: CGPoint) -> CGPoint {
  384.     return CGPoint(x: p1.x + p2.x, y: p1.y + p2.y)
  385. }
  386.  
  387. func length(_ points: [CGPoint]) -> CGFloat? {
  388.     guard points.count > 1 else { return nil }
  389.     return points.reduce((points.first!, CGFloat(0)), {
  390.         return ($1, $0.1 + length($0.0, $1))
  391.     }).1
  392. }
  393.  
  394. func length(_ p1: CGPoint, _ p2: CGPoint) -> CGFloat {
  395.     return length(CGPoint(x: p2.x - p1.x, y: p2.y - p1.y))
  396. }
  397.  
  398. func length(_ p: CGPoint) -> CGFloat {
  399.     return (p.x * p.x + p.y * p.y).squareRoot()
  400. }
  401.  
  402. func interpolate(_ p1: CGPoint, _ p2: CGPoint, _ t: CGFloat) -> CGPoint {
  403.     return CGPoint(x: (p2.x - p1.x) * t + p1.x, y: (p2.y - p1.y) * t + p1.y)
  404. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement