本文介绍了ios - 在输入时替换/删除 UITextView 中的字符 [•]的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有自定义 NSTextStorage 的 UITextView,如果前一行以列表项目符号开头,我会在每次 Enter 之后添加一个列表项目符号.

I have a UITextView with a custom NSTextStorage, where I add a list bullet, after each Enter, if the prior line starts with a list bullet.

当用户输入并且光标所在行的开头只有一个列表项目符号时,我删除列表项目符号并留在该行.

When a user Enter and there is only a list bullet in the beginning of the line where the cursor is, I remove the list bullet and stay on that line.

第一个函数按预期工作.但是我很难弄清楚如何删除列表项目符号.

The first function works as expected. But I have a hard time figuring out how to remove the list bullet.

           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)
          }

上面的代码不起作用.我可能没有使用正确的范围.

It is the code above that does not work. I probably do not use the correct Range.

这是我的全部代码.

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)
    }
}

我还使用了 self.deleteCharacters(in:) 而不是下面的函数.它也不起作用.


I used also used self.deleteCharacters(in:) instead of the following function. It did not work either.

replaceCharactersInRange(next, withString: " ", selectedRangeLocationMove: text.last!.count)

总而言之,当用户输入并停留在该行时,我只需要删除光标所在的单词.


To sum up, I just need to remove a word where the cursor is when a user Enter and stay on that line.

如果您能帮我解决这个问题,我将不胜感激.

I will be very grateful if you help me fix this issue.

如何测试这段代码?

  1. 在第一行,添加一个项目符号 + 一个空格 [• ],写几个字然后输入.
  2. 从现在开始,它添加了一个项目符号,因为前一行以项目符号开头.

推荐答案

在下面找到修改后的函数.使用 Xcode 11.4/iOS 13.4 测试

Find below modified functions. Tested with Xcode 11.4 / iOS 13.4

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()
}

这篇关于ios - 在输入时替换/删除 UITextView 中的字符 [•]的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 07:58