Como saber em tempo de execução se um aplicativo iOS está sendo executado através de uma instalação Beta do TestFlight


122

É possível detectar em tempo de execução que um aplicativo foi instalado através do TestFlight Beta (enviado pelo iTunes Connect) versus a App Store? Você pode enviar um único pacote de aplicativos e disponibilizá-lo por ambos. Existe uma API que pode detectar de que maneira foi instalada? Ou o recibo contém informações que permitem determinar isso?


4
Só para esclarecer, você está falando sobre o novo teste beta do TestFlight através do iTunes Connect? Ou você está falando sobre quando carregou diretamente no TestFlight?
keji

A nova versão beta do TestFlight, esclarecerá
combinatória

1
Parece que - [NSString containsString:] é uma adição ao ios8. Se o teste automático da App Store tentar executá-lo no ios7, não vá. ([receiverURLString rangeOfString: @ "sandboxReceipt"]. location! = NSNotFound) deve fazer o truque.
Rgorge

Obrigado @ rgeorge, que foi um erro estúpido!
combinatorial

2
Gostaria de perguntar sobre a detecção no iOS 6 que não possui appStoreReceiptURL, mas parece que o aplicativo TestFlight é apenas para iOS 8; então - [NSString containsString] pode estar bem, afinal. Coloquei os testes beta da loja de aplicativos em espera por causa disso, mas acho que algumas pessoas podem estar usando uma estratégia de testes híbridos, com o Ad-Hoc para testes herdados e o AppStore beta para beta público, para que rangeOfString ainda vença.
Gordon Dove

Respostas:


117

Para um aplicativo instalado através do TestFlight Beta, o arquivo de recebimento é nomeado StoreKit\sandboxReceiptversus o usual StoreKit\receipt. Usando [NSBundle appStoreReceiptURL]você pode procurar sandboxReceipt no final do URL.

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSString *receiptURLString = [receiptURL path];
BOOL isRunningTestFlightBeta =  ([receiptURLString rangeOfString:@"sandboxReceipt"].location != NSNotFound);

Observe que sandboxReceipttambém é o nome do arquivo de recebimento ao executar compilações localmente e para compilações executadas no simulador.


7
Como observado, isso funciona para testes locais no dispositivo, mas não no simulador. Eu adicionei algo como #if TARGET_IPHONE_SIMULATOR isRunningInTestMode = YES; #endif Obviamente, isso precisa de #import <TargetConditionals.h>
Gordon Dove

13
Versão compacta: [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"](Verdadeiro se estiver executando o binário distribuído TestFlight) via Supertop / Haddad
Nick

2
Esse método não pode ser usado em pacotes configuráveis ​​de extensão, pois o recebimento existe apenas para pacotes configuráveis ​​do host.
precisa saber é

2
Meu teste do iOS 8 resulta StoreKit/sandboxReceiptquando instalado como uma compilação de depuração via Xcode no dispositivo ou simulador. Portanto, isso pode não distinguir com precisão as compilações do testflight de todas as outras compilações.
pkamb

3
Também parece retornar YES ao instalar uma compilação com distribuição Ad Hoc.
Keller

74

Com base na resposta combinatória, criei a seguinte classe auxiliar SWIFT. Com essa classe, você pode determinar se é uma compilação de depuração, teste de vôo ou appstore.

enum AppConfiguration {
  case Debug
  case TestFlight
  case AppStore
}

struct Config {
  // This is private because the use of 'appConfiguration' is preferred.
  private static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
  
  // This can be used to add debug statements.
  static var isDebug: Bool {
    #if DEBUG
      return true
    #else
      return false
    #endif
  }

  static var appConfiguration: AppConfiguration {
    if isDebug {
      return .Debug
    } else if isTestFlight {
      return .TestFlight
    } else {
      return .AppStore
    }
  }
}

Usamos esses métodos em nosso projeto para fornecer diferentes IDs de rastreamento ou cadeia de conexão por ambiente:

  func getURL(path: String) -> String {    
    switch (Config.appConfiguration) {
    case .Debug:
      return host + "://" + debugBaseUrl + path
    default:
      return host + "://" + baseUrl + path
    }
  }

OU:

  static var trackingKey: String {
    switch (Config.appConfiguration) {
    case .Debug:
      return debugKey
    case .TestFlight:
      return testflightKey
    default:
      return appstoreKey
    }
  }

ATUALIZAÇÃO 05-02-2016: Um pré-requisito para usar uma macro de pré-processador como #if DEBUG é definir alguns sinalizadores personalizados do Swift Compiler. Mais informações nesta resposta: https://stackoverflow.com/a/24112024/639227


1
@Urkman Verifique se você está definindo a -D DEBUGbandeira. Mais informações podem ser encontradas aqui .
Caleb

Thnx @Caleb, adicionei mais explicações sobre os pré-requisitos à resposta.
LorenzoValentijn

1
Obrigado pela sua resposta, achei muito útil! Também é bom saber, #if targetEnvironment(simulator)você determina se está executando em um simulador. Então, eu tenho as opções Simulator / TestFlight / AppStore (que é no meu caso preferível a Debug) :-)
JeroenJK

38

Versão Swift moderna, responsável por simuladores (com base na resposta aceita):

private func isSimulatorOrTestFlight() -> Bool {
    guard let path = Bundle.main.appStoreReceiptURL?.path else {
        return false
    }
    return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
}

É bom incluir o simulador, mas você pode alterar o nome da função, pois isso não é mais verdadeiro para todos os casos.
dbn 29/09

2
UAU! Funciona! Impressionante! Retorna TRUE para TestFlight e FALSE for AppStore para a mesma construção (uma construção criada no esquema com um provisionamento). Perfeito! Obrigado!
Argus

@dbn, você pode explicar por que isso não é mais verdade para todos os casos?
Ethan

1
@Esta resposta foi editada depois que eu fiz o meu comentário; o nome do método costumava serisTestFlight()
dbn


2

Eu uso a extensão Bundle+isProductionno Swift 5.2:

import Foundation

extension Bundle {
    var isProduction: Bool {
        #if DEBUG
            return false
        #else
            guard let path = self.appStoreReceiptURL?.path else {
                return true
            }
            return !path.contains("sandboxReceipt")
        #endif
    }
}

Então:

if Bundle.main.isProduction {
    // do something
}

-3

Existe uma maneira de usá-lo em meus projetos. Aqui estão os passos.

No Xcode, vá para as configurações do projeto (projeto, não alvo) e adicione a configuração "beta" à lista:

insira a descrição da imagem aqui



Então você precisa criar um novo esquema que executará o projeto na configuração "beta". Para criar um esquema, clique aqui:

insira a descrição da imagem aqui



Nomeie esse esquema como desejar. Você deve editar as configurações deste esquema. Para fazer isso, toque aqui:

insira a descrição da imagem aqui



Selecione a guia Arquivo, onde você pode selecionar Build configuration

insira a descrição da imagem aqui



Em seguida, você precisa adicionar uma chave Configcom valor $(CONFIGURATION)à lista de propriedades de informações do projeto, como esta:

insira a descrição da imagem aqui



Então é exatamente o que você precisa no código para fazer algo específico à versão beta:

let config = Bundle.main.object(forInfoDictionaryKey: "Config") as! String
if config == "Debug" {
  // app running in debug configuration
}
else if config == "Release" {
  // app running in release configuration
}
else if config == "Beta" {
  // app running in beta configuration
}

6
Embora essa seja uma técnica útil, ela não responde à pergunta. Um único binário é enviado à App Store e pode ser executado a partir do download através do TestFlight ou posterior após a execução aprovada após o download na App Store. A questão é detectar a versão em execução.
combinatorial

Existe uma opção para criar 2 arquivos em primeiro lugar. um para testflight e outro para a loja de aplicativos.
21417 Klemen

É possível, mas eles precisam ter números de compilação diferentes. E isso significa gerenciar duas construções em vez de uma.
combinatória

ok, na minha opinião vale a pena. Especialmente se você usar ferramentas de integração contínua.
Klemen 17/02

@KlemenZagar, sua abordagem é bem conhecida e boa, mas não responde à pergunta.
Stanislav Pankevich
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.