2015-12-15 6 views
13

Я работаю над text view, который заменяет заполнители UITextFields. Я передаю ему объект (структуру или словарь) с текстом, содержащим несколько экземпляров токена-заполнителя. Словарь также содержит массив полей, из которых мы хотим собирать данные. Моя цель - разместить UITextFields (или другие виды) во всем тексте и скрыть маркеры.Интерполирование UITextFields с UITextView с использованием текстового набора?

Использование методов 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 Не то, что мы хотим. Добавление пути исключения, по-видимому, делает расчетные местоположения недействительными, но получение 0, 0 назад для каждого прямого метода не помогает.

Как я могу попросить менеджера компоновки пересчитать позицию для глифов на экране после добавления пути исключения или скрытия глифа?

EDIT: ответ Per 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

Я не понял этого. Вместо этого я реализовал подкласс UITextField, который делает что-то подобное. – Moshe

+0

вы можете подключить образец проекта xcode и поделиться им? Я бы хотел попробовать. – ShahiM

+0

У меня нет образца проекта на данный момент, но я посмотрю, смогу ли я его создать. – Moshe

ответ

2

Я могу только высказать предложения, как я новичок в TextKit себя, но что-то посмотрел на меня в вашем коде, и я подумал, что я упоминаю об этом на случай, если это поможет.

В вашей функции createAndAssignExclusionPathsForInputTextFields вы обрабатываете свои поля в порядке вашего массива self.textFields.

Когда вы устанавливаете значение self.textContainer.exclusionPaths в конце функции, диспетчер компоновки (потенциально) переводит текст вокруг всех ваших исключений, тем самым аннулируя некоторые из ваших вычислений, которые были выполнены без учета влияние других исключений.

Путь вокруг этого состоит в том, чтобы отсортировать массив self.textFields так, чтобы они соответствовали текстовому потоку (они уже могут быть в указанном порядке, я не могу сказать). Затем вы должны очистить все исключения из self.textContainer.exclusionPaths и добавить их обратно один за другим, чтобы перед вычислением следующего исключения менеджер компоновки переплавил текст вокруг ранее добавленного исключения.(Я считаю, что он делает это каждый раз, когда вы устанавливаете исключения, но если это не так, вам может потребоваться вызвать функцию layoutManager обеспеченияLayoutForManager)

Вы должны сделать это в текстовом потоке, чтобы каждое исключение было добавлено в область текст, который не приведет к аннулированию любого предыдущего исключения.

Оптимизация этого, чтобы избежать полного ясного/перестроения исключений, также возможно, но я хотел бы предложить получить исходный исходный код перед тем, как попытаться это сделать.

+0

Похоже, что обеспечениеLayoutForManager, возможно, было тем, чего мне не хватало. Я посмотрю и дам вам знать. – Moshe

+0

Итак, есть несколько способов обеспеченияLayoutFor .... Кажется, они не работают для меня. – Moshe

+0

Мои поля действительно в порядке. – Moshe

0

Возможно, вам следует использовать makeGlyphsForCharacterRange вместо этого, а также обеспечитьLayoutForManager. Мне нужно будет разработать более полную программу тестирования, чтобы точно это понять (сейчас у меня недостаточно свободного времени).

Однако я смотрел альтернативный способ редактирования редактируемых полей и редактируемого текста в 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  
} 

Это оставить еще несколько вещей, чтобы управлять такими, как обходе между полями, исходным положением курсора и некоторым поведением курсора для форматированного ввода текста, но все они, кажется, достаточно просто сделать.

Если вы не слишком далеко ушли от пути исключения, и ваши требования не сдерживают, возможно, это может помочь очистить блокпост.

+0

У меня на самом деле есть тот другой метод, который работает довольно хорошо. См. Мои комментарии к вопросу. Это немного сложнее, если вы хотите правильно обрабатывать выбор. – Moshe