使用Text Kit使用UITextView插入UITextFields?

时间:2015-12-15 18:18:47

标签: ios layout uitextview textkit

我正在使用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. 我无法成功隐藏占位符令牌。
  4. 因此,要解决我的列表中的第一个问题,我尝试在计算下一个问题之前添加排除路径,方法是更改​​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
    }
    

2 个答案:

答案 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      
}

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

如果你没有走太远的排除路径并且你的要求不是限制,那么这可能有助于清除障碍。