web-dev-qa-db-fra.com

Comment ajouter une sous-vue qui a son propre UIViewController dans Objective-C?

Je me bats avec des sous-vues qui ont leur propre UIViewControllers. J'ai un UIViewController avec une vue (rose clair) et deux boutons sur un toolbar. Je veux que la vue bleue s'affiche lorsque le premier bouton est enfoncé et que la vue jaune s'affiche avec le deuxième bouton. Cela devrait être facile si je voulais simplement afficher une vue. Mais la vue bleue contiendra une table, elle a donc besoin de son propre contrôleur. C'était ma première leçon. J'ai commencé avec cette SO question où j'ai appris que j'avais besoin d'un contrôleur pour la table.

Donc, je vais reculer et faire quelques pas ici. Ci-dessous est une image d'un point de départ simple avec mon utilitaire ViewController (le contrôleur de vue principal) et les deux autres contrôleurs (bleu et jaune). Imaginez que lorsque l'utilitaire ViewController (la vue principale) est affiché pour la première fois, la vue bleue (par défaut) sera affichée là où se trouve la vue rose. Les utilisateurs pourront cliquer sur les deux boutons pour aller et venir et la vue rose ne sera JAMAIS affichée. Je veux juste que la vue bleue aille là où la vue rose est et que la vue jaune va où la vue rose est. J'espère que cela a du sens.

Simple Storyboard image

J'essaie d'utiliser addChildViewController. D'après ce que j'ai vu, il y a deux façons de procéder: La vue conteneur dans storyboard ou addChildViewController par programme. Je veux le faire par programme. Je ne veux pas utiliser un NavigationController ou une barre d'onglets. Je veux juste ajouter les contrôleurs et pousser la vue correcte dans la vue rose lorsque le bouton associé est enfoncé.

Voici le code que j'ai jusqu'à présent. Tout ce que je veux faire, c'est afficher la vue bleue là où se trouve la vue rose. D'après ce que j'ai vu, je devrais pouvoir simplement addChildViewController et ajouterSubView. Ce code ne fait pas ça pour moi. Ma confusion prend le dessus sur moi. Quelqu'un peut-il m'aider à afficher la vue bleue là où se trouve la vue rose?

Ce code n'est pas destiné à faire autre chose que d'afficher la vue bleue dans viewDidLoad.

IDUtilityViewController.h

#import <UIKit/UIKit.h>

@interface IDUtilityViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *utilityView;
@end

IDUtilityViewController.m

#import "IDUtilityViewController.h"
#import "IDAboutViewController.h"

@interface IDUtilityViewController ()
@property (nonatomic, strong) IDAboutViewController *aboutVC;
@end

@implementation IDUtilityViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.aboutVC = [[IDAboutViewController alloc]initWithNibName:@"AboutVC" bundle:nil];
    [self addChildViewController:self.aboutVC];
    [self.aboutVC didMoveToParentViewController:self];
    [self.utilityView addSubview:self.aboutVC.aboutView];
}

@end

-------------------------- EDIT -------------- ----------------

Self.aboutVC.aboutView est nul. Mais je l'ai câblé dans le storyboard. Dois-je encore l'instancier?

enter image description here

52
Patricia

Ce message qui date des premiers jours de l'iOS moderne, a généralement la dernière syntaxe, telle que Swift 4 actuellement. Si vous commencez avec iOS, la mise en page automatique, etc., cela vous permettra de continuer.

Dans iOS aujourd'hui "tout est une vue de conteneur" . C'est la façon de base de créer des applications aujourd'hui.

Une application peut être si simple qu'elle n'a qu'une seule vue. Mais même dans ce cas, chaque "chose" à l'écran est une vue de conteneur.

C'est aussi simple que ça ...


(A) Faites glisser une vue de conteneur vers votre scène ...

Faites glisser une vue de conteneur dans votre vue de scène. (Tout comme vous traîneriez, disons, un UIButton.)

La vue du conteneur est la chose brune sur cette image. Il s'agit en fait de votre vue de scène .

enter image description here

Lorsque vous faites glisser une vue de conteneur dans votre vue de scène, Xcode vous donne automatiquement deux choses :

  1. Vous obtenez la vue conteneur à l'intérieur de votre vue de scène , et,

  2. vous obtenez un tout nouveau UIViewController qui est juste assis quelque part sur le blanc de votre storyboard .

Les deux sont connectés avec la chose "Symbole maçonnique" - expliqué ci-dessous!


(B) Cliquez sur ce nouveau contrôleur de vue (la nouvelle chose que Xcode a faite pour vous quelque part sur la zone blanche, pas la chose à l'intérieur de votre scène ) ... et changez la classe !

C'est vraiment aussi simple que cela.

Vous avez terminé.


Voici la même chose expliquée visuellement.

Remarquez la vue du conteneur à (A).

Remarquez le contrôleur à (B).

shows a container view and the associated view controller

Cliquez sur B. (C'est B - pas A!)

Allez à l'inspecteur en haut à droite. Notez qu'il dit "UIViewController"

[enter image description here] [3]

Modifiez-le en votre propre classe personnalisée, qui est un UIViewController.

enter image description here

Donc, j'ai une classe Swift Snap qui est un UIViewController.

enter image description here

Alors là où il est écrit "UIViewController" dans l'inspecteur, j'ai tapé "Snap".

(Comme d'habitude, Xcode complétera automatiquement "Snap" lorsque vous commencerez à taper "Snap ...".)

C'est tout ce qu'il y a à faire - vous avez terminé.


Comment changer la vue du conteneur - par exemple, une vue de table.

Ainsi, lorsque vous cliquez pour ajouter une vue de conteneur, Apple vous donne automatiquement un contrôleur de vue lié, assis sur le storyboard.

En fait (2017): cela en fait un UIViewController par défaut.

C'est idiot: il devrait demander de quel type vous avez besoin. Par exemple, vous avez souvent besoin d'une vue tabulaire. Voici comment le changer en quelque chose de différent:

Au moment de l'écriture, Xcode vous donne un UIViewController par défaut. Supposons que vous souhaitiez un UICollectionViewController à la place:

(i) Faites glisser une vue de conteneur vers votre scène. Regardez le UIViewController sur le storyboard que Xcode vous donne par défaut.

(ii) Faites glisser un nouveau UICollectionViewController n'importe où sur la zone blanche principale du storyboard.

(iii) Cliquez sur la vue conteneur à l'intérieur de votre scène. Cliquez sur l'inspecteur de connexions. Remarquez qu'il y a une "séquence déclenchée". Passez la souris sur le "Triggered Segue" et notez que Xcode met en surbrillance tous l'UIViewController indésirable.

(iv) Cliquez sur le "x" pour réellement supprimer ce déclenchement déclenché.

(v) Faites glisser depuis ce déclencheur déclenché (viewDidLoad est le seul choix). Faites glisser le storyboard vers votre nouveau UICollectionViewController. Lâchez prise et une fenêtre contextuelle apparaît. Vous devez sélectionner embed .

(vi) Simplement supprimer tous les UIViewController indésirables. Vous avez terminé.

Version courte: supprimez le UIViewController indésirable. Mettez un nouveau UICollectionViewController sur le storyboard. Faites glisser la souris depuis: les connexions de la vue du conteneur - Trigger Segue - viewDidLoad vers votre nouveau contrôleur. Assurez-vous de sélectionner "incorporer" dans la fenêtre contextuelle.


Saisie de l'identifiant de texte ...

Vous aurez l'un de ces éléments "carré dans un carré" symbole maçonnique: il est sur la "ligne flexible" reliant votre vue de conteneur avec le contrôleur de vue .

La chose "symbole maçonnique" est la séquence.

enter image description here

Sélectionnez la séquence en cliquant sur la chose "symbole maçonnique".

Regardez à votre droite.

Vous [~ # ~] devez [~ # ~] saisir un identificateur de texte pour la séquence.

Vous décidez du nom. Il peut s'agir de n'importe quelle chaîne de texte. Un choix judicieux est souvent "segueClassName".

Si vous suivez ce modèle, toutes vos séquences seront appelées segueClockView, seguePersonSelector, segueSnap, segueCards, etc.

Ensuite, où utilisez-vous cet identifiant de texte?


Comment se connecter "à" le contrôleur enfant ...

Ensuite, procédez comme suit, dans le code, dans le ViewController de la scène entière.

Supposons que vous ayez trois vues de conteneur dans la scène. Chaque vue de conteneur contient un contrôleur différent, par exemple "Snap", "Clock" et "Other".

Dernière syntaxe Swift3 (2017)

var snap:Snap?
var clock:Clock?
var other:Other?

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if (segue.identifier == "segueSnap")
            { snap = (segue.destination as! Snap) }
    if (segue.identifier == "segueClock")
            { clock = (segue.destination as! Clock) }
    if (segue.identifier == "segueOther")
            { other = (segue.destination as! Other) }
}

C'est si simple. Vous connectez une variable pour faire référence aux contrôleurs, en utilisant l'appel prepareForSegue.


Comment se connecter dans "l'autre sens", jusqu'au parent ...

Disons que vous êtes "dans" le contrôleur que vous avez mis dans une vue conteneur ("Snap" dans l'exemple).

Il peut être déroutant d'accéder au contrôleur de vue "boss" au-dessus de vous ("Dash" dans l'exemple). Heureusement, c'est aussi simple que cela:

// Dash is the overall scene.
// Here we are in Snap. Snap is one of the container views inside Dash.

class Snap {

var myBoss:Dash?    
override func viewDidAppear(_ animated: Bool) { // MUST be viewDidAppear
    super.viewDidAppear(animated)
    myBoss = self.parent as? Dash
}

Critique: Fonctionne uniquement à partir de viewDidAppear ou version ultérieure. Ne fonctionnera pas dans viewDidLoad.

Vous avez terminé.


Important: que seulement fonctionne pour les vues de conteneur.

Astuce avancée importante: N'oubliez pas, cela ne fonctionne que pour les vues de conteneurs.

De nos jours avec les identificateurs de storyboard, il est courant de simplement afficher de nouvelles vues à l'écran (plutôt que dans Android). Donc, disons que l'utilisateur veut modifier quelque chose ...

    // let's just pop a view on the screen.
    // this has nothing to do with container views
    //
    let e = ...instantiateViewController(withIdentifier: "Edit") as! Edit
    e.modalPresentationStyle = .overCurrentContext
    self.present(e, animated: false, completion: nil)

Lorsque vous utilisez une vue de conteneur, IT IS GARANTI que Dash sera le contrôleur de vue parent de Snap.

Cependant c'est PAS NÉCESSAIREMENT LE CAS lorsque vous utilisez instantiateViewController.

Très confusément, dans iOS, le contrôleur de vue parent n'est pas lié à la classe qui l'a instancié. (Il pourrait être le même, mais ce n'est généralement pas le même.) Le self.parent le modèle est uniquement pour les vues de conteneur.

(Pour un résultat similaire dans le modèle instantiateViewController, vous devez utiliser un protocole et un délégué, en vous rappelant que le délégué sera un maillon faible.)


prepareForSegue mal nommé ...

Il est à noter que "prepareForSegue" est un très mauvais nom!

"prepareForSegue" est utilisé à deux fins: le chargement des vues de conteneur et la transition entre les scènes.

Mais en pratique, vous faites très rarement la transition entre les scènes! Alors que presque toutes les applications ont de nombreuses vues de conteneurs, bien sûr.

Cela aurait beaucoup plus de sens si "prepareForSegue" était appelé quelque chose comme "loadingContainerView".


Plus d'un...

Une situation courante est la suivante: vous avez une petite zone sur l'écran, où vous souhaitez afficher l'un des différents contrôleurs de vue. Par exemple, l'un des quatre widgets.

La façon la plus simple de le faire: il suffit d'avoir quatre vues de conteneurs différentes toutes assis dans la même zone identique . Dans votre code, masquez simplement les quatre et activez celui que vous souhaitez voir.

Sur le storyboard, un UIView "titulaire" vide, qui contient simplement les quatre vues de conteneur. Vous pouvez ensuite dimensionner ou déplacer les quatre à la fois en dimensionnant ou en déplaçant le "support". Dans votre code, il vous suffit de disposer de quatre prises UIView, une pour chacune des vues de conteneur. Copiez et collez le code ci-dessus, "Comment se connecter au contrôleur enfant", pour connecter les quatre contrôleurs de vue contenus.


Remarque - Les références du storyboard arrivent!

Comme le souligne SimplGy ci-dessous "iOS 9 Références du storyboard rendre les vues de conteneurs encore plus impressionnantes. Vous pouvez définir votre vue réutilisable (contrôleur) où vous le souhaitez et la référencer à partir de n'importe quelle vue de conteneur dans plusieurs story-boards modulaires. "

Notez également que - plutôt confus - souvent aujourd'hui, vous ne vous embêtez pas avec les vues de conteneur!

Vous simplement instantiateViewController#withIdentifier dans de nombreuses situations.

Mais notez le "gotchya" à propos de .parent expliqué ci-dessus. Le point de vue des conteneurs est que vous êtes instantanément et simplement assuré de la chaîne parent.

Si vous allez avec instantiateViewController#withIdentifier en utilisant une référence de storyboard, vous devez jouer avec un protocole et un délégué (en vous rappelant que le délégué sera un maillon faible). Mais vous pouvez ensuite l'utiliser "à la volée" partout avec souplesse.

En revanche, en utilisant un "fixe", pour ainsi dire, la vue du conteneur est extrêmement simple, et vous vous connectez instantanément entre le parent et l'enfant comme expliqué ci-dessus.

131
Fattie

Je vois deux problèmes. Tout d'abord, puisque vous créez les contrôleurs dans le storyboard, vous devez les instancier avec instantiateViewControllerWithIdentifier:, ne pas initWithNibName:bundle:. Deuxièmement, lorsque vous ajoutez la vue en tant que sous-vue, vous devez lui donner un cadre. Alors,

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.aboutVC = [self.storyboard instantiateViewControllerWithIdentifier:@"aboutVC"]; // make sure you give the controller this same identifier in the storyboard
    [self addChildViewController:self.aboutVC];
    [self.aboutVC didMoveToParentViewController:self];
    self.aboutVC.view.frame = self.utilityView.bounds;
    [self.utilityView addSubview:self.aboutVC.aboutView];
}
5
rdelmar