J'ai mis à jour une partie de mon ancien code et des réponses avec Swift 3, mais lorsque je suis arrivé à Swift Strings et à l'indexation, il m'a été difficile de comprendre les choses.
Plus précisément, j'essayais ce qui suit:
let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5) // error
où la deuxième ligne me donnait l'erreur suivante
'advancedBy' n'est pas disponible: pour faire avancer un index de n étapes, appelez 'index (_: offsetBy :)' sur l'occurrence CharacterView qui a généré l'index.
Je vois que String
a les méthodes suivantes.
str.index(after: String.Index)
str.index(before: String.Index)
str.index(String.Index, offsetBy: String.IndexDistance)
str.index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)
C’était vraiment déroutant au début, alors j’ai commencé à jouer avec eux jusqu’à ce que je les comprenne. J'ajoute une réponse ci-dessous pour montrer comment ils sont utilisés.
Tous les exemples suivants utilisent
var str = "Hello, playground"
startIndex
et endIndex
startIndex
est l'index du premier caractèreendIndex
est l'index after le dernier caractère.Exemple
// character
str[str.startIndex] // H
str[str.endIndex] // error: after last character
// range
let range = str.startIndex..<str.endIndex
str[range] // "Hello, playground"
Avec Swift 4's gammes unilatérales , la plage peut être simplifiée à l’une des formes suivantes.
let range = str.startIndex...
let range = ..<str.endIndex
Je vais utiliser le formulaire complet dans les exemples suivants pour des raisons de clarté, mais par souci de lisibilité, vous voudrez probablement utiliser les plages unilatérales de votre code.
after
Comme dans: index(after: String.Index)
after
fait référence à l'index du caractère directement après l'index donné.Exemples
// character
let index = str.index(after: str.startIndex)
str[index] // "e"
// range
let range = str.index(after: str.startIndex)..<str.endIndex
str[range] // "Ello, playground"
before
Comme dans: index(before: String.Index)
before
fait référence à l'index du caractère directement avant l'index donné.Exemples
// character
let index = str.index(before: str.endIndex)
str[index] // d
// range
let range = str.startIndex..<str.index(before: str.endIndex)
str[range] // Hello, playgroun
offsetBy
Comme dans: index(String.Index, offsetBy: String.IndexDistance)
offsetBy
peut être positive ou négative et commence à partir de l'index donné. Bien qu'il soit du type String.IndexDistance
, vous pouvez lui donner une Int
.Exemples
// character
let index = str.index(str.startIndex, offsetBy: 7)
str[index] // p
// range
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
str[range] // play
limitedBy
Comme dans: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)
limitedBy
est utile pour s'assurer que le décalage ne provoque pas l'index pour sortir des limites. C'est un index englobant. Comme il est possible que le décalage dépasse la limite, cette méthode retourne une option. Il retourne nil
si l'index est hors limites.Exemple
// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
str[index] // p
}
Si l'offset avait été 77
au lieu de 7
, l'instruction if
aurait été ignorée.
Il serait beaucoup plus facile d'utiliser un index Int
pour Strings. La raison pour laquelle vous devez créer un nouveau String.Index
pour chaque chaîne est que les caractères dans Swift ne sont pas tous de la même longueur sous le capot. Un seul caractère Swift peut être composé d'un, de deux ou même de plusieurs points de code Unicode. Ainsi, chaque chaîne unique doit calculer les index de ses caractères.
C'est peut-être pour cacher cette complexité derrière une extension d'index Int, mais je suis réticent à le faire. Il est bon de se rappeler de ce qui se passe réellement.
Je faisais face au même problème et je me suis retrouvé avec une solution adaptée à mes besoins. J'ai un UITextView à l'intérieur d'un tableViewController. J'ai utilisé function: textViewDidChange, puis vérifié pour return-key-input . Puis s'il détectait return-key-input, j'ai supprimé l'entrée de la touche de retour et ignoré le clavier.
func textViewDidChange(_ textView: UITextView) {
tableView.beginUpdates()
if textView.text.contains("\n"){
textView.text.remove(at: textView.text.index(before: textView.text.endIndex))
textView.resignFirstResponder()
}
tableView.endUpdates()
}