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.