【问题标题】:Interpolating UITextFields with UITextView using Text Kit?使用 Text Kit 用 UITextView 插入 UITextFields?
【发布时间】:2015-12-15 18:18:46
【问题描述】:

我正在开发一个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
}

此时,我有三个问题:

  1. 一旦我为 textContainer 分配了排除路径,计算出来的其他占位符的字形位置现在都是错误的。
  2. calculatePositionOfPlaceholderAtIndex 方法给了我很好的 y 值,但 x 值都是 0。
  3. 我无法成功隐藏占位符标记。

因此,为了解决我列表中的第一个问题,我尝试在计算下一个问题之前添加排除路径,方法是更改​​ 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
}

【问题讨论】:

标签: ios layout uitextview textkit


【解决方案1】:

我只能提出建议,因为我自己是 TextKit 的新手,但你的代码中有一些东西让我很生气,我想我会提到它以防万一。

在您的 createAndAssignExclusionPathsForInputTextFields 函数中,您按照 self.textFields 数组的顺序处理字段。

当您在函数末尾设置 self.textContainer.exclusionPaths 的值时,布局管理器将(可能)重新排列所有排除项周围的文本,从而使您执行的某些计算无效,而没有考虑到其他排除的影响。

解决这个问题的方法是对你的 self.textFields 数组进行排序,以便它们对应于文本流(它们可能已经按照这个顺序,我不知道)。然后,您必须清除 self.textContainer.exclusionPaths 中的所有排除项并将它们一一添加,以便在计算下一个排除项之前,布局管理器已围绕先前添加的排除项重排文本。 (我相信每次设置排除项时都会这样做,但如果没有,您可能需要调用 layoutManager 的 ensureLayoutForManager 函数)

您必须按文本流顺序执行此操作,以便将每个排除项添加到不会使任何先前排除项无效的文本区域。

也可以对此进行优化以避免完全清除/重建排除项,但我建议在尝试之前先达到一个工作基线。

【讨论】:

  • 看起来 ensureLayoutForManager 可能是我所缺少的。我去看看,让你知道。
  • 所以有一些 ensureLayoutFor... 方法。它们似乎不适合我。
  • 我的字段确实是有序的。
  • 然后,我相信只要在每次添加之间重新调整布局,就可以随时添加它们(而不是在末尾设置数组)应该可以解决大部分问题。注意:您也必须在开始时清除它们。再说一次,我可能完全在左外野,因为我自己还没有尝试过。 Oopps 刚刚注意到您修改过的算法,它似乎确实做到了。等我下班回来再仔细看看。
  • 我也试过了。某些原因导致布局失效并且不会根据排除路径重新计算
【解决方案2】:

也许您应该使用 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      
}

这留下了一些需要管理的事情,例如字段之间的制表符、初始光标位置和格式化文本输入的一些光标行为,但它们看起来都很简单。

如果您没有在排除路径上走得太远,并且您的要求不是限制性的,也许这可以帮助清除障碍。

【讨论】:

  • 我现在实际上有其他方法工作得很好。请参阅我的问题。如果您也想正确处理选择,那就有点棘手了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-27
  • 2013-03-10
  • 1970-01-01
  • 2013-09-28
  • 2016-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多