我一直在尝试创建一个图像,在UITextView中用线条之间的空格突出显示文本,如下所示:
但是,当我尝试在Swift中执行此操作时,我发现使用NSAttributedString.Key.backgroundColor
突出显示文本和NSMutableParagraphStyle().lineSpacing
以增加UITextView之间的行间距只是扩展突出显示,如下所示:
有什么方法可以控制.backgroundColor
的高度,使它不完全覆盖线之间的空白?
或者我是否必须创建每个矩形并将其叠加在文本顶部以获得我想要的结果?
弄清楚了。
看起来你必须使用CoreText将它拉下来,而不仅仅是TextKit。
我仍然需要弄清楚如何扩展高光,使它们覆盖字母的底部而不是顶部。而且我必须弄清楚如何移动高光以使它们“落后”文本并且不会使字体颜色变浅,但这将使你获得90%的方式。
import UIKit
import CoreText
import PlaygroundSupport
// Sources
// https://stackoverflow.com/questions/48482657/catextlayer-render-attributedstring-with-truncation-and-paragraph-style
// https://stackoverflow.com/a/52320276/1291940
// https://stackoverflow.com/a/55283002/1291940
// Create a view to display what's going on.
var demoView = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
demoView.backgroundColor = UIColor.white // Haven't figured out if you can create a boundary around a UIView
PlaygroundPage.current.liveView = demoView // Apparently it doesn't matter where we place this code
// Calculates height of frame given a string of a certain length
extension String {
func sizeOfString(constrainedToWidth width: Double, font: UIFont) -> CGSize {
let attributes = [NSAttributedString.Key.font : font]
let attString = NSAttributedString(string: self, attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attString)
return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0, length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
}
}
// Unwraps optional so our program doesn't crash in case the user doesn't have the specified font.
func unwrappedFont(fontSize: CGFloat) -> UIFont {
if let textFont = UIFont(name: "Futura", size: fontSize) {
return textFont
}
else {
return UIFont.systemFont(ofSize: fontSize)
}
}
let string = "When you hear or read someone weaving their ideas into a beautiful mosaic of words, try to remember, they are almost certainly wrong."
var dynamicHeight = string.sizeOfString(constrainedToWidth: 500, font: unwrappedFont(fontSize: 40)).height
// dynamicHeight = 500
let boxSize = CGSize(width: 500, height: dynamicHeight)
// let boxSize = CGSize(width: 500, height: 500)
var imageBounds : [CGRect] = [] // rectangle highlight
let renderer = UIGraphicsImageRenderer(size: boxSize)
let img = renderer.image { ctx in
// Flipping the coordinate system
ctx.cgContext.textMatrix = .identity
ctx.cgContext.translateBy(x: 0, y: boxSize.height) // Alternatively y can just be 500.
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
// Setting up constraints for quote frame
let range = NSRange( location: 0, length: string.count)
guard let context = UIGraphicsGetCurrentContext() else { return }
let path = CGMutablePath()
let bounds = CGRect(x: 0, y: 0, width: boxSize.width, height: boxSize.height)
path.addRect(bounds)
let attrString = NSMutableAttributedString(string: string)
attrString.addAttribute(NSAttributedString.Key.font, value: UIFont(name: "Futura", size: 40)!, range: range )
let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
CTFrameDraw(frame, context)
// Setting up variables for highlight creation
let lines = CTFrameGetLines(frame) as NSArray
var lineOriginsArray : [CGPoint] = []
var contextHighlightRect : CGRect = CGRect()
var counter = 0
// Draws a rectangle over each line.
for line in lines {
let ctLine = line as! CTLine
let numOfLines: size_t = CFArrayGetCount(lines)
lineOriginsArray = [CGPoint](repeating: CGPoint.zero, count: numOfLines)
CTFrameGetLineOrigins(frame, CFRangeMake(0,0), &lineOriginsArray)
imageBounds.append(CTLineGetImageBounds(ctLine, context))
// Draw highlights
contextHighlightRect = CGRect(x: lineOriginsArray[counter].x, y: lineOriginsArray[counter].y, width: imageBounds[counter].size.width, height: imageBounds[counter].size.height)
ctx.cgContext.setStrokeColor(red: 0, green: 0, blue: 0, alpha: 0.5)
ctx.cgContext.stroke(contextHighlightRect)
ctx.cgContext.setFillColor(red: 1, green: 1, blue: 0, alpha: 0.3)
ctx.cgContext.fill(contextHighlightRect)
counter = counter + 1
}
}
// Image layer
let imageLayer = CALayer()
imageLayer.contents = img.cgImage
imageLayer.position = CGPoint(x: 0, y: 0)
imageLayer.frame = CGRect(x: 0, y: 0, width: 500, height: dynamicHeight)
// Adding layers to view
demoView.layer.addSublayer(imageLayer)
那段代码正在解决我的问题。
- (void)viewDidLoad {
[super viewDidLoad];
UIMenuItem *highlightMenuItem = [[UIMenuItem alloc] initWithTitle:@"Highlight" action:@selector(highlight)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:highlightMenuItem]];
float sysVer = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVer >= 8.0) {
self.textView.layoutManager.allowsNonContiguousLayout = NO;
}
}
- (void)highlight {
NSRange selectedTextRange = self.textView.selectedRange;
[attributedString addAttribute:NSBackgroundColorAttributeName
value:[UIColor redColor]
range:selectedTextRange];
float sysVer = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVer < 8.0) {
// iOS 7 fix
self.textView.scrollEnabled = NO;
self.textView.attributedText = attributedString;
self.textView.scrollEnabled = YES;
} else {
self.textView.attributedText = attributedString;
}
}