2017-02-08 2 views
6

Ich färbe einige Teile eines Textes aus einer API (denke "@mention" wie auf Twitter) mit NSAttributedString.NSAttributedString und emojis: Problem mit Positionen und Längen

Die API gibt mir den Text und ein Array von Entitäten, die die Teile des Textes darstellen, die erwähnt werden (oder Links, Tags, etc.), die farbig sein sollten.

Aber manchmal ist die Färbung wegen Emojis versetzt.


Zum Beispiel mit diesem Text: "@ericd Some Text @apero"

die API gibt:

[ { " text ":" ericd ", " len ": 6, " pos ": 0 }, { "text": "apero" "len": 6, "po": 18 } ]

, die ich mit einem NSAttributedString Verwendung NSRange erfolgreich übersetzen:

for m in entities.mentions { 
    let r = NSMakeRange(m.pos, m.len) 
    myAttributedString.addAttribute(NSForegroundColorAttributeName, value: someValue, range: r) 
} 

Wir sehen, dass "pos": 18 korrekt ist, hier beginnt "@apero". Die farbigen Teile sind wie erwartet "@ericd" und "@apero".

"Some Text @ericd ✌ @apero."

:

aber, wenn einige spezifische Kombinationen von Emojis im Text verwendet werden, wird die API übersetzen nicht gut auf NSAttributedString wird die Färbung Offset

gibt:

[ { "text": "ericd", "len": 6, "po": 0 }, { "text": "apero" "len": 6, "po": 22 } ]

"pos": 22: der API-Autor gibt an, dass dies korrekt ist, und ich verstehe ihre Sichtweise.

Leider hat NSAttributedString nicht einverstanden sind, meine Färbung ist aus:

enter image description here

Die letzten Zeichen für die zweite Erwähnung nicht gefärbt sind (weil die „pos“ zu kurz ist, weil der Emojis ?).

Wie Sie vielleicht bereits erraten haben, kann ich in keiner Weise die Art, wie sich die API verhält, ändern, ich muss mich auf der Client-Seite anpassen.

Außer dass ... Ich habe keine Ahnung, was zu tun ist. Soll ich herausfinden, welche Art von Emojis im Text enthalten ist, und die Position von Erwähnungen manuell ändern, wenn es ein problematisches Emoji gibt? Aber was wären die Kriterien, um festzustellen, welches Emoji die Position verschiebt und welches nicht? Und wie entscheidet man, wie viel Offset ich brauche? Vielleicht wird das Problem von NSAttributedString verursacht?

Ich verstehe, dass dies auf die Emojis Länge einmal im Vergleich zu ihrer Länge als getrennte Zeichen verwandt ist, aber ... nun ja ... Ich bin verloren (Seufzer).


Bitte beachte, dass ich versucht habe, eine Lösung ähnlich wie this stuff zu implementieren, da mein API mit diesem kompatibel ist, aber es funktionierte nur teilweise einige Emojis waren noch brechen die Indizes:

enter image description here

Antwort

9

Ein Swift String bietet verschiedene "Ansichten" auf seinen Inhalt. Eine gute Übersicht ist in "Strings in Swift 2" im Swift Blog:

  • characters ist eine Sammlung von Charakterwerten oder erweitert Graphem-Cluster.
  • unicodeScalars ist eine Sammlung von Unicode-Skalarwerten.
  • utf8 ist eine Sammlung von UTF-8-Code-Einheiten.
  • utf16 ist eine Sammlung von UTF-16-Code-Einheiten.

Wie es in der Diskussion stellte sich heraus, pos und len von Ihrem API sind Indizes in die Ansicht Unicode Skalare.

Auf der anderen Seite ist die Methode der addAttribute()NSMutableAttributedString nimmt ein NSRange, das heißt den Bereich zu Indizes der UTF-16-Codepunkte in einem NSString entspricht.

String bietet Methoden, um zwischen Indizes der unterschiedliche Ansichten "übersetzen" (vergleiche NSRange to Range<String.Index>):

let text = "@ericd Some text. ✌ @apero" 
let pos = 22 
let len = 6 

// Compute String.UnicodeScalarView indices for first and last position: 
let from32 = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: pos) 
let to32 = text.unicodeScalars.index(from32, offsetBy: len) 

// Convert to String.UTF16View indices: 
let from16 = from32.samePosition(in: text.utf16) 
let to16 = to32.samePosition(in: text.utf16) 

// Convert to NSRange by computing the integer distances: 
let nsRange = NSRange(location: text.utf16.distance(from: text.utf16.startIndex, to: from16), 
         length: text.utf16.distance(from: from16, to: to16)) 

Diese NSRange ist, was Sie für die zugeschrieben Zeichenfolge benötigen:

let attrString = NSMutableAttributedString(string: text) 
attrString.addAttribute(NSForegroundColorAttributeName, 
         value: UIColor.red, 
         range: nsRange) 

Update für Swift 4 (Xcode 9): In S wift 4, die Standard-Bibliothek stellt Verfahren zwischen Swift String Bereiche und NSString Bereiche zu konvertieren, damit die Berechnungen vereinfachen

let text = "@ericd Some text. ✌ @apero" 
let pos = 22 
let len = 6 

// Compute String.UnicodeScalarView indices for first and last position: 
let fromIdx = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: pos) 
let toIdx = text.unicodeScalars.index(fromIdx, offsetBy: len) 

// Compute corresponding NSRange: 
let nsRange = NSRange(fromIdx..<toIdx, in: text) 
+1

Interessant! (Der Bereich, den ich verwende, ist bereits ein NSRange, aber kein Swift Range). Ich werde etwas Zeit brauchen, um dies zu studieren und Tests durchzuführen, um zu sehen, wie es sich im Vergleich zur API, die ich anrufe, verhält.Danke, ich gebe Feedback wenn möglich. – Moritz

+0

In Ihrem Beispiel, wenn Sie pos 22 und len 6 verwenden, wie meine API für denselben Text, wird "@apero" erst nach dem "@" (anstatt der gesamten Erwähnung) farbig dargestellt (https://www.evernote.com)/l/AOznB6IhPzxJrbvva5Jxx24cyFv9MrhSqkI). – Moritz

+0

@EricAya: Aber das "@" von "@apero" ist auf Position 21, nicht 22. Oder wie zählt man die Charaktere? –

Verwandte Themen