Como detectar se o aplicativo está sendo criado para dispositivo ou simulador no Swift

277

No Objective-C, podemos saber se um aplicativo está sendo criado para dispositivo ou simulador usando macros:

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

Essas são macros de tempo de compilação e não estão disponíveis no tempo de execução.

Como posso conseguir o mesmo no Swift?

RaffAl
fonte
2
Não é assim que detectar o simulador ou um dispositivo real em tempo de execução no Objective-C. Essas são diretivas do compilador que resultam em código diferente, dependendo da compilação.
Rddydy
Obrigado. Eu editei minha pergunta.
precisa saber é o seguinte
9
AS RESPOSTAS MAIS ELEVADAS NÃO SÃO A MELHOR MANEIRA DE RESOLVER ESTE PROBLEMA! A resposta de mbelsky (atualmente muito distante) é a única solução que vem sem armadilhas. Até Greg Parker, da Apple, sugeriu fazê-lo dessa maneira: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
MESMO NO CAPS, É INGÊNIO SUGERIR QUE HÁ ALGO ERRADO COM UMA VERIFICAÇÃO RUNTIME. As sugestões dos engenheiros da Apple costumam ser mal pensadas, ou são aplicadas apenas em determinadas situações, de modo que por si só significa menos do que nada.
Fattie
1
@Fattie: Seria interessante saber por que nenhuma das respostas dadas satisfaz suas necessidades e o que você está exatamente esperando ao oferecer a recompensa.
Martin R

Respostas:

363

Atualização 30/01/19

Embora essa resposta possa funcionar, a solução recomendada para uma verificação estática (conforme esclarecido por vários engenheiros da Apple) é definir um sinalizador de compilador personalizado direcionado aos simuladores do iOS. Para instruções detalhadas sobre como fazer isso, consulte a resposta de @ mbelsky .

Resposta original

Se você precisar de uma verificação estática (por exemplo, não um tempo de execução, se / else), não poderá detectar o simulador diretamente, mas poderá detectar o iOS em uma arquitetura de desktop, como segue

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

Após a versão Swift 4.1

Uso mais recente, agora diretamente para todos em uma condição, para todos os tipos de simuladores precisam aplicar apenas uma condição -

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

Para mais esclarecimentos, você pode verificar a proposta Swift SE-0190


Para versão mais antiga -

Claramente, isso é falso em um dispositivo, mas retorna verdadeiro para o iOS Simulator, conforme especificado na documentação :

A configuração de construção do arch (i386) retorna verdadeira quando o código é compilado para o simulador iOS de 32 bits.

Se você estiver desenvolvendo para um simulador que não seja o iOS, basta alterar o osparâmetro: por exemplo

Detectar o simulador watchOS

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

Detectar o simulador de tvOS

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

Ou, até, detectar qualquer simulador

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

Se você estiver bem com uma verificação de tempo de execução, poderá inspecionar a TARGET_OS_SIMULATORvariável (ou TARGET_IPHONE_SIMULATORno iOS 8 e abaixo), que é verdadeira em um simulador.

Observe que isso é diferente e um pouco mais limitado do que usar um sinalizador de pré-processador. Por exemplo, você não poderá usá-lo no lugar onde a if/elseé sintaticamente inválido (por exemplo, fora do escopo das funções).

Digamos, por exemplo, que você deseja ter importações diferentes no dispositivo e no simulador. Isso é impossível com uma verificação dinâmica, enquanto que é trivial com uma verificação estática.

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

Além disso, como o sinalizador é substituído por a 0ou a 1pelo pré-processador rápido, se você o usar diretamente em uma if/elseexpressão, o compilador emitirá um aviso sobre código inacessível.

Para contornar esse aviso, consulte uma das outras respostas.

Gabriele Petronella
fonte
1
Mais leitura aqui . E para ser ainda mais restritivo, você pode usar arch(i386) && os(iOS).
ahruss
1
Isto não funcionou para mim. Eu tinha que verificar tanto para i386 e x86_64
akaru
3
ESTA RESPOSTA NÃO É A MELHOR MANEIRA DE RESOLVER ESTE PROBLEMA! A resposta de mbelsky (atualmente muito distante) é a única solução que vem sem armadilhas. Até Greg Parker, da Apple, sugeriu fazer dessa maneira: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@russbishop, este provou ser um conselho útil para centenas de pessoas até agora, compensando a falta de uma API. Em vez de seqüestrar a resposta assinando um comentário na parte superior, basta se comunicar. Atualizei a resposta para esclarecer que essa não é mais uma solução atualizada e forneço um link para a que parece mais correta.
Gabriele Petronella 14/09
9
No Swift 4.1, você poderá dizer #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/proposals/… )
Hamish
172

ATUALIZADO PARA SWIFT 4.1. Use em #if targetEnvironment(simulator)vez disso. Fonte

Para detectar o simulador no Swift, você pode usar a configuração de compilação:

  • Defina esta configuração -D IOS_SIMULATOR no Swift Compiler - Sinalizadores personalizados> Outros sinalizadores Swift
  • Selecione Qualquer SDK do iOS Simulator neste menu suspensoLista suspensa

Agora você pode usar esta declaração para detectar o simulador:

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

Além disso, você pode estender a classe UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator
mbelsky
fonte
8
Essa deve ser a melhor resposta! Até Greg Parker, da Apple, sugeriu o seguinte: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
atualização de uso do swift 3: UIDevice.current.isSimulator
tylernol
1
Posso perguntar por que, se eu adicionar isso em Liberar, isso não funciona?
William Hu
3
Esta é a única resposta correta. Você também pode configurá-lo em xcconfigarquivos usando OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDe OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORpara substituir o Simulador.
russbishop 14/09
1
No Xcode 9.2, essa resposta falhou ao compilar algumas vezes. A remoção do "-" antes do "D" resolveu o problema para mim.
Blake
160

Informações atualizadas a partir de 20 de fevereiro de 2018

Parece que @russbishop tem uma resposta autorizada que a torna "incorreta" - mesmo que pareça funcionar por um longo tempo.

Detectar se o aplicativo está sendo criado para o dispositivo ou simulador no Swift

Resposta Anterior

Com base na resposta do @ WZW e nos comentários do @ Pang, criei uma estrutura de utilitário simples. Esta solução evita os avisos produzidos pela resposta da @ WZW.

import Foundation

struct Platform {

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

}

Exemplo de uso:

if Platform.isSimulator {
    print("Running on Simulator")
}
Daniel
fonte
10
Solução muito melhor do que a aceita. De fato, se algum dia (embora seja muito improvável) a Apple decidir usar o i386 ou x85_64 em dispositivos iOS, a resposta aceita não funcionará… ou mesmo se os computadores de mesa receberem um novo processo!
Frizlab
2
Confirmou que isso funciona perfeitamente no Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... mesma coisa, simplificada. 1 graças
Dan Rosenstark 14/03
1
@daniel Isso funciona bem e é realmente mais direto do que minha solução. No entanto, vale a pena notar que é mais limitado do que uma etapa real do pré-processador. Se você precisar que parte do código não seja incluída no destino (por exemplo, você deseja escolher entre duas importações em tempo de compilação), será necessário usar uma verificação estática. Eu editei minha resposta para destacar essa diferença.
Gabriele Petronella 04/04
ESTA RESPOSTA NÃO É A MELHOR MANEIRA DE RESOLVER ESTE PROBLEMA! A resposta de mbelsky (atualmente muito distante) é a única solução que vem sem armadilhas. Até Greg Parker, da Apple, sugeriu fazer dessa maneira: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@Fattie TARGET_OS_SIMULATOR != 0 está na resposta . É a solução dada por Daniel. Não há necessidade de adicioná-lo novamente em uma variável livre, ele já está lá. Se você acha que tê-lo em uma estrutura é ruim e tê-lo em uma variável livre é melhor, publique um comentário sobre isso ou faça sua própria resposta. Obrigado.
Eric Aya
69

Do Xcode 9.3

#if targetEnvironment(simulator)

O Swift suporta uma nova condição de plataforma targetEnvironment com um único simulador de argumento válido. A compilação condicional do formato '#if targetEnvironment (simulator)' agora pode ser usada para detectar quando o destino da compilação é um simulador. O compilador Swift tentará detectar, avisar e sugerir o uso do targetEnvironment (simulador) ao avaliar as condições da plataforma que parecem estar testando indiretamente os ambientes do simulador, por meio das condições existentes da plataforma os () e arch (). (SE-0190)

iOS 9 ou superior:

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
    }
}

Antes do iOS 9:

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

Objetivo-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
fonte
2
Comparar seqüências de caracteres é mais frágil do que usar constantes definidas.
Michael Peterson
@ P1X3L5 você está certo! Mas eu estou supor que este método é chamado no modo de depuração - não poderia ser tão sólido, mas rápido para adicionar um projeto
HotJard
1
@GantMan obrigado pela resposta. Eu
corrigi
@HotJard agradável, este não produz will never be executedaviso
Dannie P
59

Swift 4

Agora você pode usar targetEnvironment(simulator)como argumento.

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

Atualizado para o Xcode 9.3

Matt Swift
fonte
8
Agora, essa deve ser a resposta aceita. Eu gostaria que houvesse uma maneira de o SO propor uma nova resposta sugerida com base em atualizações para linguagens de programação / SO.
quemeful 23/05
4
é um ótimo ponto @quemeful - é uma das poucas falhas básicas do SO. Como os sistemas de computação mudam tão rapidamente, quase todas as respostas no SO ficam erradas ao longo do tempo .
Fattie
40

Deixe-me esclarecer algumas coisas aqui:

  1. TARGET_OS_SIMULATORnão está definido no código Swift em muitos casos; você pode importá-lo acidentalmente devido a um cabeçalho de ponte, mas isso é quebradiço e não é suportado. Também não é possível em estruturas. É por isso que algumas pessoas estão confusas sobre se isso funciona no Swift.
  2. Eu desaconselho fortemente o uso da arquitetura como substituto do simulador.

Para executar verificações dinâmicas:

A verificação ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nilestá perfeitamente bem.

Você também pode obter o modelo subjacente que está sendo simulado, verificando SIMULATOR_MODEL_IDENTIFIERcomo retornará as strings iPhone10,3.

Para executar verificações estáticas:

Xcode 9.2 e versões anteriores: defina seu próprio sinalizador de compilação Swift (como mostrado em outras respostas).

O Xcode 9.3+ usa a nova condição targetEnvironment:

#if targetEnvironment(simulator)
    // for sim only
#else
    // for device
#endif
russbishop
fonte
1
Parece que você tem algumas informações internas novas aqui. Muito útil! Nota: TARGET_OS_SIMULATOR trabalhou por algum tempo no código do aplicativo e da estrutura; e também está funcionando no Xcode 9.3 b3. Mas acho que isso é "acidental". Uma espécie de chatice; porque esta parece ser a maneira menos invasiva. Como um provedor de código de estrutura que pode ser compilado no Xcode 9.3 ou anterior, parece que teremos que agrupar #if targetEnvironment ... em uma macro #if swift (> = 4.1) para evitar erros do compilador. Ou acho que use .... ambiente ["SIMULATOR_DEVICE_NAME"]! = Zero. Essa verificação parece mais hacky, IMO.
Daniel
se tiver o erro "Condição inesperada da plataforma (esperado 'os', 'arch' ou 'swift')" usando o targetEnvironment (simulador)
Zaporozhchenko Oleksandr
O @Aleksandr foi targetEnvironmentparar no Xcode 9.3. Você precisa de uma versão mais recente do Xcode.
russbishop
@russbishop bom trabalho esclarecendo isso para a última nova era - obrigado!
Fattie
Enviei uma recompensa de 250, uma vez que esta resposta parece adicionar as informações mais recentes e mais recentes
felicidades Fattie
15

O que funciona para mim desde o Swift 1.0 é procurar uma arquitetura diferente de arm:

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

     //simulator
#else 
     //device

#endif
akaru
fonte
14

Tempo de execução, mas mais simples do que a maioria das outras soluções aqui:

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

Como alternativa, você pode simplesmente chamar uma função auxiliar Objective-C que retorna um booleano que usa a macro do pré-processador (especialmente se você já estiver misturando no seu projeto).

Edit: Não é a melhor solução, especialmente a partir do Xcode 9.3. Veja a resposta do HotJard

calço
fonte
3
Eu faço isso, mas recebo avisos na cláusula else porque "nunca será executada". Temos uma regra de alerta zero, de modo :-(
Erics
ele mostrará um aviso, mas faz sentido, dependendo se você tiver um simulador ou dispositivo selecionado para construção, o aviso será exibido na parte que não será executada, mas sim irritante para uma política de aviso zero
Fonix
1
Apenas vendo avisos quando uso em == 0vez de != 0. Usá-lo como descrito acima, mesmo com um elsebloco depois, não produz nenhum aviso no Swift 4 Xcode Versão 9.2 (9C40b)
shim
Também o testei em um alvo de simulador e em um dispositivo físico. Também parece ser o mesmo no Swift 3.2 (mesma versão do Xcode).
shim
No Xcode 9.3 + Swift 4.1, notei que ele possui o aviso mesmo com! = 0. Sheesh.
shim
10

Nos sistemas modernos:

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

É fácil.

Fattie
fonte
1
Não sei por que o primeiro deve ser "mais correto" do que a resposta de Daniel . - Observe que o segundo é uma verificação do tempo de compilação. Feliz Ano Novo!
Martin R
5

TARGET_IPHONE_SIMULATORfoi descontinuado no iOS 9. TARGET_OS_SIMULATORé a substituição. Também TARGET_OS_EMBEDDEDestá disponível.

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 */ 
Pica-pau-cinzento
fonte
1
Eu tentei TARGET_OS_SIMULATOR, mas não funciona ou é reconhecido pelo Xcode enquanto TARGET_IPHONE_SIMULATOR faz. Estou construindo para o iOS 8.0 acima.
CodeOverRide
Estou olhando os cabeçalhos do iOS 9. Vou atualizar minha resposta.
Nuthatch
5

Espero que esta extensão seja útil.

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

Uso:

if UIDevice.isSimulator {
    print("running on simulator")
}
Lucas Chwe
fonte
@ChetanKoli, eu estava indo para tornar o código muito claro, e não curto, por isso é fácil de entender para qualquer um. Não sei como me sinto sobre sua edição.
Lucas Chwe
3

No Xcode 7.2 (e versões anteriores, mas ainda não testei quanto antes), você pode definir um sinalizador de compilação específico da plataforma "-D TARGET_IPHONE_SIMULATOR" para "Any iOS Simulator".

Procure nas configurações de construção do projeto em "Swift Compiler - Flags do Cliente" e defina o sinalizador em "Other Swift Flags". Você pode definir um sinalizador específico da plataforma clicando no ícone 'mais' quando passar o mouse sobre uma configuração de compilação.

Existem algumas vantagens em fazer isso desta maneira: 1) Você pode usar o mesmo teste condicional ("#if TARGET_IPHONE_SIMULATOR") em seu código Swift e Objective-C. 2) Você pode compilar variáveis ​​que se aplicam apenas a cada build.

Captura de tela das configurações de compilação do Xcode

xgerrit
fonte
1

Eu usei esse código abaixo no Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}
ak_ninan
fonte
1
Eu faço isso, mas recebo avisos na cláusula else porque "nunca será executada". Temos uma regra de alerta zero, de modo grrrr ....
Erics
Ele exibirá um aviso sempre que você estiver tentando executar com um dispositivo; se você estiver selecionado um simulador para execução, ele não exibirá o aviso.
ak_ninan
1
ele está obsoleto
rcmstark
1

Swift 4:

Atualmente, prefiro usar a classe ProcessInfo para saber se o dispositivo é um simulador e que tipo de dispositivo está em uso:

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

Mas, como você sabe, simModelCodenão é um código confortável para entender imediatamente que tipo de simulador foi lançado; portanto, se necessário, você pode tentar ver essa outra resposta do SO para determinar o modelo atual do iPhone / dispositivo e ter uma experiência mais humana. string legível.

Alessandro Ornano
fonte
1

Aqui está um exemplo do Xcode 11 Swift baseado na incrível resposta do HotJard acima , que também adiciona um isDeviceBool e usa em SIMULATOR_UDIDvez do nome. As atribuições variáveis ​​são feitas em cada linha, para que você possa examiná-las mais facilmente no depurador, se desejar.

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
    }
}

Há também a entrada do dicionário DTPlatformNameque deve conter simulator.

Alex Zavatone
fonte
0

Use este código abaixo:

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

Trabalhos para Swift 4eXcode 9.4.1

Haroldo Gondim
fonte
0

Xcode 11, Swift 5

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

Além de outras respostas.

No Objective-c, verifique se você incluiu TargetConditionals .

#include <TargetConditionals.h>

antes de usar TARGET_OS_SIMULATOR.

M. Ali
fonte