ios-输入时替换/删除UITextView中的字符[•;]
我有一个带有自定义NSTextStorage的UITextView,如果前一行以列表项目符号开头,我会在每次输入后添加一个列表项目符号 当用户输入并且光标所在的行的开头只有一个列表项目符号时,我将删除列表项目符号并停留在该行 第一个函数按预期工作。但我很难想出如何删除列表中的项目符号ios-输入时替换/删除UITextView中的字符[•;],ios,swift,uitextview,nsrange,nstextstorage,Ios,Swift,Uitextview,Nsrange,Nstextstorage,我有一个带有自定义NSTextStorage的UITextView,如果前一行以列表项目符号开头,我会在每次输入后添加一个列表项目符号 当用户输入并且光标所在的行的开头只有一个列表项目符号时,我将删除列表项目符号并停留在该行 第一个函数按预期工作。但我很难想出如何删除列表中的项目符号 上面的代码不起作用。我可能没有使用正确的范围 这是我所有的代码 class TextView: UITextView { internal var storage: TextStorage!
上面的代码不起作用。我可能没有使用正确的范围 这是我所有的代码
class TextView: UITextView {
internal var storage: TextStorage!
var defaultAttributes: [NSAttributedString.Key: AnyObject] = [:]
override init(frame: CGRect, textContainer: NSTextContainer?) {
let container = (textContainer == nil) ? NSTextContainer() : textContainer!
container.widthTracksTextView = true
container.heightTracksTextView = true
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(container)
self.storage = TextStorage()
self.storage.addLayoutManager(layoutManager)
super.init(frame: .zero, textContainer: container)
self.textContainerInset = .init(top: 16, left: 16, bottom: 16, right: 16)
self.isScrollEnabled = true
self.storage.textView = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
文本存储
总之,我只需要在用户输入并停留在该行时删除光标所在的单词 如果你能帮我解决这个问题,我将非常感激 更新 如何测试此代码
找到下面修改过的函数。使用Xcode 11.4/iOS 13.4进行测试
override func replaceCharacters(范围:NSRange,带str:String){
变量listPrefix:字符串?=nil
if(TextUtils.isReturn(str:str)){
让currentLine=TextUtils.startOffset(self.string,位置:range.location)。0
设separateds=currentLine.components(separatedBy:“”)
如果分隔.first!。包含(“•”&¤tLine.trimmingCharacters(在:。空格中)。计数==1{
listPrefix=“”
}
否则{
如果separateds.count>=2{
如果分隔。第一个!包含(“•”){
listPrefix=“•”
}
}
}
}
开始编辑
backingStore.replaceCharacters(在:范围内,带:str)
已编辑(.editedCharacters,范围:range,changeInLength:(str作为NSString.length-range.length)
endEditing()
guard let prefix=listPrefix-else{
返回
}
如果前缀为.isEmpty{
让text=string.split(分隔符:“\n”)
let next=NSMakeRange(textView.selectedRange.location-(text.last!.count)-1,text.last!.count+1)
ReplaceCharactersRange(下一步,带字符串:“”,selectedRangeLocationMove:0)
}否则{
让newRange=NSMakeRange(textView.selectedRange.location+str.count,0)
ReplaceCharactersRange(新范围,带字符串:前缀,selectedRangeLocationMove:prefix.count)
}
}
重写func setAttributes(attrs:[NSAttributedString.Key:Any]?,范围:NSRange){
guard range.upperBound此代码不按声明的方式工作。使用Xcode 11.4/iOS 13.4测试,TextView是以编程方式创建的。列表项目符号从未插入任何输入的文本,因此用例和期望值不清楚。是否更新代码?
class TextView: UITextView {
internal var storage: TextStorage!
var defaultAttributes: [NSAttributedString.Key: AnyObject] = [:]
override init(frame: CGRect, textContainer: NSTextContainer?) {
let container = (textContainer == nil) ? NSTextContainer() : textContainer!
container.widthTracksTextView = true
container.heightTracksTextView = true
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(container)
self.storage = TextStorage()
self.storage.addLayoutManager(layoutManager)
super.init(frame: .zero, textContainer: container)
self.textContainerInset = .init(top: 16, left: 16, bottom: 16, right: 16)
self.isScrollEnabled = true
self.storage.textView = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class TextStorage: NSTextStorage {
var backingStore: NSMutableAttributedString = NSMutableAttributedString()
var textView: UITextView!
override var string: String {
return self.backingStore.string
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
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) {
var listPrefix: String? = nil
if (TextUtils.isReturn(str: str)) {
let currentLine = TextUtils.startOffset(self.string, location: range.location).0
let separateds = currentLine.components(separatedBy: " ")
if separateds.first!.contains("•") && currentLine.trimmingCharacters(in: .whitespaces).count == 1 {
listPrefix = ""
}
else {
if separateds.count >= 2 {
if separateds.first!.contains("•") {
listPrefix = "• "
}
}
}
}
beginEditing()
backingStore.replaceCharacters(in: range, with:str)
edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
endEditing()
guard let prefix = listPrefix else {
return
}
if prefix.isEmpty {
let text = string.split(separator: "\n")
let next = NSMakeRange(textView.selectedRange.location - (text.last!.count) ,0)
replaceCharactersInRange(next, withString: " ", selectedRangeLocationMove: text.last!.count)
} else {
let newRange = NSMakeRange(textView.selectedRange.location + str.count, 0)
replaceCharactersInRange(newRange, withString: prefix, selectedRangeLocationMove: prefix.count)
}
}
override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
beginEditing()
backingStore.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
endEditing()
}
func replaceCharactersInRange(_ replaceRange: NSRange, withString str: String, selectedRangeLocationMove: Int) {
if textView.undoManager!.isUndoing {
textView.selectedRange = NSMakeRange(textView.selectedRange.location - selectedRangeLocationMove, 0)
replaceCharactersInRange(NSMakeRange(replaceRange.location, str.count), withString: "")
} else {
replaceCharactersInRange(replaceRange, withString: str)
textView.selectedRange = NSMakeRange(textView.selectedRange.location + selectedRangeLocationMove, 0)
}
}
}
extension NSMutableAttributedString {
func replaceCharactersInRange(_ range: NSRange, withString str: String) {
if isSafeRange(range) {
replaceCharacters(in: range, with: str)
}
}
func isSafeRange(_ range: NSRange) -> Bool {
if range.location < 0 {
return false
}
let maxLength = range.location + range.length
return maxLength <= string.count
}
}
class TextUtils {
class func isReturn(str: String) -> Bool {
return str == "\n"
}
class func isBackspace(str: String) -> Bool {
return str == ""
}
class func startOffset(_ string: String, location: Int) -> (String, Int) {
var offset: Int = 0
var word = NSString(string: string).substring(to: location)
let lines = string.components(separatedBy: "\n")
if lines.count > 0 {
let last = lines.last!
offset = word.count - last.count
word = last
}
return (word, offset)
}
}
replaceCharactersInRange(next, withString: " ", selectedRangeLocationMove: text.last!.count)
override func replaceCharacters(in range: NSRange, with str: String) {
var listPrefix: String? = nil
if (TextUtils.isReturn(str: str)) {
let currentLine = TextUtils.startOffset(self.string, location: range.location).0
let separateds = currentLine.components(separatedBy: " ")
if separateds.first!.contains("•") && currentLine.trimmingCharacters(in: .whitespaces).count == 1 {
listPrefix = ""
}
else {
if separateds.count >= 2 {
if separateds.first!.contains("•") {
listPrefix = "• "
}
}
}
}
beginEditing()
backingStore.replaceCharacters(in: range, with:str)
edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
endEditing()
guard let prefix = listPrefix else {
return
}
if prefix.isEmpty {
let text = string.split(separator: "\n")
let next = NSMakeRange(textView.selectedRange.location - (text.last!.count) - 1, text.last!.count + 1)
replaceCharactersInRange(next, withString: " ", selectedRangeLocationMove: 0)
} else {
let newRange = NSMakeRange(textView.selectedRange.location + str.count, 0)
replaceCharactersInRange(newRange, withString: prefix, selectedRangeLocationMove: prefix.count)
}
}
override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
guard range.upperBound <= string.count else { return }
beginEditing()
backingStore.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
endEditing()
}