I'm working on a text view that replaces placeholders with UITextFields. I pass it an object (a struct or a dictionary) with text containing multiple instances of a placeholder token. The dictionary also contains an array of fields that we want to collect data from. My goal is to place UITextFields (or other views) throughout my text, and hide the tokens.
Using NSLayoutManager methods to calculate the location of my placeholder tokens in the text containers, I convert those points to CGRects and then exclusion paths to, flow my text around the text fields. Here's what that looks like :
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
}
At this point, I have three issues:
- Once I assign an exclusion path to the textContainer, the calculated glyph positions for the other placeholders are now all wrong.
- The
calculatePositionOfPlaceholderAtIndex
method is giving me pretty good y values, but the x values are all 0. - I haven't been able successfully hide the placeholder tokens.
So, to solve first issue on my list, I tried adding the exclusion path before calculating the next one, by changing 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
}
}
Now, my calculated positions are all returning 0, 0. Not what we want. Adding an exclusion path understandably makes the calculated locations invalid, but getting 0, 0 back for every rect method isn't helpful.
How can I ask the layout manager to re-calculate the position for glyphs on screen after adding an exclusion path or hiding a glyph?
EDIT: Per Alain T's answer, I tried the following with no luck:
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
}