ALL BUSINESS
COMIDA
DIRECTORIES
EDUCATIONAL
ENTERTAINMENT
FASHION TIPS
FINER THINGS
FREE CREATOR TOOLS
HEALTH
MARKETPLACE
MEMBER's ONLY
MONEY MATTER$
MOTIVATIONAL
NEWS & WEATHER
TECHNOLOGIA
TELEVISION NETWORKS
USA VOTES 2024
VIDEOS
INVESTOR RELATIONS
IN DEVELOPMENT
Posted by - Latinos MediaSyndication -
on - September 24, 2023 -
Filed in - Technology -
-
308 Views - 0 Comments - 0 Likes - 0 Reviews
I have a requirement to animate/transform the glyph's of a UITextView (or subclass). In order to achieve this I am attempting to use TextKit (i.e. NSTextContainer / NSLayoutManager / NSTextStorage) to provide me with the frame of a given glyph and then manually position each glyph in the view as a CATextLayer (needs to be CoreAnimation for smooth/performant animations).
This loosely works however FOR CERTAIN FONTS the frame supplied by TextKit's boundingRect(forGlyphRange:, in:) function does not exactly match the frame of the glyph were it being rendered in a vanilla/default UITextView. The font "Courier" for example has a significant y offset.
I have produced a contrived and simplified playground to demonstrate the problem. Code below.
Can someone help me work out how I can position the red TextKit positioned CALayer glyph's so they sit perfectly on-top of their black UITextView glyph equivalents?
import UIKit import PlaygroundSupport fileprivate class CustomTextView: UITextView { private var glyphTextLayers: [CALayer] = [] override func layoutSubviews() { super.layoutSubviews() calculateTextLayers() } private func removeGlyphTextLayers() { glyphTextLayers.forEach { $0.removeFromSuperlayer() } glyphTextLayers = [] } private func calculateTextLayers() { removeGlyphTextLayers() var index = 0 while index <= textStorage.string.count { let glyphRange = NSMakeRange(index, 1) let characterRange = layoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) guard characterRange.length > 0 else { break } let glyphRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) let attributedStringForGlyph = textStorage.attributedSubstring(from: characterRange) .replacingForegroundColor(with: .red) // For demo purposes only let textLayer = CATextLayer() textLayer.contentsScale = UIScreen.main.scale textLayer.alignmentMode = .center textLayer.frame = glyphRect textLayer.string = attributedStringForGlyph // TODO transform the glyphs // textLayer.transform = ... layer.addSublayer(textLayer) glyphTextLayers.append(textLayer) index += characterRange.length } } } extension NSAttributedString { func replacingForegroundColor(with: UIColor) -> NSAttributedString { let mutableAttributedString = NSMutableAttributedString(attributedString: self) let range = NSMakeRange(0, mutableAttributedString.length) mutableAttributedString.removeAttribute(NSAttributedString.Key.foregroundColor, range: range) mutableAttributedString.addAttributes([ NSAttributedString.Key.foregroundColor: UIColor.red as Any ], range: range) return mutableAttributedString } } class PlaygroundViewController : UIViewController { override func loadView() { let view = UIView() view.backgroundColor = .white self.view = view addCustomText(withFontNamed: "Helvetica", atY: 50) addCustomText(withFontNamed: "Courier", atY: 150) addCustomText(withFontNamed: "Futura", atY: 250) addCustomText(withFontNamed: "Optima", atY: 350) } private func addCustomText(withFontNamed fontName: String, atY y: CGFloat) { let fontSize = 48.0 let font = UIFont(name: fontName, size: fontSize)! let text = "\(fontName) \(fontSize)" let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center let attributedText = NSAttributedString(string: text, attributes: [ NSAttributedString.Key.font: font as Any, NSAttributedString.Key.foregroundColor: UIColor.black as Any, NSAttributedString.Key.paragraphStyle: paragraphStyle as Any ]) let customTextView = CustomTextView() customTextView.backgroundColor = .lightGray customTextView.attributedText = attributedText customTextView.textContainerInset = .zero customTextView.textContainer.lineFragmentPadding = 0 customTextView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(customTextView) NSLayoutConstraint.activate([ customTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 6), customTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -6), customTextView.centerYAnchor.constraint(equalTo: view.topAnchor, constant: y), customTextView.heightAnchor.constraint(equalToConstant: font.lineHeight) ]) } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = PlaygroundViewController()