Swift:在 UITableViewCell 的 UITextView 中插入复选框并使用 UITapGestureRecognizer 识别复选框上的点击
Swift: Checkbox insertion in UITextView of a UITableViewCell and using UITapGestureRecognizer to recognize taps on the checkbox
简介
上下文:
我正在开发我的第一个应用程序,一种笔记应用程序。
基本框架很简单:我有一个 UITableView,里面有单元格,有一些设计元素,还有一个大的 UITextView,用户可以在里面写笔记。
问题:
我想实现复选框功能,类似于 Apple 在其应用程序中的功能 "Notes"。我希望它是文本的 "part",所以它可以通过点击键盘上的擦除来擦除。
我已经检查了 posts 关于将字符插入到 UITextView 的 SO,这是有效的(见下面的代码),但是当涉及到实际点击时,它们的目标不同认出。但是我不知道如何检查点击是否在我的 NSAttributedString 上。
问题:
1.当用户点击它们时,如何换出组成复选框的字符? 2. 如何让 UITapGestureRecognizer 在我的 TextView 上正常工作? 另见编辑
编辑:
旧编辑问题:(通过 silicon_valleys 回答解决)
- 我的 UITapGestureRecognizer 没有按预期工作,它似乎没有响应点击。
- 如何检查我的复选框是否被点击并用复选标记替换字符?
- 如何只在用户光标所在行的开头插入复选框?
较小的新问题:
- 点击识别器现在可以完美运行。但我找不到方法 1. 将 NSRange 转换为 UITextRange 以便我可以替换字符或 2. 使用 NSRange 在 TextView 中插入字符。
代码:
复选框插入方法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: [=11=]).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 创建(在我的单元格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 操作:
@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 {
}
}
感谢阅读我的post。
无法在 UITextView
的文本中间添加 UIButton
或其他 UIControl
。不过,您可以做的是使用 UITextView
的 attributedString
属性 来呈现复选框字符。您可以通过使用带有与复选框(选中和未选中)匹配的字符的自定义字体来执行此操作。您需要将 UITapGestureRecognizer
添加到 UITextView
,然后转换触摸点以检测复选框是否被点击,并将复选框字符从选中的复选框更改为未选中,反之亦然。您还需要记住,您必须在 UITapGestureRecognizer
上设置一个委托,以确保它允许同时触摸与移动光标的正常触摸,并且仅在它位于复选框符号上时拦截点击。
您可以在键盘上方的工具栏中添加一个按钮,将复选框符号添加到文本中(参见此解决方案:How can I add a toolbar above the keyboard?)。
希望对您有所帮助。
回答您进一步的问题
我的 UITapGestureRecognizer
没有按预期工作,它似乎对点击没有反应。
这是因为您尝试在所有文本视图上使用单个手势识别器。您应该为每个 UITextView
创建一个新的 UITapGestureRecognizer
。现在,当您将它添加到以后的视图时,它将从以前的视图中删除(因此它只会检测出列的最后一个单元格上的点击)。
如何检查我的复选框是否被点击并用复选标记替换字符?
您在 checkboxTapDone(sender:)
中的代码应该可以解决这个问题。你大部分都在那里,但你创建的范围应该只有 1 个字符,而不是 9 个。然后你需要将 substring
值与选中和未选中字符的值进行比较。然后用相反的字符更新 eventText.attributedText
。
如何只在用户光标所在行的开头插入复选框?
以下代码片段可以帮助您确定可以插入复选框字符的范围。
var beginningOfLineForSelection = textView.selectedTextRange.flatMap {
textView.selectionRects(for: [=10=]).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)
对较小的新问题的回答
- 您没有看到
replaceCharacters(in:with:)
方法(将 NSRange
作为其第一个参数)的原因是您使用的是 NSAttributedString
(不是可变)。您需要先创建 attributedText
属性 的可变副本。然后您可以更改文本并在 UITextView
上重新设置它。有关示例,请参见下面的代码片段。
let newText = NSMutableAttributedString(attributedString: textView.attributedText)
newText.replaceCharacters(in: rangeOfCharactersToReplace, with: newCharacters)
textView.attributedText = newText
简介
上下文:
我正在开发我的第一个应用程序,一种笔记应用程序。
基本框架很简单:我有一个 UITableView,里面有单元格,有一些设计元素,还有一个大的 UITextView,用户可以在里面写笔记。
问题:
我想实现复选框功能,类似于 Apple 在其应用程序中的功能 "Notes"。我希望它是文本的 "part",所以它可以通过点击键盘上的擦除来擦除。
我已经检查了 posts 关于将字符插入到 UITextView 的 SO,这是有效的(见下面的代码),但是当涉及到实际点击时,它们的目标不同认出。但是我不知道如何检查点击是否在我的 NSAttributedString 上。
问题:
1.当用户点击它们时,如何换出组成复选框的字符? 2. 如何让 UITapGestureRecognizer 在我的 TextView 上正常工作? 另见编辑
编辑:
旧编辑问题:(通过 silicon_valleys 回答解决)
- 我的 UITapGestureRecognizer 没有按预期工作,它似乎没有响应点击。
- 如何检查我的复选框是否被点击并用复选标记替换字符?
- 如何只在用户光标所在行的开头插入复选框?
较小的新问题:
- 点击识别器现在可以完美运行。但我找不到方法 1. 将 NSRange 转换为 UITextRange 以便我可以替换字符或 2. 使用 NSRange 在 TextView 中插入字符。
代码:
复选框插入方法
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: [=11=]).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 创建(在我的单元格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 操作:
@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 { } }
感谢阅读我的post。
无法在 UITextView
的文本中间添加 UIButton
或其他 UIControl
。不过,您可以做的是使用 UITextView
的 attributedString
属性 来呈现复选框字符。您可以通过使用带有与复选框(选中和未选中)匹配的字符的自定义字体来执行此操作。您需要将 UITapGestureRecognizer
添加到 UITextView
,然后转换触摸点以检测复选框是否被点击,并将复选框字符从选中的复选框更改为未选中,反之亦然。您还需要记住,您必须在 UITapGestureRecognizer
上设置一个委托,以确保它允许同时触摸与移动光标的正常触摸,并且仅在它位于复选框符号上时拦截点击。
您可以在键盘上方的工具栏中添加一个按钮,将复选框符号添加到文本中(参见此解决方案:How can I add a toolbar above the keyboard?)。
希望对您有所帮助。
回答您进一步的问题
我的
UITapGestureRecognizer
没有按预期工作,它似乎对点击没有反应。这是因为您尝试在所有文本视图上使用单个手势识别器。您应该为每个
UITextView
创建一个新的UITapGestureRecognizer
。现在,当您将它添加到以后的视图时,它将从以前的视图中删除(因此它只会检测出列的最后一个单元格上的点击)。如何检查我的复选框是否被点击并用复选标记替换字符?
您在
checkboxTapDone(sender:)
中的代码应该可以解决这个问题。你大部分都在那里,但你创建的范围应该只有 1 个字符,而不是 9 个。然后你需要将substring
值与选中和未选中字符的值进行比较。然后用相反的字符更新eventText.attributedText
。如何只在用户光标所在行的开头插入复选框?
以下代码片段可以帮助您确定可以插入复选框字符的范围。
var beginningOfLineForSelection = textView.selectedTextRange.flatMap {
textView.selectionRects(for: [=10=]).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)
对较小的新问题的回答
- 您没有看到
replaceCharacters(in:with:)
方法(将NSRange
作为其第一个参数)的原因是您使用的是NSAttributedString
(不是可变)。您需要先创建attributedText
属性 的可变副本。然后您可以更改文本并在UITextView
上重新设置它。有关示例,请参见下面的代码片段。
let newText = NSMutableAttributedString(attributedString: textView.attributedText)
newText.replaceCharacters(in: rangeOfCharactersToReplace, with: newCharacters)
textView.attributedText = newText