我正在使用text view替换占位符和UITextFields。我传递了一个对象(结构或字典),其中包含多个占位符令牌实例的文本。该字典还包含我们想要从中收集数据的字段数组。我的目标是在我的文本中放置UITextFields(或其他视图),并隐藏令牌。
使用NSLayoutManager方法计算文本容器中占位符标记的位置,我将这些点转换为CGRects,然后将路径排除到文本字段周围。这是看起来像:
func createAndAssignExclusionPathsForInputTextFields () {
var index = 0
let textFieldCount = self.textFields.count
var exclusionPaths : [UIBezierPath] = []
while index < textFieldCount {
let textField : AgreementTextField = self.textFields[index]
let location = self.calculatePositionOfPlaceholderAtIndex(index)
let size = textField.intrinsicContentSize()
textField.frame = CGRectMake(location.x, location.y, size.width, size.height)
exclusionPaths.append(textField.exclusionPath())
index = index + 1
}
self.textContainer.exclusionPaths = exclusionPaths
}
// ...
func calculatePositionOfPlaceholderAtIndex(textIndex : NSInteger) -> CGPoint {
let layoutManager : NSLayoutManager = self.textContainer.layoutManager!
let delimiterRange = self.indices[textIndex]
let characterIndex = delimiterRange.location
let glyphRange = self.layoutManager.glyphRangeForCharacterRange(delimiterRange, actualCharacterRange:nil)
let glyphIndex = glyphRange.location
let rect = layoutManager.lineFragmentRectForGlyphAtIndex(glyphIndex, effectiveRange: nil, withoutAdditionalLayout: true)
let remainingRect : UnsafeMutablePointer<CGRect> = nil
let textContainerRect = self.textContainer.lineFragmentRectForProposedRect(rect, atIndex: characterIndex, writingDirection: .LeftToRight, remainingRect: remainingRect)
let position = CGPointMake(textContainerRect.origin.x, textContainerRect.origin.y)
return position
}
此时,我有三个问题:
calculatePositionOfPlaceholderAtIndex
方法给出了非常好的y值,但x值都是0。因此,要解决我的列表中的第一个问题,我尝试在计算下一个问题之前添加排除路径,方法是更改createAndAssignExclusionPathsForInputTextFields
:
func createAndAssignExclusionPathsForInputTextFields () {
var index = 0
let textFieldCount = self.textFields.count
while index < textFieldCount {
let textField : AgreementTextField = self.textFields[index]
let location = self.calculatePositionOfPlaceholderAtIndex(index)
let size = textField.intrinsicContentSize()
textField.frame = CGRectMake(location.x, location.y, size.width, size.height)
self.textContainer.exclusionPaths.append(textField.exclusionPath())
index = index + 1
}
}
现在,我的计算位置都返回0,0。不是我们想要的。可以理解地添加排除路径会使计算的位置无效,但是每个rect方法返回0,0都没有用。
在添加排除路径或隐藏字形后,如何让布局管理器重新计算屏幕上字形的位置?
编辑:根据Alain T的回答,我尝试了下面但没有运气:
func createAndAssignExclusionPathsForInputTextFields () {
var index = 0
let textFieldCount = self.textFields.count
var exclusionPaths : [UIBezierPath] = []
while index < textFieldCount {
let textField : AgreementTextField = self.textFields[index]
let location = self.calculatePositionOfPlaceholderAtIndex(index)
let size = textField.intrinsicContentSize()
textField.frame = CGRectMake(location.x, location.y, size.width, size.height)
exclusionPaths.append(textField.exclusionPath())
self.textContainer.exclusionPaths = exclusionPaths
self.layoutManager.ensureLayoutForTextContainer(self.textContainer)
index = index + 1
}
self.textContainer.exclusionPaths = exclusionPaths
}
答案 0 :(得分:2)
我只能提出建议,因为我自己是TextKit的新手,但在你的代码中有些瞪着我,我想我会提到它,以防它可以提供帮助。
在createAndAssignExclusionPathsForInputTextFields函数中,您将按self.textFields数组的顺序处理字段。
当您在函数末尾设置self.textContainer.exclusionPaths的值时,布局管理器将(可能)重新围绕所有排除项重新生成文本,从而使您执行的某些计算无效而不考虑其他排除的影响。
解决这个问题的方法是对self.textFields数组进行排序,使它们对应于文本流(它们可能已经按顺序排列,我无法告诉)。然后,您必须清除self.textContainer.exclusionPaths中的所有排除项并逐个添加它们,以便在计算下一个排除项之前,布局管理器已对先前添加的排除项周围的文本进行了重排。 (我相信它每次设置排除项时都会这样做,但如果没有,则可能需要调用layoutManager的ensureLayoutForManager函数)
您必须以文本流顺序执行此操作,以便在文本区域中添加每个排除项,这些区域不会使以前的任何排除项无效。
优化这一点以避免完全清除/重建排除项目也是可能的,但我建议在尝试之前进入工作基线。
答案 1 :(得分:0)
也许您应该使用ensureGlyphsForCharacterRange或者使用ensureLayoutForManager。我需要建立一个更全面的测试程序来确定(我现在没有足够的空闲时间)。
然而,我确实在UITextView中有另一种方法可以获得可编辑的字段和不可编辑的文本,并提出了一种纯文本方法(粗略和不完整但它是一个起点)。我们的想法是使用一些文本属性来识别可编辑字段,并使用UITextView的委托使其他所有内容都不可编辑。
// function to determine if attributes of a range of text allow editing
// (you decide which attributes correspond to editable text)
func editableText(attributes:[String:AnyObject]) -> Bool
{
if let textColor = attributes[NSForegroundColorAttributeName] as? UIColor
where textColor == UIColor.redColor() // example: red text is editable
{ return true }
return false
}
// UITextViewDelegate's function to allow editing a specific text area (range)
func textView(textView: UITextView, var shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool
{
// 0 length selection is an insertion point
// will get the attibutes of the text preceding it as typing attributes
if range.length == 0
&& text != ""
&& editableText(textView.typingAttributes)
{ return true }
// in case we're on an insertion point at the very begining of an editable field
// lets look at the next character
range.length = max(1, range.length)
// for non-zero ranges, all subranges must have the editable attributes to allow editing
// (also setting typingAttributes to cover the insertion point at begining of field case)
var canEdit = true
textView.attributedText.enumerateAttributesInRange(range, options:[])
{
if self.editableText($0.0)
{ textView.typingAttributes = $0.0 }
else
{ canEdit = false }
}
return canEdit
}
这还需要管理一些事情,例如字段之间的标签,初始光标位置和格式化文本输入的某些光标行为,但它们看起来都很简单。
如果你没有走太远的排除路径并且你的要求不是限制,那么这可能有助于清除障碍。