ios - 输入时在UITextView中替换了删除字符[-]。

问题描述 投票:1回答:1

我有一个带有自定义NSTextStorage的UITextView,在每次Enter之后,如果前一行有列表子弹,我就添加一个列表子弹。

当用户回车时,只有光标所在行的开头有一个列表子弹,我就删除列表子弹,并停留在该行上。

第一个功能按预期工作。但我很难理解如何删除列表子弹。


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

是上面的代码不工作。我可能没有使用正确的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")
     }
 }

TextStorage

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:) 而不是下面的函数。也没有成功。

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

综上所述,我只需要在用户回车时删除光标所在的一个字,并在该行停留。

如果您能帮助我解决这个问题,我将非常感激。

更新

如何测试这段代码?

  1. 在第一行,加一个子弹+一个空格[-],写几个字,然后回车。
  2. 从这时开始,因为前面一行是以子弹开头的,所以就加了一个子弹。
ios swift uitextview nsrange nstextstorage
1个回答
1
投票

查找以下修改的功能。用Xcode 11.4 iOS 13.4测试。

demo

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()
}
© www.soinside.com 2019 - 2024. All rights reserved.