Jak mogę przekonwertować NSRange
na Range<String.Index>
Swift?
Chcę użyć następującej UITextFieldDelegate
metody:
func textField(textField: UITextField!,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String!) -> Bool {
textField.text.stringByReplacingCharactersInRange(???, withString: string)
Odpowiedzi:
NSString
Wersja (w przeciwieństwie do Swift łańcucha znaków)replacingCharacters(in: NSRange, with: NSString)
akceptujeNSRange
, aby jedno proste rozwiązanie polega na konwersjiString
doNSString
pierwszej . Nazwy metod delegowania i wymiany są nieco inne w Swift 3 i 2, więc w zależności od używanego Swift:Swift 3.0
Swift 2.x
źródło
textField
zawiera znaki Unicode z wieloma kodami, takie jak emoji. Ponieważ jest to pole wejściowe, użytkownik może równie dobrze wprowadzić emoji (😂), inne znaki na stronie 1 znaków wielu jednostek kodu, takie jak flagi (🇪🇸).UITextFieldDelegate
jesttextField:shouldChangeCharactersInRange:replacementString:
metoda nie zapewni szereg która rozpoczyna się lub kończy wewnątrz Unicode, gdyż zwrotna jest oparty na wprowadzanie znaków z klawiatury przez użytkownika, a to nie jest możliwe, aby wpisać tylko część znaku Unicode emoji. Zauważ, że gdyby delegat został napisany w Obj-C, miałby ten sam problem.(textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
Począwszy od Swift 4 (Xcode 9), standardowa biblioteka Swift zapewnia metody konwersji między zakresami ciągów Swift (
Range<String.Index>
) iNSString
zakresami (NSRange
). Przykład:Dlatego zamiana tekstu w metodzie delegowania pola tekstowego może być teraz wykonywana jako
(Starsze odpowiedzi dla Swift 3 i wcześniejszych :)
Począwszy od wersji Swift 1.2,
String.Index
ma inicjalizatorktóry może być stosowany do konwersji
NSRange
doRange<String.Index>
prawidłowego (w tym wszystkich przypadkach emotikony, wskaźniki regionalnych lub innych wydłużonych klastrów grafem) bez pośredniego konwersję doNSString
:Ta metoda zwraca opcjonalny zakres ciągów, ponieważ nie wszystkie
NSRange
s są poprawne dla danego ciągu Swift.UITextFieldDelegate
Metoda delegata można następnie zapisać jakoOdwrotna konwersja to
Prosty test:
Aktualizacja dla Swift 2:
Wersja Swift 2
rangeFromNSRange()
została już podana przez Serhii Yakovenko w tej odpowiedzi , zamieszczam ją tutaj dla kompletności:Wersja Swift 2
NSRangeFromRange()
toAktualizacja dla Swift 3 (Xcode 8):
Przykład:
źródło
range
oznacza to,(0,2)
żeNSRange
odnosi się do znaków UTF-16 wNSString
. Ale liczy się jako jeden znak Unicode w ciągu Swift. -let end = advance(start, range.length)
z przywołanej odpowiedzi ulega awarii w tym przypadku z komunikatem o błędzie „błąd krytyczny: nie można zwiększyć parametru endIndex”.String
aby przejść do odwołania do interfejsu API, przeczytaj wszystkie metody i komentarze, a następnie wypróbuj różne rzeczy, aż zadziała :)let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex)
, myślę, że rzeczywiście chcemy ograniczać goutf16.endIndex - 1
. W przeciwnym razie możesz zacząć od końca łańcucha.Musisz użyć
Range<String.Index>
zamiast klasycznegoNSRange
. Sposób, w jaki to robię (być może jest lepszy sposób), polega na tym, że ciąg jestString.Index
ruchomyadvance
.Nie wiem, jaki zakres próbujesz zastąpić, ale udawaj, że chcesz zamienić pierwsze 2 znaki.
źródło
Ta odpowiedź Martina R wydaje się poprawna, ponieważ uwzględnia Unicode.
Jednak w momencie publikacji (Swift 1) jego kod nie kompiluje się w Swift 2.0 (Xcode 7), ponieważ usunęły one
advance()
funkcję. Zaktualizowana wersja znajduje się poniżej:Swift 2
Szybki 3
Szybki 4
źródło
Jest to podobne do odpowiedzi Emilie jednak skoro pytasz konkretnie w jaki sposób przekonwertować
NSRange
doRange<String.Index>
ciebie by zrobić coś takiego:źródło
NSRange
mówi, chociaż jej komponenty są liczbami całkowitymi) w widoku znaków ciągu, co może się nie powieść, gdy zostanie użyte w ciągu znaków z „znakami”, których wyrażenie wymaga więcej niż jednej jednostki UTF16.Riff na świetnej odpowiedzi @ Emilie, a nie odpowiedź zastępcza / konkurencyjna.
(Xcode6-Beta5)
Wynik:
Zwróć uwagę, że indeksy uwzględniają wiele jednostek kodu. Flaga (REGIONAL INDICATOR SYMBOL LETTERS ES) ma 8 bajtów, a (TWARZ Z ŁZY RADOŚCI) ma 4 bajty. (W tym konkretnym przypadku okazuje się, że liczba bajtów jest taka sama dla reprezentacji UTF-8, UTF-16 i UTF-32).
Zawijanie w func:
Wynik:
źródło
źródło
W Swift 2.0 przy założeniu
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
:źródło
Oto mój najlepszy wysiłek. Ale to nie może sprawdzić lub wykryć nieprawidłowego argumentu wejściowego.
Wynik.
Detale
NSRange
zNSString
zlicza kod-jednostka UTF-16 . ARange<String.Index>
od SwiftString
jest nieprzezroczysty typ względny, który zapewnia tylko operacje równości i nawigacji. Jest to celowo ukryty projekt.Chociaż
Range<String.Index>
wydaje się, że jest odwzorowany na przesunięcie jednostki kodu UTF-16, jest to tylko szczegół implementacji i nie mogłem znaleźć żadnej wzmianki o żadnej gwarancji. Oznacza to, że szczegóły implementacji można zmienić w dowolnym momencie. Wewnętrzna reprezentacja SwiftString
nie jest dość zdefiniowana i nie mogę na niej polegać.NSRange
wartości mogą być bezpośrednio mapowane naString.UTF16View
indeksy. Ale nie ma metody na konwersjęString.Index
.Swift
String.Index
jest indeksem do iteracji Swift,Character
która jest klastrem grafodowym w Unicode . Następnie musisz podać właściwy,NSRange
który wybiera prawidłowe klastry grafemów. Jeśli podasz zły zakres, jak w powyższym przykładzie, spowoduje to niepoprawny wynik, ponieważ nie można ustalić właściwego zakresu skupień grafemu.Jeśli istnieje gwarancja, że
String.Index
jest to przesunięcie jednostki kodu UTF-16, problem staje się prosty. Ale jest mało prawdopodobne.Odwrotna konwersja
W każdym razie odwrotną konwersję można wykonać dokładnie.
Wynik.
źródło
return NSRange(location: a.utf16Count, length: b.utf16Count)
należy zmienić nareturn NSRange(location: a.utf16.count, length: b.utf16.count)
Znalazłem najczystsze rozwiązanie tylko dla swift2, aby utworzyć kategorię na NSRange:
A następnie wywołaj go z funkcji delegowania pola tekstowego:
źródło
Oficjalna dokumentacja Swift 3.0 beta podała standardowe rozwiązanie tej sytuacji pod tytułem String.UTF16View w sekcji UTF16View Elements Match NSString Postacie
źródło
W przyjętej odpowiedzi uważam opcje za niewygodne. Działa to z Swift 3 i wydaje się, że nie ma problemu z emoji.
źródło
źródło