Swift中AttributedString扩展NSRange下标赋值提示下标只读
问题描述
以下代码可以正常运行:
let nsRange = NSRange(location: 5, length: 10) var attributedString = AttributedString("This is some string that I want to attribute.") // 给子串设置属性前,需要先完成范围类型转换 let start = attributedString.index(attributedString.startIndex, offsetByUnicodeScalars: nsRange.location) let end = attributedString.index(start, offsetByUnicodeScalars: nsRange.length) // 得到Range<AttributedString.Index>类型的合法范围 let range = start..<end attributedString[range].foregroundColor = .red
执行无异常。
当尝试编写如下扩展,希望通过自定义下标省略重复的范围转换逻辑时:
extension AttributedString { subscript(_ range: NSRange) -> AttributedSubstring { get { let start = index(startIndex, offsetByUnicodeScalars: range.location) let end = index(start, offsetByUnicodeScalars: range.length) return self[start..<end] } } }
使用如下代码调用:
attributedString[nsRange].foregroundColor = .red
编译失败,抛出错误:Cannot assign through subscript: subscript is get-only。
核心疑问:attributedString[nsRange]返回值为AttributedSubstring类型,该类型本身支持通过.foregroundColor = .red语法设置属性;使用原生Range<AttributedString.Index>下标的语法str[start..<end].foregroundColor = .red可正常工作,为什么自定义NSRange下标无法实现相同效果?
根本原因
问题本质是Swift普通只读下标与集合类型突变下标的行为差异:
AttributedString原生接收Range<AttributedString.Index>参数的下标,是MutableCollection协议提供的突变(mutating)下标,调用时会拿到原字符串对应存储段的可写引用,对返回的AttributedSubstring做属性修改,会直接通过引用写回原AttributedString。- 自定义的只读下标本质是普通的实例方法,非mutating的getter只会返回一个临时生成的
AttributedSubstring值拷贝,这个临时值是生命周期仅存在于当前调用语句的右值,本身不可持久修改。哪怕AttributedSubstring自身支持属性修改,修改的也是会被立即释放的临时对象,完全不会作用到原字符串上,编译器会直接拦截这种无意义的写操作。
报错信息提示"subscript is get-only",就是明确说明当前下标只实现了读取逻辑,没有实现修改后写回原实例的逻辑,无法支持通过下标修改原实例内容。
修复方法
给自定义下标补充set实现,在setter中把修改后的子串写回原字符串的对应范围即可:
extension AttributedString { subscript(_ range: NSRange) -> AttributedSubstring { get { let start = index(startIndex, offsetByUnicodeScalars: range.location) let end = index(start, offsetByUnicodeScalars: range.length) return self[start..<end] } set { let start = index(startIndex, offsetByUnicodeScalars: range.location) let end = index(start, offsetByUnicodeScalars: range.length) self[start..<end] = newValue } } }
补充setter后,attributedString[nsRange].foregroundColor = .red的调用流程就和原生下标一致:先调用getter拿到对应范围的子串,修改子串属性后,再调用setter将修改后的子串写回原字符串对应位置,可正常编译运行。
内容的提问来源于stack exchange,提问作者Peter R
相关产品推荐
相关产品推荐

