问题
Introduction
Context:
I am working on one of my first own apps, a sort of note-taking app.
The basic framework is simple: I have a UITableView with cells in them with a few design elements and a large UITextView which the user can write their notes into.
Issue:
I would like to implement the checkbox feature, similarly to what Apple has in their app "Notes". I want it to be a "part" of the text, so its erasable by hitting the erase on the keyboard.
I have checked posts on SO about inserting a character to a UITextView, which works (see code below), but they differ in their goal when it comes to the actual tap recognition. But I cant figure out how to check if the tap is on my NSAttributedString or not.
Question:
1. How do I swap out the characters that make up the checkbox when a user taps on them? 2. How do I get the UITapGestureRecognizer to work properly on my TextView? See edit also
Edit:
Old edited issues: (solved through silicon_valleys answer)
- My UITapGestureRecognizer doesn't work as intended, it doesn't seem to respond to the taps.
- How can I check if my checkbox is tapped on and replace the characters with the checkmark?
- How do I insert the checkbox only at the beginning of the line which the user's cursor is on?
Smaller new issues:
- The tap recognizer now works perfectly. But I cant find a way to 1. Convert the NSRange to a UITextRange so I can replace the characters or 2. use the NSRange to insert a character in the TextView.
Code:
checkbox insertion method
toolbarCheckbox(sender: UIBarButtonItem)
//This method is in my ViewController and adds a checkbox @objc func toolbarCheckbox(sender: UIBarButtonItem) { let checkboxCharacter: Character = "\u{25EF}" let emptyCheckbox = " \(checkboxCharacter) " guard case let cell as EventsCell = tableView.cellForRow(at: sender.indexPathID!) else {return} var beginningOfLineForSelection = cell.eventText.selectedTextRange.flatMap { cell.eventText.selectionRects(for: $0).first as? UITextSelectionRect }?.rect.origin ?? .zero beginningOfLineForSelection.x = 0 let layoutManager = cell.eventText.layoutManager let firstGlyphOnLine = layoutManager.glyphIndex( for: beginningOfLineForSelection, in: cell.eventText.textContainer, fractionOfDistanceThroughGlyph: nil) let newText = NSMutableAttributedString(attributedString: cell.eventText.attributedText) newText.insert(NSAttributedString(string: emptyCheckbox, attributes: [NSAttributedStringKey.font : UIFont(name: "Didot", size: 16)!]), at: firstGlyphOnLine) cell.eventText.attributedText = newText }
UITextView creation (in my cell class)
let eventText : GrowingTextView = { let tv = GrowingTextView(frame: .zero) let uncheckedBox = NSMutableAttributedString(string: checkboxCharacter, attributes: [NSAttributedStringKey.font : UIFont(name: "Didot", size: 16)!]) uncheckedBox.append(NSMutableAttributedString(string: checkedBoxCharacter, attributes: [NSAttributedStringKey.font : UIFont(name: "Didot", size: 16)!])) tv.attributedText = uncheckedBox tv.allowsEditingTextAttributes = true tv.isScrollEnabled = false tv.textContainerInset = UIEdgeInsetsMake(1, 1, 0, 1) tv.translatesAutoresizingMaskIntoConstraints = false tv.backgroundColor = .clear return tv }()
UITapGestureRecognizer action:
@objc func checkboxTapDone(sender: UITapGestureRecognizer) { guard let cell = tableView.cellForRow(at: sender.indexPathID!) as? EventsCell else { return } let layoutManager = cell.eventText.layoutManager var location = sender.location(in: cell.eventText) location.x -= cell.eventText.textContainerInset.left location.y -= cell.eventText.textContainerInset.top let textTapped = layoutManager.glyphIndex(for: location, in: cell.eventText.textContainer, fractionOfDistanceThroughGlyph: nil) let substring = (cell.eventText.attributedText.string as NSString).substring(with: NSMakeRange(textTapped, 1)) if substring == uncheckedBox { } else if substring == checkedBox { } }
Thanks for reading my post.
回答1:
It is not possible to add a UIButton
or other UIControl
in the middle of the text of a UITextView
. What you could do though, is using the attributedString
property of the UITextView
to render a checkbox character. You can do this by using a custom font with a character that matches the checkbox (checked and unchecked). You will need to add a UITapGestureRecognizer
to the UITextView
and then convert the point of the touch to detect if a checkbox was tapped and change the checkbox character from a selected checkbox to unselected and vice versa. You also need to bear in mind that you will have to set a delegate on the UITapGestureRecognizer
to make sure it allows simultaneous touches with the normal touches for moving the cursor and only intercept the tap when it is on a checkbox symbol.
You can add a button in a toolbar above the keyboard which will add the checkbox symbol to the text (see this solution: How can I add a toolbar above the keyboard?).
I hope this helps.
Answer to your further questions
My
UITapGestureRecognizer
doesn't work as intended, it doesn't seem to respond to the taps.This is because you try to use a single gesture recognizer on all of your textviews. You should create a new
UITapGestureRecognizer
for eachUITextView
. Now it will be removed from the previous views as you are adding the to the later views (so it will only detect the tap on the last cell that is dequeued).How can I check if my checkbox is tapped on and replace the characters with the checkmark?
The code you have inside
checkboxTapDone(sender:)
should deal with that. You are mostly there, but the range you are creating should only be 1 character, not 9. You then need to compare thesubstring
value with the value of a checked and unchecked character. Then update theeventText.attributedText
with the opposite character.How do I insert the checkbox only at the beginning of the line which the user's cursor is on?
The following code snippet should help you determine the range where you can insert the checkbox character.
var beginningOfLineForSelection = textView.selectedTextRange.flatMap {
textView.selectionRects(for: $0).first as? UITextSelectionRect
}?.rect.origin ?? .zero
beginningOfLineForSelection.x = 0
let layoutManager = textView.layoutManager
let firstGlyphOnLine = layoutManager.glyphIndex(
for: beginningOfLineForSelection,
in: textView.textContainer,
fractionOfDistanceThroughGlyph: nil
)
let insertCheckboxRange = NSMakeRange(firstGlyphOnLine, 0)
Answer to smaller new issues
- The reason you weren't seeing the
replaceCharacters(in:with:)
method (which takes anNSRange
as its first argument), is because you were using theNSAttributedString
(which isn't mutable). You need to first create a mutable copy of theattributedText
property. Then you can change the text and set it again on theUITextView
. See code snippet below for an example.
let newText = NSMutableAttributedString(attributedString: textView.attributedText)
newText.replaceCharacters(in: rangeOfCharactersToReplace, with: newCharacters)
textView.attributedText = newText
来源:https://stackoverflow.com/questions/51803423/swift-checkbox-insertion-in-uitextview-of-a-uitableviewcell-and-using-uitapgest