ios - Replace/delete characters [• ] in UITextView on Enter

好久不见. 提交于 2021-01-28 05:17:30

问题


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.

This is all of my code.

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

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.

Update

How to test this code?

  1. On the first line, add a bullet + one space [• ], write a few words and enter.
  2. From this point on, it adds a bullet since the prior line starts with a bullet.

回答1:


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


来源:https://stackoverflow.com/questions/61714083/ios-replace-delete-characters-in-uitextview-on-enter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!