我按照本教程创建了它的 Mac 版本。它工作得很好,除了有一个我无法弄清楚的错误。如果您尝试编辑字符串中间的任何内容,光标会跳到字符串的末尾。像这样:
这是一个示例项目,或者您可以创建一个新的 macOS 项目并将其放在默认的ViewController.swift中:
import Cocoa
class ViewController: NSViewController, NSTextViewDelegate {
var textView: NSTextView!
var textStorage: FancyTextStorage!
override func viewDidLoad() {
super.viewDidLoad()
createTextView()
}
func createTextView() {
// 1
let attrs = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: 13)]
let attrString = NSAttributedString(string: "This is a *cool* sample.", attributes: attrs)
textStorage = FancyTextStorage()
textStorage.append(attrString)
let newTextViewRect = view.bounds
// 2
let layoutManager = NSLayoutManager()
// 3
let containerSize = CGSize(width: newTextViewRect.width, height: .greatestFiniteMagnitude)
let container = NSTextContainer(size: containerSize)
container.widthTracksTextView = true
layoutManager.addTextContainer(container)
textStorage.addLayoutManager(layoutManager)
// 4
textView = NSTextView(frame: newTextViewRect, textContainer: container)
textView.delegate = self
view.addSubview(textView)
// 5
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.topAnchor.constraint(equalTo: view.topAnchor),
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
然后创建一个带有以下子类的FancyTextStorage类NSTextStorage
:
class FancyTextStorage: NSTextStorage{
let backingStore = NSMutableAttributedString()
private var replacements: [String: [NSAttributedString.Key: Any]] = [:]
override var string: String {
return backingStore.string
}
override init() {
super.init()
createHighlightPatterns()
}
func createHighlightPatterns() {
let boldAttributes = [NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 13)]
replacements = ["(\\*\\w+(\\s\\w+)*\\*)": boldAttributes]
}
func applyStylesToRange(searchRange: NSRange) {
let normalAttrs = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: 13, weight: .regular), NSAttributedString.Key.foregroundColor: NSColor.init(calibratedRed: 0.5, green: 0.5, blue: 0.5, alpha: 1.0)]
addAttributes(normalAttrs, range: searchRange)
// iterate over each replacement
for (pattern, attributes) in replacements {
do {
let regex = try NSRegularExpression(pattern: pattern)
regex.enumerateMatches(in: backingStore.string, range: searchRange) {
match, flags, stop in
// apply the style
if let matchRange = match?.range(at: 1) {
print("Matched pattern: \(pattern)")
addAttributes(attributes, range: matchRange)
// reset the style to the original
let maxRange = matchRange.location + matchRange.length
if maxRange + 1 < length {
addAttributes(normalAttrs, range: NSMakeRange(maxRange, 1))
}
}
}
}
catch {
print("An error occurred attempting to locate pattern: " +
"\(error.localizedDescription)")
}
}
}
func performReplacementsForRange(changedRange: NSRange) {
var extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRange(for: NSMakeRange(changedRange.location, 0)))
extendedRange = NSUnionRange(changedRange, NSString(string: backingStore.string).lineRange(for: NSMakeRange(NSMaxRange(changedRange), 0)))
beginEditing()
applyStylesToRange(searchRange: extendedRange)
endEditing()
}
override func processEditing() {
performReplacementsForRange(changedRange: editedRange)
super.processEditing()
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] {
return backingStore.attributes(at: location, effectiveRange: range)
}
override func replaceCharacters(in range: NSRange, with str: String) {
print("replaceCharactersInRange:\(range) withString:\(str)")
backingStore.replaceCharacters(in: range, with:str)
edited(.editedCharacters, range: range,
changeInLength: (str as NSString).length - range.length)
}
override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
//print("setAttributes:\(String(describing: attrs)) range:\(range)")
backingStore.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) {
fatalError("init(pasteboardPropertyList:ofType:) has not been implemented")
}
}
似乎当字符串被重写时,它不会保留光标位置,但是 iOS 上的相同代码(来自上述教程)没有这个问题。
有任何想法吗?