Невозможно объяснить поведение, потому что оно вызывает как неопределенное поведение неопределенного поведения , так и неопределенное поведение , поэтому мы не можем делать какие-либо общие предсказания об этом коде, хотя, если вы прочитайте работу Олве Модала, такие как Deep C и Unspecified и Undefined , иногда вы можете делать хорошие догадки в очень конкретных случаях с конкретным компилятором и средой, но пожалуйста, не делайте этого где-нибудь рядом с производством.
Итак, переходим к неуказанному поведению , в черновик c99 standard раздел 6.5
пункт 3 говорит ( emphasis mine ):
Группировка операторов и операндов обозначается синтаксисом.74) За исключением случаев, указанных ниже (для функции -call (), & amp ;, ||,?: и запятые), порядок оценки подвыражений и порядок, в котором происходят побочные эффекты, не определены.
blockquote>Итак, когда у нас есть такая строка:
i = i++ + ++i;
, мы не знаем будет ли сначала оцениваться
i++
или++i
. Это главным образом для того, чтобы дать компилятору лучшие варианты оптимизации .Здесь также имеется неопределенное поведение , так как программа модифицирует переменные (
i
],u
и т. д.) более одного раза между точками последовательности . Из проекта стандартного раздела6.5
пункта 2 ( emphasis mine ):Между предыдущей и следующей точкой последовательности объект должен иметь свой запомненное значение, измененное не более одного раза путем оценки выражения. Кроме того, предыдущее значение должно быть считано только для определения хранимого значения.
blockquote>он ссылается на следующие примеры кода как неопределенные:
i = ++i + 1; a[i++] = i;
Всего в этих примерах код пытается изменить объект более одного раза в одной и той же точке последовательности, что закончится с
;
в каждом из этих случаев:i = i++ + ++i; ^ ^ ^ i = (i++); ^ ^ u = u++ + ++u; ^ ^ ^ u = (u++); ^ ^ v = v++ + ++v; ^ ^ ^
Неопределенное поведение определяется в черновик c99 стандарта в разделе
3.4.4
как:использование неопределенного значения или другое поведение, когда настоящий международный стандарт предусматривает два или больше возможностей и не налагает никаких дополнительных требований, которые выбраны в любом случае
blockquote>и неопределенное поведение определено в разделе
3.4.3
следующим образом:при использовании непереносимой или ошибочной программной конструкции или ошибочных данных, для которых настоящий международный стандарт не предъявляет требований
blockquote>и отмечает, что:
Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами, ведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей диагностического сообщения или без него), до прекращения перевода или исполнения ( с выдачей диагностического сообщения).
blockquote>
Метод делегата работает, но ТОЛЬКО, если вложение имеет изображение в атрибуте изображения И если editable = NO! Поэтому, если у вас есть образ, вставленный в атрибутString из другого места, кажется, что данные заканчиваются тем, что они хранятся в файлеWrapper, и в следующий раз, когда вы добавите атрибут attribring в textView, атрибут изображения равен нулю, а менеджер компоновки или что-то еще получает изображение из файла.
Где-то в документах упоминается, что в NSTextAttachment нет методов для сохранения атрибута изображения.
Чтобы протестировать эту попытку, скопируйте фотографию из приложения «Фото» и вставьте ее в свой текстовый рисунок, теперь, если вы удерживаете ее пальцем, вы должны увидеть всплывающее меню по умолчанию. Теперь, если вы сохраните этот богатый текст, скажете в объект Core Data, а затем извлечете его, атрибут изображения будет равен нулю, но данные изображения будут в attachment.fileWrapper.regularFileContents
. Это боль, и я бы с удовольствием знать намерения инженеров. Таким образом, у вас есть два варианта.
Помните о побочном эффекте Это делает недействительным fileWrapper. Я хочу изменить размер изображения, но также сохранить оригинал, чтобы я не потерял полное разрешение. Я думаю, что единственный способ сделать это может заключаться в подклассе NSTextAttachment.
EDIT:
Я выяснил, как создать пользовательские NSTextAttachments - вот ссылка для заинтересованных http://ossh.com.au/design-and- технология / разработка программного обеспечения / реализация-rich-text-with-images-on-os-x-and-ios /
EDIT 2: настройка меню в режиме редактирования см. в разделе следующие документы Apple, проблема «touchEnded» никогда не кажется вызванной, поэтому вам, возможно, придется попробовать использовать touchhesBegan. Однако осторожно, что вы не вмешиваетесь в поведение редактирования по умолчанию.
Обратите внимание, что в приведенном ниже коде вам нужно будет добавить код после комментария // selection management
, чтобы определить, какой символ был затронут, проверьте, является ли это особым текстовым вложением, а затем измените меню редактирования или предпринять некоторые другие действия.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *theTouch = [touches anyObject];
if ([theTouch tapCount] == 2 && [self becomeFirstResponder]) {
// selection management code goes here...
// bring up edit menu.
UIMenuController *theMenu = [UIMenuController sharedMenuController];
CGRect selectionRect = CGRectMake (currentSelection.x, currentSelection.y, SIDE, SIDE);
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES];
}
}
В качестве альтернативы вы можете добавить пользовательское меню, добавив пункт меню, а затем изменив метод canPerformAction.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
LOG(@"canPerformAction: called");
if (action == @selector(viewImage)) {
// Check the selected character is the special text attachment character
return YES;
}
return NO;
}
Вот код добавления, но его немного суетливый. Второй метод просто отключает меню редактирования по умолчанию, если обнаружено вложение.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
FLOG(@"touchesBegan:withEvent: called");
if (self.selectedRange.location != NSNotFound) {
FLOG(@" selected location is %d", self.selectedRange.location);
int ch;
if (self.selectedRange.location >= self.textStorage.length) {
// Get the character at the location
ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
} else {
// Get the character at the location
ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
}
if (ch == NSAttachmentCharacter) {
FLOG(@" selected character is %d, a TextAttachment", ch);
} else {
FLOG(@" selected character is %d", ch);
}
}
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
FLOG(@"canPerformAction: called");
FLOG(@" selected location is %d", self.selectedRange.location);
FLOG(@" TextAttachment character is %d", NSAttachmentCharacter);
if (self.selectedRange.location != NSNotFound) {
int ch;
if (self.selectedRange.location >= self.textStorage.length) {
// Get the character at the location
ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location-1];
} else {
// Get the character at the location
ch = [[[self textStorage] string] characterAtIndex:self.selectedRange.location];
}
if (ch == NSAttachmentCharacter) {
FLOG(@" selected character is %d, a TextAttachment", ch);
return NO;
} else {
FLOG(@" selected character is %d", ch);
}
// Check for an attachment
NSTextAttachment *attachment = [[self textStorage] attribute:NSAttachmentAttributeName atIndex:self.selectedRange.location effectiveRange:NULL];
if (attachment) {
FLOG(@" attachment attribute retrieved at location %d", self.selectedRange.location);
return NO;
}
else
FLOG(@" no attachment at location %d", self.selectedRange.location);
}
return [super canPerformAction:action withSender:sender];
}
Apple делает это очень сложно. Как отмечают другие, метод делегата вызывается, но только тогда, когда isEditable
является false
, или когда пользователь делает нажатие и удерживает вложение. Если вы хотите получить информацию о простом взаимодействии с краном во время редактирования, забудьте об этом.
Я пошел по путям touchesBegan:
и hitTest:
, как с проблемами. Способы касания называются после , [[8] уже обработали взаимодействие, а hitTest:
слишком грубо, потому что оно противоречит первому статусу ответчика и т. Д.
] Мое решение в конце концов было распознавателями жестов. Apple использует их внутри, что объясняет, почему touchesBegan:
в действительности не является жизнеспособным: распознаватели жестов уже обработали событие.
Я создал новый класс распознавания жестов для использования с UITextView
. Он просто проверяет местоположение крана, и если это приложение, оно обрабатывает его. Я делаю все другие распознаватели жестов подчиненными моему, поэтому мы сначала смотрим на события, а другие только вступают в игру, если наша неудача.
Класс распознавателя жестов ниже, наряду с расширение для добавления к UITextView
. Я добавляю его в свой подкласс UITextView
в awakeFromNib
, вот так. (Вам не нужно использовать подкласс, если у вас его нет.)
override func awakeFromNib() {
super.awakeFromNib()
let recognizer = AttachmentTapGestureRecognizer(target: self, action: #selector(handleAttachmentTap(_:)))
add(recognizer)
, и я обрабатываю действие, вызывая существующий метод UITextViewDelegate
textView(_:,shouldInteractWith:,in:,interaction:)
. Вы можете так же легко поместить код обработки непосредственно в действие, а не использовать делегат.
@IBAction func handleAttachmentTap(_ sender: AttachmentTapGestureRecognizer) {
let _ = delegate?.textView?(self, shouldInteractWith: sender.attachment!, in: NSRange(location: sender.attachmentCharacterIndex!, length: 1), interaction: .invokeDefaultAction)
}
Вот основной класс.
import UIKit
import UIKit.UIGestureRecognizerSubclass
/// Recognizes a tap on an attachment, on a UITextView.
/// The UITextView normally only informs its delegate of a tap on an attachment if the text view is not editable, or a long tap is used.
/// If you want an editable text view, where you can short cap an attachment, you have a problem.
/// This gesture recognizer can be added to the text view, and will add requirments in order to recognize before any built-in recognizers.
class AttachmentTapGestureRecognizer: UIGestureRecognizer {
/// Character index of the attachment just tapped
private(set) var attachmentCharacterIndex: Int?
/// The attachment just tapped
private(set) var attachment: NSTextAttachment?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
attachmentCharacterIndex = nil
attachment = nil
let textView = view as! UITextView
if touches.count == 1, let touch = touches.first, touch.tapCount == 1 {
let point = touch.location(in: textView)
let glyphIndex: Int? = textView.layoutManager.glyphIndex(for: point, in: textView.textContainer, fractionOfDistanceThroughGlyph: nil)
let index: Int? = textView.layoutManager.characterIndexForGlyph(at: glyphIndex ?? 0)
if let characterIndex = index, characterIndex < textView.textStorage.length {
if NSAttachmentCharacter == (textView.textStorage.string as NSString).character(at: characterIndex) {
attachmentCharacterIndex = characterIndex
attachment = textView.textStorage.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment
state = .recognized
} else {
state = .failed
}
}
} else {
state = .failed
}
}
}
extension UITextView {
/// Add an attachment recognizer to a UITTextView
func add(_ attachmentRecognizer: AttachmentTapGestureRecognizer) {
for other in gestureRecognizers ?? [] {
other.require(toFail: attachmentRecognizer)
}
addGestureRecognizer(attachmentRecognizer)
}
}
Этот же подход мог бы предположительно, будет использоваться для отводов по ссылкам.
Swift 3 answer:
func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
return true
}
Убедитесь, что у вас есть textView isEditable = false
, isSelectable = true
и isUserInteractionEnabled = true
. Ответ Duncan не упоминал isUserInteractionEnabled
, это должно быть true
, иначе оно не будет работать.
Вы можете сделать это программно (textView.isEditable = false) или через инспектор атрибутов:
Используйте hitTest, чтобы получить штрих в подклассе UITextView. Это позволяет избежать проблем со стандартными функциями редактирования. Из позиции получим индекс символа, а затем проверьте символ для вложения.