Comment détecter si une application est en cours de création pour un appareil ou un simulateur dans Swift

277

Dans Objective-C, nous pouvons savoir si une application est en cours de création pour un appareil ou un simulateur à l'aide de macros:

#if TARGET_IPHONE_SIMULATOR
    // Simulator
#else
    // Device
#endif

Ce sont des macros de compilation et non disponibles au moment de l'exécution.

Comment puis-je réaliser la même chose dans Swift?

RaffAl
la source
2
Ce n'est pas ainsi que vous pouvez détecter le simulateur ou un véritable appareil au moment de l'exécution dans Objective-C. Ce sont des directives de compilation qui entraînent un code différent en fonction de la construction.
rmaddy
Merci. J'ai édité ma question.
RaffAl
9
LES RÉPONSES LES PLUS ÉLEVÉES NE SONT PAS LA MEILLEURE FAÇON DE RÉSOUDRE CE PROBLÈME! La réponse de mbelsky (actuellement très loin) est la seule solution sans écueils. Même Greg Parker d'Apple a suggéré de le faire de cette façon: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
MÊME EN CASQUETTES, IL EST NATUREL DE SUGGÉRER QU'IL N'Y A rien de mal avec un chèque de durée. Les suggestions des ingénieurs d'Apple sont souvent des ordures mal pensées ou ne s'appliquent que dans certaines situations, de sorte que soi-même signifie moins que rien.
Fattie
1
@Fattie: Il serait intéressant de savoir pourquoi aucune des réponses données ne répond à vos besoins, et ce que vous espérez exactement en offrant la prime.
Martin R

Réponses:

364

Mise à jour 30/01/19

Bien que cette réponse puisse fonctionner, la solution recommandée pour une vérification statique (telle que clarifiée par plusieurs ingénieurs Apple) consiste à définir un indicateur de compilateur personnalisé ciblant les simulateurs iOS. Pour des instructions détaillées sur la façon de procéder, voir la réponse de @ mbelsky .

Réponse originale

Si vous avez besoin d'une vérification statique (par exemple pas un runtime si / sinon), vous ne pouvez pas détecter directement le simulateur, mais vous pouvez détecter iOS sur une architecture de bureau comme suit

#if (arch(i386) || arch(x86_64)) && os(iOS)
    ...
#endif

Après la version Swift 4.1

Dernière utilisation, maintenant directement pour tous dans une condition pour tous les types de simulateurs doivent appliquer une seule condition -

#if targetEnvironment(simulator)
  // your simulator code
#else
  // your real device code
#endif

Pour plus de précisions, vous pouvez consulter la proposition Swift SE-0190


Pour l'ancienne version -

De toute évidence, cela est faux sur un appareil, mais cela renvoie vrai pour le simulateur iOS, comme spécifié dans la documentation :

La configuration de construction arch (i386) renvoie true lorsque le code est compilé pour le simulateur iOS 32 bits.

Si vous développez pour un simulateur autre que iOS, vous pouvez simplement faire varier le osparamètre: par exemple

Détectez le simulateur watchOS

#if (arch(i386) || arch(x86_64)) && os(watchOS)
...
#endif

Détecter le simulateur tvOS

#if (arch(i386) || arch(x86_64)) && os(tvOS)
...
#endif

Ou même détecter n'importe quel simulateur

#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(watchOS) || os(tvOS))
...
#endif

Si vous êtes plutôt d'accord avec une vérification de l'exécution, vous pouvez inspecter la TARGET_OS_SIMULATORvariable (ou TARGET_IPHONE_SIMULATORdans iOS 8 et ci-dessous), ce qui est vrai sur un simulateur.

Veuillez noter que cela est différent et légèrement plus limité que l'utilisation d'un indicateur de préprocesseur. Par exemple, vous ne pourrez pas l'utiliser à un endroit où a if/elseest syntaxiquement invalide (par exemple en dehors des étendues de fonctions).

Supposons, par exemple, que vous souhaitiez avoir des importations différentes sur l'appareil et sur le simulateur. C'est impossible avec une vérification dynamique, alors que c'est trivial avec une vérification statique.

#if (arch(i386) || arch(x86_64)) && os(iOS)
  import Foo
#else
  import Bar
#endif

De plus, comme l'indicateur est remplacé par a 0ou a 1par le préprocesseur rapide, si vous l'utilisez directement dans une if/elseexpression, le compilateur émet un avertissement concernant le code inaccessible.

Pour contourner cet avertissement, consultez l'une des autres réponses.

Gabriele Petronella
la source
1
Plus de lecture ici . Et pour être encore plus restrictif, vous pouvez utiliser arch(i386) && os(iOS).
ahruss
1
Cela n'a pas fonctionné pour moi. J'ai dû vérifier à la fois pour i386 et x86_64
akaru
3
CETTE RÉPONSE N'EST PAS LA MEILLEURE FAÇON DE RÉSOUDRE CE PROBLÈME! La réponse de mbelsky (actuellement très loin) est la seule solution sans écueils. Même Greg Parker d'Apple a suggéré de le faire de cette façon: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@russbishop cela s'est avéré être un conseil utile à des centaines de personnes jusqu'à présent, compensant une API manquante. Au lieu de détourner la réponse en signant un commentaire en haut, il suffit de communiquer. J'ai mis à jour la réponse pour clarifier que ce n'est plus une solution à jour et j'ai fourni un lien vers celle qui semble plus correcte.
Gabriele Petronella
9
Dans Swift 4.1, vous pourrez dire #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/proposals/… )
Hamish
172

MIS À JOUR POUR SWIFT 4.1. Utilisez #if targetEnvironment(simulator)plutôt. La source

Pour détecter le simulateur dans Swift, vous pouvez utiliser la configuration de build:

  • Définissez cette configuration -D IOS_SIMULATOR dans Swift Compiler - Indicateurs personnalisés> Autres indicateurs Swift
  • Sélectionnez N'importe quel SDK iOS Simulator dans cette liste déroulanteLa liste déroulante

Vous pouvez maintenant utiliser cette instruction pour détecter le simulateur:

#if IOS_SIMULATOR
    print("It's an iOS Simulator")
#else
    print("It's a device")
#endif

Vous pouvez également étendre la classe UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator
mbelsky
la source
8
Cela devrait être la meilleure réponse! Même Greg Parker d'Apple a suggéré de cette façon: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
mise à jour d'utilisation pour swift 3: UIDevice.current.isSimulator
tylernol
1
Puis-je demander pourquoi si j'ajoute ceci sous Libération, cela ne fonctionne pas?
William Hu
3
Ceci est la seule bonne réponse. Vous pouvez également configurer cela dans des xcconfigfichiers en utilisant OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDet OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORpour remplacer le simulateur.
russbishop
1
Sur Xcode 9.2, cette réponse échouait à compiler une partie du temps. La suppression du "-" avant le "D" a résolu le problème pour moi.
Blake
160

Informations mises à jour au 20 février 2018

Il semble que @russbishop ait une réponse faisant autorité qui rend cette réponse "incorrecte" - même si cela a semblé fonctionner pendant longtemps.

Détecter si l'application est en cours de création pour un appareil ou un simulateur dans Swift

Réponse précédente

Sur la base de la réponse de @ WZW et des commentaires de @ Pang, j'ai créé une structure utilitaire simple. Cette solution évite les avertissements produits par la réponse de @ WZW.

import Foundation

struct Platform {

    static var isSimulator: Bool {
        return TARGET_OS_SIMULATOR != 0
    }

}

Exemple d'utilisation:

if Platform.isSimulator {
    print("Running on Simulator")
}
Daniel
la source
10
Meilleure solution que celle acceptée. En effet, si un jour (même si c'est très peu probable) Apple décide d'utiliser i386 ou x85_64 sur les appareils iOS, la réponse acceptée ne fonctionnera pas… ou même si les ordinateurs de bureau obtiennent un nouveau proc!
Frizlab
2
Confirmé que cela fonctionne parfaitement sur Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... même chose, simplifié. +1 remerciements
Dan Rosenstark
1
@daniel Cela fonctionne bien et c'est en fait plus simple que ma solution. Cependant, il convient de noter qu'elle est plus limitée qu'une étape de préprocesseur réelle. Si vous avez besoin qu'une partie du code ne soit pas incluse dans la cible (par exemple, vous voulez choisir entre deux importations au moment de la compilation), vous devez utiliser une vérification statique. J'ai édité ma réponse pour souligner cette différence.
Gabriele Petronella
CETTE RÉPONSE N'EST PAS LA MEILLEURE FAÇON DE RÉSOUDRE CE PROBLÈME! La réponse de mbelsky (actuellement très loin) est la seule solution sans écueils. Même Greg Parker d'Apple a suggéré de le faire de cette façon: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@Fattie TARGET_OS_SIMULATOR != 0est déjà dans la réponse . C'est la solution donnée par Daniel. Il n'est pas nécessaire de l'ajouter à nouveau dans une variable libre, il est déjà là. Si vous pensez que l'avoir dans une structure est mauvais et que l'avoir dans une variable libre est préférable, postez un commentaire à ce sujet ou faites votre propre réponse. Merci.
Eric Aya
69

Depuis Xcode 9.3

#if targetEnvironment(simulator)

Swift prend en charge une nouvelle condition de plate-forme targetEnvironment avec un seul simulateur d'argument valide. La compilation conditionnelle du formulaire '#if targetEnvironment (simulator)' peut maintenant être utilisée pour détecter quand la cible de build est un simulateur. Le compilateur Swift tentera de détecter, d'avertir et de suggérer l'utilisation de targetEnvironment (simulator) lors de l'évaluation indirecte des conditions de plate-forme qui semblent tester les environnements de simulateur, via les conditions de plate-forme os () et arch () existantes. (SE-0190)

iOS 9+:

extension UIDevice {
    static var isSimulator: Bool {
        return NSProcessInfo.processInfo().environment["SIMULATOR_DEVICE_NAME"] != nil
    }
}

Swift 3:

extension UIDevice {
    static var isSimulator: Bool {
        return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
    }
}

Avant iOS 9:

extension UIDevice {
    static var isSimulator: Bool {
        return UIDevice.currentDevice().model == "iPhone Simulator"
    }
}

Objectif c:

@interface UIDevice (Additions)
- (BOOL)isSimulator;
@end

@implementation UIDevice (Additions)

- (BOOL)isSimulator {
    if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
        return [NSProcessInfo processInfo].environment[@"SIMULATOR_DEVICE_NAME"] != nil;
    } else {
        return [[self model] isEqualToString:@"iPhone Simulator"];
    }
}

@end
HotJard
la source
2
La comparaison de chaînes est plus fragile que l'utilisation de constantes définies.
Michael Peterson
@ P1X3L5 vous avez raison! Mais je suppose que cette méthode est appelée en mode débogage - elle ne pourrait pas être aussi solide, mais rapide à ajouter à un projet
HotJard
1
@GantMan merci pour la réponse. J'ai corrigé le code
HotJard
@HotJard nice, celui-ci ne produit pas d' will never be executedavertissement
Dannie P
59

Swift 4

Vous pouvez maintenant utiliser targetEnvironment(simulator)comme argument.

#if targetEnvironment(simulator)
    // Simulator
#else
    // Device
#endif

Mis à jour pour Xcode 9.3

Matt Swift
la source
8
Cela devrait maintenant être la réponse acceptée. Je souhaite qu'il y ait un moyen sur SO de proposer une nouvelle réponse suggérée basée sur les mises à jour des OS / langages de programmation.
2018
4
c'est un bon point @quemeful - c'est l'un des rares échecs de base de SO. Étant donné que les systèmes informatiques changent si rapidement, presque toutes les réponses sur SO deviennent fausses au fil du temps .
Fattie
40

Permettez-moi de clarifier certaines choses ici:

  1. TARGET_OS_SIMULATORn'est pas défini dans le code Swift dans de nombreux cas; vous pouvez l'obtenir accidentellement en raison d'un en-tête de pontage, mais il est fragile et non pris en charge. Ce n'est même pas possible dans les frameworks. C'est pourquoi certaines personnes ne savent pas si cela fonctionne dans Swift.
  2. Je déconseille fortement d'utiliser l'architecture comme substitut du simulateur.

Pour effectuer des vérifications dynamiques:

La vérification ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nilest parfaitement bien.

Vous pouvez également obtenir la simulation du modèle sous-jacent en vérifiant SIMULATOR_MODEL_IDENTIFIERqui renverra des chaînes comme iPhone10,3.

Pour effectuer des vérifications statiques:

Xcode 9.2 et versions antérieures: définissez votre propre drapeau de compilation Swift (comme indiqué dans les autres réponses).

Xcode 9.3+ utilise la nouvelle condition targetEnvironment:

#if targetEnvironment(simulator)
    // for sim only
#else
    // for device
#endif
Russbishop
la source
1
Il semble que vous ayez de nouvelles informations internes ici. Très utile! Remarque TARGET_OS_SIMULATOR a fonctionné pendant un certain temps à la fois dans le code d'application et de framework; et cela fonctionne également dans Xcode 9.3 b3. Mais je suppose que c'est "accidentel". Une sorte de déception; parce que cela semble être le moyen le moins hacky. En tant que fournisseur de code cadre pouvant être compilé dans Xcode 9.3 ou une version antérieure, il semble que nous devrons encapsuler #if targetEnvironment ... dans une macro #if swift (> = 4.1) pour éviter les erreurs du compilateur. Ou je suppose utiliser .... environnement ["SIMULATOR_DEVICE_NAME"]! = Nil. Cette vérification semble plus hacky, OMI.
Daniel
si vous avez une "condition de plate-forme inattendue (" os "," arc "ou" rapide ")" erronée lors de l'utilisation de targetEnvironment (simulateur)
Zaporozhchenko Oleksandr
@Aleksandr a targetEnvironmentatterri dans Xcode 9.3. Vous avez besoin d'une version plus récente de Xcode.
russbishop
@russbishop bon travail pour éclaircir cela pour la dernière nouvelle ère - merci!
Fattie
J'ai envoyé une prime de 250, car cette réponse semble ajouter les informations les plus récentes et les plus récentes - cheers
Fattie
15

Ce qui fonctionne pour moi depuis Swift 1.0 est de rechercher une architecture autre que arm:

#if arch(i386) || arch(x86_64)

     //simulator
#else 
     //device

#endif
akaru
la source
14

Runtime, mais plus simple que la plupart des autres solutions ici:

if TARGET_OS_SIMULATOR != 0 {
    // target is current running in the simulator
}

Alternativement, vous pouvez simplement appeler une fonction d'assistance Objective-C qui renvoie un booléen qui utilise la macro du préprocesseur (surtout si vous mixez déjà dans votre projet).

Edit: Pas la meilleure solution, surtout depuis Xcode 9.3. Voir la réponse de HotJard

cale
la source
3
Je fais cela mais j'obtiens des avertissements dans la clause else car elle "ne sera jamais exécutée". Nous avons une règle d'avertissement zéro, donc :-(
EricS
il affichera un avertissement mais cela a du sens, selon que vous avez un simulateur ou un appareil sélectionné pour la construction, l'avertissement s'affichera sur la partie qui ne sera pas exécutée, mais ouais ennuyeux pour une politique d'avertissement zéro
Fonix
1
Ne voyant que les avertissements lorsque j'utilise à la == 0place de != 0. L'utiliser comme écrit ci-dessus, même avec un elsebloc après, ne produit aucun avertissement dans Swift 4 Xcode Version 9.2 (9C40b)
shim
Je l'ai également testé sur une cible de simulateur ainsi que sur un appareil physique. Semble également être le même dans Swift 3.2 (même version Xcode).
shim
Dans Xcode 9.3 + Swift 4.1, je viens de remarquer qu'il a l'avertissement même avec! = 0. Sheesh.
cale
10

Dans les systèmes modernes:

#if targetEnvironment(simulator)
    // sim
#else
    // device
#endif

C'est très simple.

Fattie
la source
1
Je ne sais pas pourquoi la première devrait être "plus correcte" que la réponse de Daniel . - Notez que le second est un contrôle de temps de compilation. Bonne année!
Martin R
5

TARGET_IPHONE_SIMULATORest obsolète dans iOS 9. TARGET_OS_SIMULATORest le remplacement. Est également TARGET_OS_EMBEDDEDdisponible.

De TargetConditionals.h :

#if defined(__GNUC__) && ( defined(__APPLE_CPP__) || defined(__APPLE_CC__) || defined(__MACOS_CLASSIC__) )
. . .
#define TARGET_OS_SIMULATOR         0
#define TARGET_OS_EMBEDDED          1 
#define TARGET_IPHONE_SIMULATOR     TARGET_OS_SIMULATOR /* deprecated */
#define TARGET_OS_NANO              TARGET_OS_WATCH /* deprecated */ 
Sittelle
la source
1
j'ai essayé TARGET_OS_SIMULATOR mais ne fonctionne pas ou je ne suis pas reconnu par le Xcode alors que TARGET_IPHONE_SIMULATOR le fait. Je construis pour iOS 8.0 ci-dessus.
CodeOverRide
Je regarde les en-têtes iOS 9. Je mettrai à jour ma réponse.
Sittelle du
5

J'espère que cette extension sera utile.

extension UIDevice {
    static var isSimulator: Bool = {
        #if targetEnvironment(simulator)
        return true
        #else
        return false
        #endif
    }()
}

Usage:

if UIDevice.isSimulator {
    print("running on simulator")
}
Lucas Chwe
la source
@ChetanKoli, j'allais rendre le code très clair, plutôt que court, donc c'est facile à comprendre pour n'importe qui. Je ne sais pas ce que je pense de votre montage.
Lucas Chwe
3

Dans Xcode 7.2 (et plus tôt mais je n'ai pas testé combien auparavant), vous pouvez définir un indicateur de build spécifique à la plate-forme "-D TARGET_IPHONE_SIMULATOR" pour "Any iOS Simulator".

Regardez dans les paramètres de construction du projet sous "Compilateur Swift - Indicateurs client", puis définissez l'indicateur dans "Autres indicateurs Swift". Vous pouvez définir un indicateur spécifique à la plate-forme en cliquant sur l'icône «plus» lorsque vous survolez une configuration de build.

Il y a quelques avantages à le faire de cette façon: 1) Vous pouvez utiliser le même test conditionnel ("#if TARGET_IPHONE_SIMULATOR") dans votre code Swift et Objective-C. 2) Vous pouvez compiler des variables qui ne s'appliquent qu'à chaque build.

Capture d'écran des paramètres de construction de Xcode

xgerrit
la source
1

J'ai utilisé ce code ci-dessous dans Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}
ak_ninan
la source
1
Je fais cela mais j'obtiens des avertissements dans la clause else car elle "ne sera jamais exécutée". Nous avons une règle d'avertissement zéro, alors grrrr ....
EricS
Il affichera un avertissement chaque fois que vous essayez de courir avec un appareil, si vous êtes sélectionné pour le simulateur, il n'affichera pas l'avertissement.
ak_ninan
1
il est obsolète
rcmstark
1

Swift 4:

Actuellement, je préfère utiliser la classe ProcessInfo pour savoir si le périphérique est un simulateur et quel type de périphérique est utilisé:

if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
            print("yes is a simulator :\(simModelCode)")
}

Mais, comme vous le savez, ce simModelCoden'est pas un code confortable pour comprendre immédiatement quel type de simulateur a été lancé, donc, si vous en avez besoin, vous pouvez essayer de voir cette autre réponse SO pour déterminer le modèle actuel d'iPhone / appareil et d'avoir un humain plus chaîne lisible.

Alessandro Ornano
la source
1

Voici un exemple Xcode 11 Swift basé sur la réponse impressionnante de HotJard ci - dessus , cela ajoute également un isDeviceBool et utilise à la SIMULATOR_UDIDplace du nom. Des affectations variables sont effectuées sur chaque ligne afin que vous puissiez les examiner plus facilement dans le débogueur si vous le souhaitez.

import Foundation

// Extensions to UIDevice based on ProcessInfo.processInfo.environment keys
// to determine if the app is running on an actual device or the Simulator.

@objc extension UIDevice {
    static var isSimulator: Bool {
        let environment = ProcessInfo.processInfo.environment
        let isSimulator = environment["SIMULATOR_UDID"] != nil
        return isSimulator
    }

    static var isDevice: Bool {
        let environment = ProcessInfo.processInfo.environment
        let isDevice = environment["SIMULATOR_UDID"] == nil
        return isDevice
    }
}

Il y a aussi l'entrée du dictionnaire DTPlatformNamequi devrait contenir simulator.

Alex Zavatone
la source
0

Utilisez ce code ci-dessous:

#if targetEnvironment(simulator)
   // Simulator
#else
   // Device
#endif

Fonctionne pour Swift 4etXcode 9.4.1

Haroldo Gondim
la source
0

Xcode 11, Swift 5

    #if !targetEnvironment(macCatalyst)
    #if targetEnvironment(simulator)
        true
    #else
        false        
    #endif
    #endif
UnchartedWorks
la source
0

En plus d'autres réponses.

Dans Objective-c, assurez-vous simplement que vous avez inclus TargetConditionals .

#include <TargetConditionals.h>

avant d'utiliser TARGET_OS_SIMULATOR.

M. Ali
la source