Accéder au contenu principal

UIMenuBuilder avec MacCatalyst

Lorsque l'on porte une application IOS sur MacCatalyst, se pose la question du menu.
Ici je voulais ajouter un menu "Open" à mon application, mais comment faire ?

J'ai mis mon code entre "#if TARGET_OS_MACCATALYST" car ce code fonctionne aussi avec IOS el la notion de UIMenuBuilder n'apparait que dans IOS13, or je développe mes application pour IOS9 car beaucoup d'utilisateurs ont des vieux appareils...

#if TARGET_OS_MACCATALYST

-(void)buildMenuWithBuilder:(id)builder {

    NSLog(@"buildMenuWithBuilder !!");
    [super buildMenuWithBuilder:builder];

    if(builder.system == UIMenuSystem.mainSystem) {

        [builder removeMenuForIdentifier:UIMenuFormat]; 
        // Le menu "Format" ne m'interresse pas

        // On crée la commande... 
        //L'action doit être une fonction déclarée dans le FirstResponder
        UIKeyCommand *command_O = [UIKeyCommand 
                 commandWithTitle:@"Import"
                            image:nil                                                       
                           action:NSSelectorFromString(@"importfile:")
                            input:@"O"
                    modifierFlags:UIKeyModifierCommand
                     propertyList:nil];

        // On crée l'entrée du menu à partir de la commande
        UIMenu *menu = [UIMenu 
                    menuWithTitle:@""              // Inutilisé ici
                            image:nil              // Utile que sous IOS
                       identifier:@"UTI unique"    // Un nom unique
                          options:UIMenuOptionsDisplayInline
                         children:@[ command_O ]]; // Une seule commande

        // On insert l'entrée de menu 
        // en première position dans le menu Fichier.
        [builder insertChildMenu:menu atStartOfMenuForIdentifier:UIMenuFile];

    }
}


#endif

Donc la fonction buildMenuWithBuilder: doit être ajouté dans le AppDelegate.m et contenir :
- L'appel à [super BuildMenuWithBuilder:builder]; pour que l'app crée bien le menu.
- Un test pour vérifier que l'on parle bien du menu système, et si ce n'est pas le cas ne rien faire.
- Vous pouvez supprimer des menu que vous ne voulez pas avec [builder removeMenuForIdentifier:], les menus standards ont évidemment leur constante de définit (comme ici UIMenuFormat).
- Pour chaque entrée du menu :
  - On crée le UIKeyCommand qui définit quelle action va être effectuée.
  - On crée le UIMenu avec UIMenuOptionDisplayInline pour une entrée simple (pas de sous menu).
  - On insère le menu au début ou à la fin d'un menu déjà présent.

Voilà, le menu "Open" apparait... Mais petite remarque sur l'action de la UIKeyCommand. Vous aurez peut-être remarqué qu'il n'y a pas de Target et que j'ai utilisé NSSelectorFromString() ) la place de @selector().

En fait l'action doit être faite par le FirstResponder. C'est pourquoi on ne peut pas utilise @selector() qui est une macro du précompilateur. La fonction pointée par action n'existe pas ici dans le AppDelegate, je l'ai mise dans la classe de mon premier ViewController.  Il faut donc utiliser NSSelectorFromString() pour éviter un Warning, car la fonction n'est pas visible par le compilateur.

Ce système est très intéressant car si la fonction n'est pas définie dans le FirstResponder, l'entrée dans le menu est grisée automatiquement. 

Donc cas 1 : 

Imaginons que vous ayez un premier ViewController qui répond à l'action open, le menu apparait actif, même si l'on passe dans un autre ViewController, l'action reste active car le FirstResponder est capable de répondre à tous les sélecteurs définis dans sa classe, mais aussi à tous les sélecteurs définis dans le ViewController parents.

Et cas 2 :

Si nous définissons notre action dans un ViewController qui n'est pas le root, ce n'est que quand notre ViewController sera actif que l'entrée de menu sera active, et elle restera active même si le ViewController présente de nouveaux ViewController. Et évidemment elle redeviendra inactive si l'on quitte le ViewController.




Commentaires

Posts les plus consultés de ce blog

UISwitch dans un UITableViewCell

Je construis des formulaire dans des UITable, chaque UITableViewCell peut donc contenir des UILabel, UISwitch, UIButton, etc. Je partage le code entre plusieurs application, mais j'ai eu un bug dans l'une d'elles que je n'avais pas dans les autres. Les gadgets interactifs ne fonctionnaient pas... En fait, j'avais construit mes UITableViewCell avec du code comme celui-ci : -( instancetype ) initWithStyle :( UITableViewCellStyle )style reuseIdentifier :( NSString *)reuseIdentifier {     self = [ super initWithStyle :style reuseIdentifier :reuseIdentifier];     self . backgroundColor = [ UIColor clearColor ];     _uiLabel = [ UILabel new ];     _uiLabel . frame = CGRectMake ( 8 , 4 , self . frame . size . width - 51 - 24 , self . frame . size . height - 8 );     _uiLabel . translatesAutoresizingMaskIntoConstraints = NO ;     _uiLabel . textAlignment = NSTextAlignmentLeft ;     _uiLabel . lineBreakMode = NSLineBreakByWordWrapping ;     _uiLabel . adjustsFontS

Tiret insécable

 Ecrivant un livre sur CoreAnimation en Objective-C et en Swift, j'écris souvent "Objective-C".  Mais je ne souhaite pas que la césure du texte se face avec le C, il me faut donc un tiret insécable. Malheureusement il n'y a pas de tiret insécable sur le clavier du Mac, il faut donc utiliser le caractère Unicode U+2011. Mais le clavier du Mac ne comprends pas non plus par défaut de possibilité d'ajouter un caractère par son code Unicode.  Il faut donc aller dans les réglages d'Apple, dans les réglages du clavier, dans les méthodes de saisie et ajouter le clavier "Universel (Unicode Hex)". Ensuite, lorsque l'on a besoin d'un caractère Unicode, on change de clavier et on utilise la touche Option + le code en hexadécimal. Pour accélérer le changement d'un clavier à l'autre, toujours dans les réglages du clavier, vous pouvez activer les raccourcis clavier. Bon, c'était encore trop long, alors j'ai mis un remplacement automatique da

UISceneSession is only available on iOS 13

Lorsque vous faites une nouvelle application, celle-ci est par défaut pour le dernier IOS.  Mais si vos clients, comme les miens, ont de vieux appareils, vous pourriez vouloir que votre application fonctionne avec IOS9 (pour le moment on peut encore faire des apps IOS9, mais je crois que la limite sera IOS12 dans pas longtemps). Dans les infos de déploiement, vous changez donc le IOS 14  en IOS 9. Mais, le lancement de l'app vous donne un écran noir ! Pour régler le problème, il faut ajouter une propriété "window" dans l'interface du appDelegate. @interface AppDelegate : UIResponder < UIApplicationDelegate > @property ( strong , nonatomic ) UIWindow * window ; @end Reste les warnings... Le plus simple est de faire confiance à Xcode, tapez sur les warnings et un menu vous proposera un "fix" qui ajoutera un API_AVAILABLE à la définition de la fonction. - ( void ) scene :( UIScene *)scene willConnectToSession :( UISceneSession *)session options :(