Concrètement voici le process que j’aimerai atteindre :
les boutons auront différentes données ( exemple : tableau périodique éléments).
l’utilisateur pourra sélectionner grâce à différents sliders qui agiront eux mêmes sur des labels (cela marche parfaitement)
mon problème est celui-là : l’indice incrémenté dans le label ( via slider ou clavier ) permettra une sélection de différents boutons par surbrillance ( exemple passage en couleur bleu).
Ces boutons auront évidement une certaine valeur que je leur aurai donné.
Malheureusement je n’arrive pas à cela malgré des heures à m’arracher les cheveux.
Quelqu’un pourrait-il m’aider, je lui serai sincèrement reconnaissant.
Je n’ai pas saisi complètement ce que tu désire faire.
Mais si je peux te donner un conseil, ne mélange pas UI et Logique métier.
Pour ton problème j’imagine que tu as des données (un tableau avec les elements périodiques et ces elements ont des attributs, masse moléculaire, etc…)
Et tu souhaites filtrer les résultats a partir d’un slider.
Il faut que tu récupères la valeur du slider. Que tu change le jeu de données (les variables) à afficher.
Et ensuite tu fais une méthode updateUI() qui va se charger UNIQUEMENT de mettre à jour l’interface utilisateur.
Pour faire simple, il ne faut pas faire un UIAction sur le slide qui effectue directement un button.text = slide.value
J’espère avoir pu aider un peu sinon n’hésite pas si tu as besoin de précisions.
Je ne comprend pas le besoin de boutons pour tes éléments. Un bouton ça sert à déclencher une action, pas à afficher une information. Un label ou une vue personnalisée avec une petite image (nom élément + symbole chimique + une image de mise en situation) ne serait pas plus approprié ?
J’ai 6 labels sur l’écran, représentant chacun un élément atomique. Et un Slider.
class ViewController: UIViewController {
// Outlets vers les Labels des éléments chimiques
@IBOutlet weak var hydrogene : UILabel!
@IBOutlet weak var sodium : UILabel!
@IBOutlet weak var fer : UILabel!
@IBOutlet weak var cobalt : UILabel!
@IBOutlet weak var cuivre : UILabel!
@IBOutlet weak var californium : UILabel!
@IBOutlet weak var slider: UISlider!
// Tableau pour mémoriser la liste des Labels/Elements
var listeLabels = [UILabel]()
// Mémorisation Label Actif
var numeroLabelCourant = 0
override func viewDidLoad() {
super.viewDidLoad()
// Initialisation de la liste des Labels
listeLabels = [hydrogene, sodium, fer, cobalt, cuivre, californium]
// Initialisation du Slider
// (cela peut se faire dans le Storyboard)
slider.minimumValue = 0
slider.maximumValue = 1.0
slider.value = 0
numeroLabelCourant = 0
actualisationInterface()
}
func actualisationInterface() {
// Remise à 0 de la couleur de tous les Labels
for label in listeLabels {
label.backgroundColor = UIColor.clear
}
// changement de la couleur du LabelCourant
let labelCourant = listeLabels[numeroLabelCourant]
labelCourant.backgroundColor = UIColor.red
}
// Calcul de l'objet pointé par le Slider
func calculObjetSlider() -> Int {
let nombreObjets = listeLabels.count
var objetSlider = 0
// Le slider est-il a son extrémité droite ?
if slider.value == 1.0 {
objetSlider = nombreObjets - 1
} else {
objetSlider = Int(slider.value*Float(nombreObjets))
}
return objetSlider
}
// Action Slider
@IBAction func actionSlider(_ sender: UISlider) {
let objetSlider = calculObjetSlider()
// Le slider pointe-t-il sur un nouveau Label ?
if objetSlider != numeroLabelCourant {
numeroLabelCourant = objetSlider
actualisationInterface()
}
}
}
L’astuce c’est de stocker les références des Labels dans un tableau :
// Initialisation de la liste des Labels
listeLabels = [hydrogene, sodium, fer, cobalt, cuivre, californium]
Quand le Slider change de position il suffit de calculer à quel élément il correspond. J’ai 6 éléments dans le tableau, ce qui correspond à 6 zones différentes du Slider.
Une fois la zone déterminée (numérotée de 0 à 5), il suffit de lire le tableau pour connaître l’objet graphique concerné.
J’ai utilisé des Labels, matérialisant l’élément chimique courant avec un fond de couleur rouge. Tu peux reprendre le même principe avec n’importe quel type d’objets graphiques (des boutons, des images, etc…).
Un grand merci à vous deux pour votre aide.
Merci beaucoup Draken pour cette démo, c’est vraiment sympa de ta part d’avoir pu me consacrer un peu de ton temps.
Excusez moi de vous déranger , tout d’abord merci pour votre aide.
Je me suis un précipité dans ma réponse et votre démonstration est vraiment top mais je pense que j’ai mal exprimé mon but :
Je vais reprendre votre ViewController car il expose exactement ce que je veux faire ( avec un peu plus d’éléments mais peu importe…) :
imaginons que l’hydrogène est la valeur 10
le sodium la valeur 20
le fer la valeur 30
le cobalt la valeur 40
le cuivre la valeur 50
le californium la valeur 60
Maintenant imaginons que je veuille sélectionner les éléments qui ont une valeur <= à 30, je met donc la valeur de mon slider sur 30 et la, les éléments qui ont une valeur <= 30 se mettent en surbrillance ou change couleur de fond.
C’est ce que je dis plus haut dans mon message.
Les labels/boutons ne sont pas la pour stocker des informations. Ils sont la pour afficher de l’information.
L’information doit être ailleurs dans le code, pas sur le label.
Alors que faut il utiliser pour mon slider sélectionne des éléments (qui ont une valeur).
Et une fois que mes élément sont en surbrillance si je clique sur l’un d’eux, que cela fasse apparaître une fiche descriptive par exemple? Par contre pas dans un autre ViewController mais dans le même ViewController.
J’ai une solution rapide à réaliser. C’est un peu de la magouille, mais ça marche. Comme le dis @gaveline les objets graphiques ne sont pas là pour stocker de l’information, mais pour en afficher (voir le paradigme MVC). Je vais cependant tricher un peu…
Tous les objets graphiques ont une propriété .tag permettant de leurs associer une valeur de type Int. On s’en sert généralement comme système d’identification. Il me semble que l’exercice sur la calculatrice l’utilise pour identifier la touche pressée.
Je m’en suis servis pour stocker la valeur atomique associée à chaque Label. Par exemple, avec Storyboard j’ai écrit la valeur 10 dans la propriété .tag du label Hydrogène.
La variable globale (au contrôleur) masseAtomiqueCourante me sert à construire l’affichage.
func actualisationInterface() {
// Remise à 0 de la couleur de tous les Labels
for label in listeLabels {
label.backgroundColor = UIColor.clear
}
// On boucle sur la liste des Labels pour comparer
// leurs masses atomatiques avec la masse atomique courante
for label in listeLabels {
let masseAtomique = label.tag
if masseAtomique <= masseAtomiqueCourante {
label.backgroundColor = UIColor.red
}
}
}
Je commence par remettre l’affichage dans son état initial, avec une boucle parcourant tous les Labels.
Puis j’effectue une seconde boucle pour comparer la masse atomique de chaque label avec la masseAtomique de référence du Slider. Si c’est plus petit, je change la couleur de fond en ROUGE …
class ViewController: UIViewController {
// Outlets vers les Labels des éléments chimiques
@IBOutlet weak var hydrogene : UILabel!
@IBOutlet weak var sodium : UILabel!
@IBOutlet weak var fer : UILabel!
@IBOutlet weak var cobalt : UILabel!
@IBOutlet weak var cuivre : UILabel!
@IBOutlet weak var californium : UILabel!
@IBOutlet weak var slider: UISlider!
// Tableau pour mémoriser la liste des Labels/Elements
var listeLabels = [UILabel]()
// Mémorisation masse atomique courante
var masseAtomiqueCourante = 0
override func viewDidLoad() {
super.viewDidLoad()
// Initialisation de la liste des Labels
listeLabels = [hydrogene, sodium, fer, cobalt, cuivre, californium]
// Initialisation du Slider
// (cela peut se faire dans le Storyboard)
slider.minimumValue = 0
slider.maximumValue = 103.0
slider.value = 0
actualisationInterface()
}
func actualisationInterface() {
// Remise à 0 de la couleur de tous les Labels
for label in listeLabels {
label.backgroundColor = UIColor.clear
}
// On boucle sur la liste des Labels pour comparer
// leurs masses atomatiques avec la masse atomique courante
for label in listeLabels {
let masseAtomique = label.tag
if masseAtomique <= masseAtomiqueCourante {
label.backgroundColor = UIColor.red
}
}
}
// Action Slider
@IBAction func actionSlider(_ sender: UISlider) {
let masseAtomiqueSlider = Int(slider.value)
if masseAtomiqueSlider != masseAtomiqueCourante {
masseAtomiqueCourante = masseAtomiqueSlider
actualisationInterface()
}
}
}
C’est encore plus simple que l’exemple précédent.
Techniquement, ce n’est pas très propre. Un objet graphique ne devrais pas contenir une information nécessaire à l’exécution de l’application. Je ferais un autre exemple plus orthodoxe demain.
J’ai réalisé une version différente, basée sur une DataSource.
class ElementChimique {
var nom : String
var masseAtomique = 0
var description : String
init (_ nom:String, _ masseAtomique:Int, _ description:String) {
self.nom = nom
self.masseAtomique = masseAtomique
self.description = description
}
}
// Description des corps chimiques
// DataSource de l'Application
fileprivate let dataSourcesElements = [
ElementChimique("Hydrogène", 10, "bla bla"),
ElementChimique("Sodium", 20, "bla bla"),
ElementChimique("Fer", 30, "bla bla"),
ElementChimique("Cobalt", 40, "bla bla"),
ElementChimique("Cuivre", 50, "bla bla"),
ElementChimique("Californium", 60, "bla bla")
]
Cette DataSource contient la description complète des éléments chimiques. Il n’y a pas d’informations stockées ici et là dans le projet. Tout se trouve dans la dataSource, que l’on appelle aussi Modèle de données. C’est le fameux M du paradigme MVC dont on parle beaucoup en développement iOS.
Un élément est défini par son nom, sa masse atomique et une description (que je n’utilise pas, c’est juste pour montrer le principe).
Voici le code du viewController :
class ViewController: UIViewController {
// Outlets vers les UILabels
@IBOutlet weak var labelElement00: UILabel!
@IBOutlet weak var labelElement01: UILabel!
@IBOutlet weak var labelElement02: UILabel!
@IBOutlet weak var labelElement03: UILabel!
@IBOutlet weak var labelElement04: UILabel!
@IBOutlet weak var labelElement05: UILabel!
@IBOutlet weak var slider: UISlider!
// Tableau pour mémoriser la liste des Labels/Elements
var listeLabels = [UILabel]()
// Mémorisation masse atomique courante
var masseAtomiqueCourante = 0
// Initialisation du ViewControleur
override func viewDidLoad() {
super.viewDidLoad()
// Initialisation de la liste des Labels
listeLabels = [
labelElement00,
labelElement01,
labelElement02,
labelElement03,
labelElement04,
labelElement05]
// Initialisation du Slider
// (cela peut se faire dans le Storyboard)
slider.minimumValue = 0
slider.maximumValue = 103.0
slider.value = 0
actualisationInterface()
}
func actualisationInterface() {
// Enumération des Labels pour remplir les contenus
// et remise à 0 de la couleur
for (index, label) in listeLabels.enumerated() {
// Récupération de l'élément Chimique correspondant au Label
// à partir de la source de données
let element = dataSourcesElements[index]
let nomElement = element.nom
// Remplissage du Label
label.text = nomElement
// On force le label à s'ajuster à la taille du texte
label.sizeToFit()
label.backgroundColor = UIColor.clear
}
// Enumération sur la liste des Labels
// pour récupérer les masses atomiques des éléments
for (index, label) in listeLabels.enumerated() {
let element = dataSourcesElements[index]
if element.masseAtomique <= masseAtomiqueCourante {
label.backgroundColor = UIColor.red
}
}
}
// Action Slider
@IBAction func actionSlider(_ sender: UISlider) {
let masseAtomiqueSlider = Int(slider.value)
if masseAtomiqueSlider != masseAtomiqueCourante {
masseAtomiqueCourante = masseAtomiqueSlider
actualisationInterface()
}
}
}
Il y a une totale indépendance entre la dataSource et l’interface graphique. Tu peux faire évoluer les deux indépendamment. C’est le code du viewController qui fait le lien entre les deux.
Pour qu’un élément graphique (vue, label, image, etc…), soit sensible à un événement tactile, il faut passer par Storyboard pour cocher sa propriété User Interaction Enabled.
C’est ce que j’ai fait avec les 6 labels de ma démo.
J’ai ensuite déposé un détecteur de Gesture sur chaque label.
6 détecteurs de gestures, c’est 6 fonctions d’actions dans le code, enfin normalement. J’ai « rusé » en créant une action pour la première gesture, puis en tirant le trait des 5 autres vers le même code.
// Action Gesture sur un Label
@IBAction func actionSelectionElement(_ sender: UITapGestureRecognizer) {
if let tag = sender.view?.tag {
// Identification de l'élement à partir du Tag
let element = dataSourcesElements[tag]
print (element.nom)
}
}
L’astuce pour identifier le label sélectionné, c’est de passer par le .tag. J’ai donné à chaque label un tag allant de 0 à 5, comme dans l’un de mes exemples précédents. Là, il ne s’agit pas de lire une masse atomique, mais d’identifier le composant graphique sélectionné. Si tu crée une table périodique complète, avec les 103 éléments connus, tu apprécieras d’avoir une seule méthode d’action au lieu de 103 différentes … On appelle cela la « factorisation » du code.
Le tag est récupéré par l’intermédiaire du Sender. C’est un lien vers l’objet ayant provoqué l’action, c’est à dire le Détecteur de Gesture. Pour connaître l’objet graphique contenant le .tag, il faut accéder à la propriété .view de la gesture.
Petit problème technique : cette propriété est une variable optionnelle. Pour je-ne-sais-quelle-raison, iOS semble penser qu’une gesture fantôme peut provoquer un événement tactile sans être associé à une surface tactile.
Cela nous oblige à tester la validité de l’optionnelle avant de lire le tag. Le plus simple est de passer par la syntaxe if let, comme ceci :
if let tag = sender.view?.tag {
// Identification de l'élement à partir du Tag
let element = dataSourcesElements[tag]
print (element.nom)
}
Je ne fais pas grande chose avec la détection du label, juste identifier l’élément et afficher son nom dans le terminal d’Xcode. A toi de reprendre le principe pour aller plus loin…