- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在使用Markdown语法的Mac应用程序中的富文本编辑器上工作。我使用NSTextStorage
监视Markdown语法中的匹配项,然后将样式实时应用于NSAttributedString
,如下所示:
在这一点上,我对此事已经不知所措,但我为取得进展感到兴奋。 :) This tutorial was very helpful。
下一步,当呈现NSTextView
的字符串时,我想隐藏Markdown字符。因此,在上面的示例中,键入了最后一个星号后,我希望隐藏* *
字符,并以粗体显示sample
。
我正在使用NSLayoutManager
委托(delegate),并且可以看到匹配的字符串,但是我不清楚如何使用shouldGenerateGlyphs
方法生成修改后的字形/属性。这是我到目前为止所拥有的:
func layoutManager(_: NSLayoutManager, shouldGenerateGlyphs _: UnsafePointer<CGGlyph>, properties _: UnsafePointer<NSLayoutManager.GlyphProperty>, characterIndexes _: UnsafePointer<Int>, font _: NSFont, forGlyphRange glyphRange: NSRange) -> Int {
let pattern = "(\\*\\w+(\\s\\w+)*\\*)" // Look for stuff like *this*
do {
let regex = try NSRegularExpression(pattern: pattern)
regex.enumerateMatches(in: textView.string, range: glyphRange) {
match, _, _ in
// apply the style
if let matchRange = match?.range(at: 1) {
print(matchRange) <!-- This is the range of *sample*
// I am confused on how to provide the updated properties below...
// let newProps = NSLayoutManager.GlyphProperty.null
// layoutManager.setGlyphs(glyphs, properties: newProps, characterIndexes: charIndexes, font: aFont, forGlyphRange: glyphRange)
// return glyphRange.length
}
}
} catch {
print("\(error.localizedDescription)")
}
return 0
}
setGlyphs
中?
最佳答案
前言
我实现了此方法,以在我的应用程序中实现类似的目的。请记住,此API的文档非常少,因此我的解决方案基于反复试验,而不是深入了解此处的所有 Activity 部件。
简而言之:它应该可以工作,但使用后果自负 :)
还要注意,我在此答案中涉及了许多细节,以期使它可以被任何Swift开发人员访问,甚至包括那些没有使用Objective-C或C语言的开发人员。您可能已经知道以下详细内容。
在TextKit和字形上
要理解的一件事很重要,那就是字形是一个或多个字符的可视表示形式,如WWDC 2018 Session 221“TextKit Best Practices”中所述:
我建议收看整个谈话。在了解layoutManager(_:shouldGenerateGlyphs:properties:characterIndexes:font:forGlyphRange:)
的工作原理的特定情况下,它并不是 super 有帮助,但是它提供了大量有关TextKit的工作原理的一般信息。
了解shouldGenerateGlyphs
所以。据我了解,每次NSLayoutManager即将在渲染它们之前生成一个新的字形时,它将使您有机会通过调用layoutManager(_:shouldGenerateGlyphs:properties:characterIndexes:font:forGlyphRange:)
来修改此字形。
修改字形
根据文档,如果要修改字形,则应在此方法中通过调用setGlyphs(_:properties:characterIndexes:font:forGlyphRange:)
进行修改。
对我们来说很幸运,setGlyphs
期望使用与shouldGenerateGlyphs
中传递给我们的参数完全相同的参数。这意味着从理论上讲,您可以仅通过调用shouldGenerateGlyphs
来实现setGlyphs
,一切都会好起来的(但这并不是 super 有用)。
返回值
该文档还说shouldGenerateGlyphs
的返回值应该是“此方法中存储的实际字形范围”。这没有多大意义,因为期望的返回类型是Int
,而不是人们期望的NSRange
。从反复试验中,我认为框架希望我们在这里返回从索引0开始的传递的glyphRange
中修改的字形的数量(稍后再进行介绍)。
另外,“存储在此方法中的字形范围”是对setGlyphs
的调用,该调用将在内部存储新生成的字形(此字词措辞很差)。
一个不太有用的实现
因此,这是shouldGenerateGlyphs
的正确实现(该操作什么都不做):
func layoutManager(_ layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties: UnsafePointer<NSLayoutManager.GlyphProperty>, characterIndexes: UnsafePointer<Int>, font: UIFont, forGlyphRange glyphRange: NSRange) -> Int {
layoutManager.setGlyphs(glyphs, properties: fixedPropertiesPointer, characterIndexes: characterIndexes, font: font, forGlyphRange: glyphRange)
return glyphRange.length
}
0
:
By returning 0, it can indicate for the layout manager to do the default processing.
shouldGenerateGlyphs
的大多数参数是
UnsafePointer
。这是Swift层中泄漏的TextKit C API,并且首先使实现此方法变得很麻烦。
UnsafePointer
类型的参数都是数组(在C中,
SomeType *
或它的Swift等效
UnsafePointer<SomeType>
-是我们表示数组的方式),
和这些数组的长度均为glyphRange.length
。间接记录在
setGlyphs
方法中:
Each array has glyphRange.length items
UnsafePointer
API,我们可以使用如下循环来迭代这些数组的元素:
for i in 0 ..< glyphRange.length {
print(properties[i])
}
UnsafePointer
将执行指针算术,以在给定下标传递给任何索引的情况下访问正确地址的内存。我建议阅读
UnsafePointer
文档,这确实是很酷的东西。
setGlyphs
有用的东西
setGlyphs
?
properties
参数,但这可能是一个坏主意,因为该内存块不是我们所有的,而且我们不知道一旦退出该内存框架将如何处理该内存。方法。
setGlyphs
:
var modifiedGlyphProperties = [NSLayoutManager.GlyphProperty]()
for i in 0 ..< glyphRange.length {
// This contains the default properties for the glyph at index i set by the framework.
var glyphProperties = properties[i]
// We add the property we want to the mix. GlyphProperty is an OptionSet, we can use `.insert()` to do that.
glyphProperties.insert(.null)
// Append this glyph properties to our properties array.
modifiedGlyphProperties.append(glyphProperties)
}
// Convert our Swift array to the UnsafePointer `setGlyphs` expects.
modifiedGlyphProperties.withUnsafeBufferPointer { modifiedGlyphPropertiesBufferPointer in
guard let modifiedGlyphPropertiesPointer = modifiedGlyphPropertiesBufferPointer.baseAddress else {
fatalError("Could not get base address of modifiedGlyphProperties")
}
// Call setGlyphs with the modified array.
layoutManager.setGlyphs(glyphs, properties: modifiedGlyphPropertiesPointer, characterIndexes: characterIndexes, font: font, forGlyphRange: glyphRange)
}
return glyphRange.length
properties
数组中读取原始字形属性,并将自定义属性添加到该基本值中(使用
.insert()
方法)。否则,您将覆盖字形的默认属性,并且会发生奇怪的事情(例如,我已经看到
\n
字符不再插入可视换行符了)。
*
时)将更加有用。
characterIndexes
并不是通常意义上的“字符”索引,而是UTF-16代码单元的索引†。
textView.text = "Officiellement nous (👨👩👧👧) vivons dans un cha\u{0302}teau 🏰 海"
/// Returns the extended grapheme cluster at `index` in an UTF16View, merging a UTF-16 surrogate pair if needed.
private func characterFromUTF16CodeUnits(_ utf16CodeUnits: String.UTF16View, at index: Int) -> Character {
let codeUnitIndex = utf16CodeUnits.index(utf16CodeUnits.startIndex, offsetBy: index)
let codeUnit = utf16CodeUnits[codeUnitIndex]
if UTF16.isLeadSurrogate(codeUnit) {
let nextCodeUnit = utf16CodeUnits[utf16CodeUnits.index(after: codeUnitIndex)]
let codeUnits = [codeUnit, nextCodeUnit]
let str = String(utf16CodeUnits: codeUnits, count: 2)
return Character(str)
} else if UTF16.isTrailSurrogate(codeUnit) {
let previousCodeUnit = utf16CodeUnits[utf16CodeUnits.index(before: codeUnitIndex)]
let codeUnits = [previousCodeUnit, codeUnit]
let str = String(utf16CodeUnits: codeUnits, count: 2)
return Character(str)
} else {
let unicodeScalar = UnicodeScalar(codeUnit)!
return Character(unicodeScalar)
}
}
// First, make sure we'll be able to access the NSTextStorage.
guard let textStorage = layoutManager.textStorage else {
fatalError("No textStorage was associated to this layoutManager")
}
// Access the characters.
let utf16CodeUnits = textStorage.string.utf16
var modifiedGlyphProperties = [NSLayoutManager.GlyphProperty]()
for i in 0 ..< glyphRange.length {
var glyphProperties = properties[i]
let character = characterFromUTF16CodeUnits(utf16CodeUnits, at: characterIndex)
// Do something with `character`, e.g.:
if character == "*" {
glyphProperties.insert(.null)
}
modifiedGlyphProperties.append(glyphProperties)
}
// Convert our Swift array to the UnsafePointer `setGlyphs` expects.
modifiedGlyphProperties.withUnsafeBufferPointer { modifiedGlyphPropertiesBufferPointer in
guard let modifiedGlyphPropertiesPointer = modifiedGlyphPropertiesBufferPointer.baseAddress else {
fatalError("Could not get base address of modifiedGlyphProperties")
}
// Call setGlyphs with the modified array.
layoutManager.setGlyphs(glyphs, properties: modifiedGlyphPropertiesPointer, characterIndexes: characterIndexes, font: font, forGlyphRange: glyphRange)
}
return glyphRange.length
// First, make sure we'll be able to access the NSTextStorage.
guard let textStorage = layoutManager.textStorage else {
fatalError("No textStorage was associated to this layoutManager")
}
// Get the first and last characters indexes for this glyph range,
// and from that create the characters indexes range.
let firstCharIndex = characterIndexes[0]
let lastCharIndex = characterIndexes[glyphRange.length - 1]
let charactersRange = NSRange(location: firstCharIndex, length: lastCharIndex - firstCharIndex + 1)
var hiddenRanges = [NSRange]()
textStorage.enumerateAttributes(in: charactersRange, options: []) { attributes, range, _ in
for attribute in attributes where attribute.key == .link {
hiddenRanges.append(range)
}
}
var modifiedGlyphProperties = [NSLayoutManager.GlyphProperty]()
for i in 0 ..< glyphRange.length {
let characterIndex = characterIndexes[i]
var glyphProperties = properties[i]
let matchingHiddenRanges = hiddenRanges.filter { NSLocationInRange(characterIndex, $0) }
if !matchingHiddenRanges.isEmpty {
glyphProperties.insert(.null)
}
modifiedGlyphProperties.append(glyphProperties)
}
// Convert our Swift array to the UnsafePointer `setGlyphs` expects.
modifiedGlyphProperties.withUnsafeBufferPointer { modifiedGlyphPropertiesBufferPointer in
guard let modifiedGlyphPropertiesPointer = modifiedGlyphPropertiesBufferPointer.baseAddress else {
fatalError("Could not get base address of modifiedGlyphProperties")
}
// Call setGlyphs with the modified array.
layoutManager.setGlyphs(glyphs, properties: modifiedGlyphPropertiesPointer, characterIndexes: characterIndexes, font: font, forGlyphRange: glyphRange)
}
return glyphRange.length
Character
的(或“扩展字素簇”)相同。同样,TextKit框架的“字符”是UTF-16代码单元(在Swift中由
Unicode.UTF16.CodeUnit
表示)。
.withUnsafeBufferPointer
将
modifiedGlyphProperties
数组转换为UnsafePointer。它消除了拥有数组实例变量以使其在内存中保持 Activity 状态的需要。
关于nstextview - 在Swift中使用NSLayoutManager隐藏Markdown字符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57467951/
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 要求提供代码的问题必须表现出对所解决问题的最低限度理解。包括尝试过的解决方案、为什么它们不起作用,以及预
为什么在 C# 中添加两个 char 结果是 int 类型? 例如,当我这样做时: var pr = 'R' + 'G' + 'B' + 'Y' + 'P'; pr 变量变为 int 类型。我希望它是
下面的代码可以编译,但 char 类型的行为与 int 类型的行为不同。 特别是 cout ::ikIsX >() ::ikIsX >() ::ikIsX >() using names
我正在寻找一个正则表达式,它可以匹配长度为 1 个或多个字符但不匹配 500 的内容。这将在 Rails 路由文件中使用,特别是用于处理异常。 路线.rb match '/500', to: 'err
对于 C 编程作业,我正在尝试编写几个头文件来检查所谓的“X 编程语言”的语法。我最近才开始,正在编写第一个头文件。这是我编写的代码: #ifndef _DeclarationsChecker_h_
为什么扩展的 ascii 字符(â、é 等)被替换为 字符? 我附上了一张图片...但我正在使用 PHP 从 MySQL 中提取数据,其中一些位置有扩展字符...我使用的是 Arial 字体。 您可以
我有一个与 R 中的断线相关的简单问题。 我正在尝试粘贴,但在获取(字符/数字)之间的断线时遇到问题。请注意,这些值包含在向量中(V1=81,V2=55,V3=25)我已经尝试过这段代码: cat(p
如何将 ANSI 字符 (char) 转换为 Unicode 字符 (wchar_t),反之亦然? 是否有用于此目的的任何跨平台源代码? 最佳答案 是的,在 中你有mbstowcs()和 wcsto
函数 fromCharCode 不适用于国际 ANSI 字符。例如,对于 ID 为 192 到 223 的俄语 ANSI (cp-1251) 字符,它返回特殊字符。如何解决这个问题? 我认为,需要将A
如果不喜欢,我想隐藏 id,但不起作用 SELECT * FROM character, character_actor WHERE character.id NOT LIKE character_a
现在这个程序成功地反转了键盘输入的单词。但是我想在我反转它之前“保存”指针中的单词,所以我可以比较两者,反转的和“原始的”,并检查它们是否是回文。我还没有太多经验,可能会出现比我知道的更多的错误,但我
Memcpy 和 memcmp 函数可以接受指针变量吗? char *p; char* q; memcpy(p,q,10); //will this work? memcmp(p,q,10); //w
恐怕我对一个相当过饱和的主题的细节有疑问,我搜索了很多,但找不到一个明确的答案来解决这个特定的明显-imho-重要的问题: 使用UTF-8将byte[]转换为String时,每个字节(8bit)都变成
我有一个奇怪的问题。我需要从 stat 命令打印输出字符串。 我已经编写了获取一些信息的代码。 import glob import os for file in glob.glob('system1
我正在使用 Java 并具有其值如下所示的字符串, String data = "vale-cx"; data = data.replaceAll("\\-", "\\-\\"); 我正在替换其中的“
String urlParameters = "login=test&password=te&ff"; 我有一个String urlParams,& - 是密码的一部分,如何使其转义,从而不被识别为分
大家好,我只想从此字符串中提取第一个字母: String str = "使 徒 行 傳 16:31 ERV-ZH"; 我只想获取这些字符: 使 徒 行 傳 并且不包括 ERV-ZH 仅数
这个问题已经有答案了: Crash or "segmentation fault" when data is copied/scanned/read to an uninitialized point
所以, 我有一个字符**;它本质上是一个句子,带有指向该句子中每个单词的指针;即 'h''i''\0''w''o''r''l''d''\0''y''a''y''!''\0' 在这种情况下,我希望使用可
这个问题在这里已经有了答案: Using quotation marks inside quotation marks (12 个答案) 关闭 7 年前。 如何打印 " 字符? 我知道打印 % 符号
我是一名优秀的程序员,十分优秀!