Last active
August 18, 2017 18:05
-
-
Save fastred/f51fe8cd58b1f2fdd07c to your computer and use it in GitHub Desktop.
JumpingDots http://holko.pl/2016/02/16/enhancing-uiviews/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UILabel+JumpingDots.swift | |
// JumpingDots | |
// | |
// Copyright (c) 2016 Arkadiusz Holko. All rights reserved. | |
// | |
import UIKit | |
import ObjectiveC | |
enum JumpingDotsError: ErrorType { | |
case MissingAttributedString | |
case DoesNotEndWithThreeDots | |
} | |
private class LabelTextStackReplica { | |
let attributedString: NSAttributedString | |
let textStorage: NSTextStorage | |
let layoutManager: NSLayoutManager | |
let textContainer: NSTextContainer | |
init(attributedString: NSAttributedString, size: CGSize) { | |
self.attributedString = attributedString | |
layoutManager = NSLayoutManager() | |
textStorage = NSTextStorage(attributedString: attributedString) | |
textStorage.addLayoutManager(layoutManager) | |
textContainer = NSTextContainer(size: size) | |
textContainer.lineFragmentPadding = 0 | |
layoutManager.addTextContainer(textContainer) | |
} | |
} | |
extension UILabel { | |
private struct AssociatedKeys { | |
static var JumpingDotsRunning = "ahk_jumpingDotsRunning" | |
static var JumpingDotsPausing = "ahk_jumpingDotsPausing" | |
} | |
private(set) var jumpingDotsRunning: Bool { | |
get { | |
return objc_getAssociatedObject(self, &AssociatedKeys.JumpingDotsRunning) as? Bool ?? false | |
} | |
set { | |
objc_setAssociatedObject(self, &AssociatedKeys.JumpingDotsRunning, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) | |
} | |
} | |
private var jumpingDotsPausing: Bool { | |
get { | |
return objc_getAssociatedObject(self, &AssociatedKeys.JumpingDotsPausing) as? Bool ?? false | |
} | |
set { | |
objc_setAssociatedObject(self, &AssociatedKeys.JumpingDotsPausing, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) | |
} | |
} | |
func startJumpingDots() throws { | |
let requiredEnding = "..." | |
guard let attributedText = attributedText else { | |
throw JumpingDotsError.MissingAttributedString | |
} | |
let text = attributedText.string | |
guard text.hasSuffix(requiredEnding) else { | |
throw JumpingDotsError.DoesNotEndWithThreeDots | |
} | |
let endingCharacterCount = requiredEnding.characters.count | |
let endingRange = NSRange(location: text.characters.count - endingCharacterCount, length: endingCharacterCount) | |
jumpingDotsRunning = true | |
var addedSubviews: [UIView] = [] | |
let originalTextColor = attributedText.attribute(NSForegroundColorAttributeName, atIndex: endingRange.location, effectiveRange: nil) as? UIColor | |
for i in 0..<endingCharacterCount { | |
let characterPosition = text.characters.count - endingCharacterCount + i | |
let boundingRect = boundingRectForCharacterAtPosition(characterPosition, inAttributedString: attributedText).integral | |
let imageView = UIImageView(frame: boundingRect) | |
imageView.image = snapshotRect(boundingRect) | |
addSubview(imageView) | |
addedSubviews.append(imageView) | |
if i == endingCharacterCount - 1 { | |
changeTextColorAtRange(endingRange, to: UIColor.clearColor()) | |
} | |
let delay = Double(i) * 0.15 | |
UIView.animateKeyframesWithDuration(1.15, delay: delay, options: [], animations: { | |
let relativeDuration = 0.282 | |
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: relativeDuration, animations: { | |
imageView.frame.origin.y = -self.bounds.height / 4 | |
}) | |
UIView.addKeyframeWithRelativeStartTime(relativeDuration, relativeDuration: relativeDuration, animations: { | |
imageView.frame.origin.y = 0 | |
}) | |
}, completion: { [weak self] finished in | |
if i == endingCharacterCount - 1 { | |
self?.triggerNextIterationIfNeededUsingEndingRange(endingRange, addedSubviews: addedSubviews, originalTextColor: originalTextColor) | |
} | |
} | |
) | |
} | |
} | |
private func triggerNextIterationIfNeededUsingEndingRange(endingRange: NSRange, addedSubviews: [UIView], originalTextColor: UIColor?) { | |
if let color = originalTextColor { | |
changeTextColorAtRange(endingRange, to: color) | |
} | |
for subview in addedSubviews { | |
subview.removeFromSuperview() | |
} | |
let resetState = { | |
self.jumpingDotsPausing = false | |
self.jumpingDotsRunning = false | |
} | |
if self.jumpingDotsPausing { | |
resetState() | |
} | |
if self.jumpingDotsRunning { | |
do { | |
try startJumpingDots() | |
} catch { | |
resetState() | |
} | |
} | |
} | |
func stopJumpingDots() { | |
jumpingDotsPausing = true | |
} | |
private func changeTextColorAtRange(range: NSRange, to color: UIColor) { | |
if let mutableAttributedString = attributedText?.mutableCopy() { | |
mutableAttributedString.addAttribute(NSForegroundColorAttributeName, value: color, range: range) | |
self.attributedText = mutableAttributedString.copy() as? NSAttributedString | |
} | |
} | |
private func boundingRectForCharacterAtPosition(characterPosition: Int, inAttributedString attributedString: NSAttributedString) -> CGRect { | |
let textStack = LabelTextStackReplica(attributedString: attributedString, size: frame.size) | |
let range = NSRange(location: characterPosition, length: 1) | |
var glyphRange = NSRange() | |
textStack.layoutManager.characterRangeForGlyphRange(range, actualGlyphRange: &glyphRange) | |
return textStack.layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textStack.textContainer) | |
} | |
} | |
extension UIView { | |
public func snapshotRect(rect: CGRect) -> UIImage { | |
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale) | |
let offsetRect = CGRectOffset(bounds, -rect.origin.x, -rect.origin.y) | |
let success = drawViewHierarchyInRect(offsetRect, afterScreenUpdates: false) | |
assert(success) | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a code from my article: http://holko.pl/2016/02/16/enhancing-uiviews/