I am using CocoaLumberjack V2.4 to save log into file.
This is the default code to set saving log into file:
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
[DDLog addLogger:fileLogger];
Log is saved in default place: /AppData/Library/Caches/Logs/
I want implement an API to remove the saved log manually. I check they have Manually clear logs as the open issue there. Anyone has suggestion?
This should work for you:
[fileLogger rollLogFileWithCompletionBlock: ^{
for (NSString *filename in fileLogger.logFileManager.sortedLogFilePaths) {
[[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
}
}];
Swift Answer:
fileLogger.rollLogFile(withCompletion: {
for filename: String in self.fileLogger.logFileManager.sortedLogFilePaths {
do {
try FileManager.default.removeItem(atPath: filename)
} catch {
print(error.localizedDescription)
}
}
})
Try this one in Swift.
func clearLogs(){
let logHelper = LogHelper(subsystem: "Logs", category: "Clear")
logHelper.i("Clearing logs...")
for logger in DDLog.allLoggers {
if let fileLogger = logger as? DDFileLogger {
fileLogger.rollLogFile{
for path in fileLogger.logFileManager.sortedLogFilePaths {
let fileURL = URL(fileURLWithPath: path)
logHelper.i("\(fileURL)")
do {
try FileManager.default.removeItem(at: fileURL)
logHelper.i("Done!")
}
catch {
logHelper.e(error.localizedDescription)
}
}
}
}
}
}
Related
After updating iOS native app with an app written on Flutter I want to read a file from filesystem on iOS device using Dart. The file I want to read has been previously written to filesystem using this ObjectiveC code:
- (void)setAccount:(FTAccountModel *)account {
_account = account;
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
path = [path stringByAppendingPathComponent:AccountModelPath];
if (account) {
NSArray * array = #[account];
[array writeToFile:path atomically:YES];
[NSKeyedArchiver archiveRootObject:array toFile:path];
}
}
I've tried the following approach in Flutter using path_provider package:
final appDocDir = await getApplicationDocumentsDirectory();
final accountDataFile = File('${appDocDir.path}/$_iosAccountDataFile');
String contents = await accountDataFile.readAsString();
print("contents: $contents");
But I get an error when calling readAsString() method:
FileSystemException: Failed to decode data using encoding 'utf-8', path = '/var/mobile/Containers/Data/Application/FBCB4862-E5EA-4C93-8C2E-3DF1F00A8645/Documents/AccountModel.data'
How to read file on iOS device using Dart and Flutter, that has been written using NSKeyedArchiver?
As of writing this answer, there are no plugins to read the file, that has been previously written to the filesystem using NSKeyedArchiver in iOS. The way to read the file is to write custom platform-specific code.
So the iOS platform code on Swift will be something like the following:
private func callGetUserIdFromNativeApp(result: #escaping FlutterResult) {
var account: FTAccountModel?
let fm = FileManager.default
let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)
if (!urls.isEmpty) {
let file = urls[0].appendingPathComponent("Accounts.data", isDirectory: false)
if (fm.fileExists(atPath: file.path)) {
if let accountArray: [FTAccountModel] = NSKeyedUnarchiver.unarchiveObject(withFile: file.path) as? [FTAccountModel] {
if (!accountArray.isEmpty) {
account = accountArray[0]
}
}
}
}
if let userId: Int = account?.userId {
result(String(userId))
} else {
result(nil)
}
}
And the flutter part will use MethodChannel to invoke the native code:
static const MethodChannel _channel = const MethodChannel("CHANNEL_NAME");
static Future<String> getUserIdFromNativeIos() async {
try {
return await _channel.invokeMethod("METHOD_NAME");
} catch (e){
return _failedString();
}
}
For now I am doing like this
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies])
{
[storage deleteCookie:cookie];
}
But it is not working on iOS 8, 64-bit device.
Any other way the clean cookies of WKWebview? Any help will be appreciated. thanks.
Apple released new APIs for iOS 9, so now we can remove domain specific cookies stored for WKWebView with below code.
Swift 4/5 version:
let dataStore = WKWebsiteDataStore.default()
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
dataStore.removeData(
ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
for: records.filter { $0.displayName.contains("facebook") },
completionHandler: completion
)
}
Below is the Swift 3 version
let dataStore = WKWebsiteDataStore.default()
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { (records) in
for record in records {
if record.displayName.contains("facebook") {
dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: [record], completionHandler: {
print("Deleted: " + record.displayName);
})
}
}
}
Objective-C version -
WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
[dateStore
fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
for (WKWebsiteDataRecord *record in records) {
if ( [record.displayName containsString:#"facebook"]) {
[[WKWebsiteDataStore defaultDataStore]
removeDataOfTypes:record.dataTypes
forDataRecords:#[record]
completionHandler:^{
NSLog(#"Cookies for %# deleted successfully",record.displayName);
}
];
}
}
}
];
The above snippet will sure work for iOS 9 and later. Unfortunately, if we use WKWebView for iOS versions before iOS 9, we still have to stick to the traditional method and delete the whole cookies storage as below.
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *cookiesFolderPath = [libraryPath stringByAppendingString:#"/Cookies"];
NSError *errors;
[[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
Swift 3 version of Sarat's answer:
let dataStore = WKWebsiteDataStore.default()
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { (records) in
for record in records {
if record.displayName.contains("facebook") {
dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: [record], completionHandler: {
print("Deleted: " + record.displayName);
})
}
}
}
Supports iOS 11.0 and above
Following solution worked well for me:
Step 1. Remove Cookie from HTTPCookieStorage
Step 2. Fetch data records from WKWebsiteDataStore and delete them.
Step 3. Create a new WKProcessPool
Create a WKWebView Extension:
extension WKWebView {
func cleanAllCookies() {
HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
print("All cookies deleted")
WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
records.forEach { record in
WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {})
print("Cookie ::: \(record) deleted")
}
}
}
func refreshCookies() {
self.configuration.processPool = WKProcessPool()
}
}
Usage:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
webView.cleanAllCookies()
webView.refreshCookies()
}
None of these options worked for me but I found one that did:
let config = WKWebViewConfiguration()
if #available(iOS 9.0, *) {
config.websiteDataStore = WKWebsiteDataStore.nonPersistentDataStore()
} else {
// I have no idea what to do for iOS 8 yet but this works in 9.
}
let webView = WKWebView(frame: .zero, configuration: config)
In iOS9:
//// Optional data
NSSet *websiteDataTypes
= [NSSet setWithArray:#[
WKWebsiteDataTypeDiskCache,
//WKWebsiteDataTypeOfflineWebApplicationCache,
WKWebsiteDataTypeMemoryCache,
//WKWebsiteDataTypeLocalStorage,
//WKWebsiteDataTypeCookies,
//WKWebsiteDataTypeSessionStorage,
//WKWebsiteDataTypeIndexedDBDatabases,
//WKWebsiteDataTypeWebSQLDatabases
]];
//// All kinds of data
//NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
//// Date from
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
//// Execute
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
// Done
NSLog(#"remove done");
}];
Building on top of all the existing answers, if you are trying to clear cookies and data records for a specific WKWebView instance 'webView' and not the 'default' stored cookies and data records, you could use the following:
let dataStore = webView.configuration.websiteDataStore
let cookieStore = dataStore.httpCookieStore
cookieStore.getAllCookies {
$0.forEach { cookie in
cookieStore.delete(cookie)
}
}
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
records.forEach { record in
dataStore.removeData(ofTypes: record.dataTypes, for: [record]) { }
}
}
Swift 5
/// old API cookies
for cookie in HTTPCookieStorage.shared.cookies ?? [] {
HTTPCookieStorage.shared.deleteCookie(cookie)
}
/// URL cache
URLCache.shared.removeAllCachedResponses()
/// WebKit cache
let date = Date(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(
ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
modifiedSince: date,
completionHandler:{})
In addition to clearing cookies in the shared cookie storage, i'd try clearing the cache (NSURLCache) and discard the WKWebView and create a new one with a new WKProcessPool
Swift 4 and shorter version:
let dataStore = WKWebsiteDataStore.default()
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
for: records.filter { $0.displayName.contains("facebook") },
completionHandler: completion)
}
It seems like NSHTTPCookieStorage is now being used in iOS 8.2 to correctly clear cookies, as required. I had shipped an app which would run this code prior to opening a WKWebView based login:
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [storage cookies])
{
[storage deleteCookie:cookie];
}
Where earlier than iOS 8.2 the website would auto-login using the saved cookies, it now correctly asks the user to re-login. All this happened without me shipping an update to the app. :)
Esqarrouth's answer is only partially right.
The correct swift version is:
var libraryPath : String = NSFileManager().URLsForDirectory(.LibraryDirectory, inDomains: .UserDomainMask).first!.path!
libraryPath += "/Cookies"
do {
try NSFileManager.defaultManager().removeItemAtPath(libraryPath)
} catch {
print("error")
}
NSURLCache.sharedURLCache().removeAllCachedResponses()
WKWebview storing nothing inside [NSHTTPCookieStorage sharedHTTPCookieStorage].
clearing WKWebsiteDataStore will be the solution for this problem.
Still for IOS8 which is using WKwebview, this method is not applicable..
In WKWebView having issue to write and read its taking some time , So when you fetch the cookie some time you will get updated cookie but sometime it will be old one, and you will get error on any server request. I was facing this issue in 3 Days ,
Solution: No need to store cookies in WKWebsiteDataStore.
Getting cookies:
Swift:
extension WKWebView {
private var httpCookieStore: WKHTTPCookieStore { return WKWebsiteDataStore.default().httpCookieStore }
func getCookies(for domain: String? = nil, completion: #escaping ([String : Any])->()) {
var cookieDict = [String : AnyObject]()
httpCookieStore.getAllCookies { cookies in
for cookie in cookies {
if let domain = domain {
if cookie.domain.contains(domain) {
cookieDict[cookie.name] = cookie.properties as AnyObject?
}
} else {
cookieDict[cookie.name] = cookie.properties as AnyObject?
}
}
completion(cookieDict)
}
}
}
Objective-c :
-(void )getAllCookies
{
NSMutableString *updatedCockies= [[NSMutableString alloc] init];
if (#available(iOS 11.0, *)) {
WKHTTPCookieStore *cookieStore = _webView.configuration.websiteDataStore.httpCookieStore;
NSLog(#"cookieStore *********************: %#",cookieStore);
[cookieStore getAllCookies:^(NSArray* cookies) {
NSHTTPCookie *cookie;
for(cookie in cookies){
NSLog(#"%#",cookie)
}
self->updatedCookie = updatedCockies;
NSLog(#"cookie *********************: %#", self->updatedCookie);
}];
}
}
Every time you want new cookie so you need to write below code:
Given Sharpio
Swift :
let config = WKWebViewConfiguration()
if #available(iOS 9.0, *) {
config.websiteDataStore = WKWebsiteDataStore.nonPersistentDataStore()
} else {
// I have no idea what to do for iOS 8 yet but this works in 9.
}
let webView = WKWebView(frame: .zero, configuration: config)
Objective C--
WKWebViewConfiguration *wkWebConfig = [WKWebViewConfiguration new];
wkWebConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
self.webView = [[WKWebView alloc] initWithFrame: CGRectZero
configuration: wkWebConfig];
*******Every time you will get new cookies********
Swift version:
var libraryPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.LibraryDirectory, NSSearchPathDomainMask.UserDomainMask, false).first!
libraryPath += "/Cookies"
do {
let result = try NSFileManager.defaultManager().removeItemAtPath(libraryPath)
print(result)
} catch {
print("error")
}
NSURLCache.sharedURLCache().removeAllCachedResponses()
how to rewrite this objective-c language to swift?
NSString *filePath = #"/Applications/MySample.app";
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
// avoid open add friend
}
regards.
Equivalent Swift 3 Code:
let filePath = "/Applications/MySample.app"
if (FileManager.default.fileExists(atPath: filePath)) {
// avoid open add friend
}
Swift 2
let filePath = "/Applications/MySample.app"
if (NSFileManager.defaultManager().fileExistsAtPath(filePath))
{
// avoid open add friend
}
Some years after the question has been asked I recommend to take rewrite literally and use the URL related API
let fileURL = URL(fileURLWithPath:"/Applications/MySample.app")
if let _ = try? fileURL.checkResourceIsReachable() {
// file exists
}
let path = "/Applications/MySample.app"
let hasFile = FileManager().fileExists(atPath: path)
if hasFile {
// use file
}
else {
// possibly inform user the file does not exist
}
In my today extension with my device unlocked, this line of code works as expected, returning the data from the image path:
let imageData = NSData(contentsOfFile: path)
However when my device is locked with a passcode, it returns nil. Is there any way to access images in the file system when the device is locked? I can access UserDefaults just fine, but not files in the directory for my shared group. Here is how I am creating the path, calling imagePath, which is correctly populated with the path I expect in both cases:
func rootFilePath() -> String? {
let manager = NSFileManager()
let containerURL = manager.containerURLForSecurityApplicationGroupIdentifier(GROUP_ID)
if let unwrappedURL = containerURL {
return unwrappedURL.path
}
else {
return nil
}
}
func imagePath() -> String? {
let rootPath = rootFilePath()
if let uPath = rootPath {
return "\(uPath)/\(imageId).png"
}
else {
return nil
}
}
I just figured it out! You need to set the file permissions accordingly:
NSFileManager *fm = [[NSFileManager alloc] init];
NSDictionary *attribs = #{NSFileProtectionKey : NSFileProtectionNone};
NSError *unprotectError = nil;
BOOL unprotectSuccess = [fm setAttributes:attribs
ofItemAtPath:[containerURL path]
error:&unprotectError];
if (!unprotectSuccess) {
NSLog(#"Unable to remove protection from file! %#", unprotectError);
}
In many cases you wouldn't normally want to do this, but because the information is intended to be viewed from the lock screen, I'm OK with removing file protection.
Is there a way in iOS to programmatically check if the currently running app was installed from the iOS App Store? This is in contrast to an app that was run via Xcode, TestFlight, or any non-official distribution source.
This is in the context of an SDK that doesn't have access to the app's source code.
To be clear - I am looking for some signature, so to speak, given to the app (presumably by Apple), that will, without dependence on any preprocessor flags or other build configurations, be accessible to any application at run time.
Apps downloaded from the App Store have a iTunesMetadata.plist file added by the store:
NSString *file=[NSHomeDirectory() stringByAppendingPathComponent:#"iTunesMetadata.plist"];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
// probably a store app
}
Perhaps you might want to check if this file exists.
Update:
In iOS8, the application bundle has been moved. According to #silyevsk, the plist is now one level above [the new application main bundle path], at /private/var/mobile/Containers/Bundle/Application/4A74359F-E6CD-44C9-925D-AC82EāāāāB5EA837/iTunesMetadata.plist, and unfortunately, this can't be accessed from the app (permission denied)
Update Nov 4th 2015:
It appears that checking the receipt name can help. It must be noted that this solution is slightly different: it doesn't return whether we're running an App Store app, but rather whether we're running a beta Testflight app. This might or might not be useful depending on your context.
On top of that, it's a very fragile solution because the receipt name could change at any time. I'm reporting it anyway, in case you have no other options:
// Objective-C
BOOL isRunningTestFlightBeta = [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:#"sandboxReceipt"];
// Swift
let isRunningTestFlightBeta = NSBundle.mainBundle().appStoreReceiptURL?.lastPathComponent=="sandboxReceipt"
Source: Detect if iOS App is Downloaded from Apple's Testflight
How HockeyKit does it
By combining the various checks you can guess whether the app is running in a Simulator, in a Testflight build, or in an AppStore build.
Here's a segment from HockeyKit:
BOOL bit_isAppStoreReceiptSandbox(void) {
#if TARGET_OS_SIMULATOR
return NO;
#else
NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;
NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;
BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:#"sandboxReceipt"];
return isSandboxReceipt;
#endif
}
BOOL bit_hasEmbeddedMobileProvision(void) {
BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:#"embedded" ofType:#"mobileprovision"];
return hasEmbeddedMobileProvision;
}
BOOL bit_isRunningInTestFlightEnvironment(void) {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (bit_isAppStoreReceiptSandbox() && !bit_hasEmbeddedMobileProvision()) {
return YES;
}
return NO;
#endif
}
BOOL bit_isRunningInAppStoreEnvironment(void) {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (bit_isAppStoreReceiptSandbox() || bit_hasEmbeddedMobileProvision()) {
return NO;
}
return YES;
#endif
}
BOOL bit_isRunningInAppExtension(void) {
static BOOL isRunningInAppExtension = NO;
static dispatch_once_t checkAppExtension;
dispatch_once(&checkAppExtension, ^{
isRunningInAppExtension = ([[[NSBundle mainBundle] executablePath] rangeOfString:#".appex/"].location != NSNotFound);
});
return isRunningInAppExtension;
}
Source: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m
A possible Swift class, based on HockeyKit's class, could be:
//
// WhereAmIRunning.swift
// https://gist.github.com/mvarie/63455babc2d0480858da
//
// ### Detects whether we're running in a Simulator, TestFlight Beta or App Store build ###
//
// Based on https://github.com/bitstadium/HockeySDK-iOS/blob/develop/Classes/BITHockeyHelper.m
// Inspired by https://stackoverflow.com/questions/18282326/how-can-i-detect-if-the-currently-running-app-was-installed-from-the-app-store
// Created by marcantonio on 04/11/15.
//
import Foundation
class WhereAmIRunning {
// MARK: Public
func isRunningInTestFlightEnvironment() -> Bool{
if isSimulator() {
return false
} else {
if isAppStoreReceiptSandbox() && !hasEmbeddedMobileProvision() {
return true
} else {
return false
}
}
}
func isRunningInAppStoreEnvironment() -> Bool {
if isSimulator(){
return false
} else {
if isAppStoreReceiptSandbox() || hasEmbeddedMobileProvision() {
return false
} else {
return true
}
}
}
// MARK: Private
private func hasEmbeddedMobileProvision() -> Bool{
if let _ = NSBundle.mainBundle().pathForResource("embedded", ofType: "mobileprovision") {
return true
}
return false
}
private func isAppStoreReceiptSandbox() -> Bool {
if isSimulator() {
return false
} else {
if let appStoreReceiptURL = NSBundle.mainBundle().appStoreReceiptURL,
let appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent
where appStoreReceiptLastComponent == "sandboxReceipt" {
return true
}
return false
}
}
private func isSimulator() -> Bool {
#if arch(i386) || arch(x86_64)
return true
#else
return false
#endif
}
}
Gist: GitHub - mvarie/WhereAmIRunning.swift
Update Dec 9th 2016:
User halileohalilei reports that "This no longer works with iOS10 and Xcode 8.". I didn't verify this, but please check the updated HockeyKit source (see function bit_currentAppEnvironment) at:
Source: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m
Over time, the above class has been modified and it seems to handle iOS10 as well.
Update Oct 6th 2020:
Hockey has been deprecated/abandoned and replaced by Microsoft's AppCenter SDK.
This is their App Store / Testflight build detection class (link to repository below code):
MSUtility+Environment.h :
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import <Foundation/Foundation.h>
#import "MSUtility.h"
/*
* Workaround for exporting symbols from category object files.
*/
extern NSString *MSUtilityEnvironmentCategory;
/**
* App environment
*/
typedef NS_ENUM(NSInteger, MSEnvironment) {
/**
* App has been downloaded from the AppStore.
*/
MSEnvironmentAppStore = 0,
/**
* App has been downloaded from TestFlight.
*/
MSEnvironmentTestFlight = 1,
/**
* App has been installed by some other mechanism.
* This could be Ad-Hoc, Enterprise, etc.
*/
MSEnvironmentOther = 99
};
/**
* Utility class that is used throughout the SDK.
* Environment part.
*/
#interface MSUtility (Environment)
/**
* Detect the environment that the app is running in.
*
* #return the MSEnvironment of the app.
*/
+ (MSEnvironment)currentAppEnvironment;
#end
MSUtility+Environment.m :
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import "MSUtility+Environment.h"
/*
* Workaround for exporting symbols from category object files.
*/
NSString *MSUtilityEnvironmentCategory;
#implementation MSUtility (Environment)
+ (MSEnvironment)currentAppEnvironment {
#if TARGET_OS_SIMULATOR || TARGET_OS_OSX || TARGET_OS_MACCATALYST
return MSEnvironmentOther;
#else
// MobilePovision profiles are a clear indicator for Ad-Hoc distribution.
if ([self hasEmbeddedMobileProvision]) {
return MSEnvironmentOther;
}
/**
* TestFlight is only supported from iOS 8 onwards and as our deployment target is iOS 8, we don't have to do any checks for
* floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1).
*/
if ([self isAppStoreReceiptSandbox]) {
return MSEnvironmentTestFlight;
}
return MSEnvironmentAppStore;
#endif
}
+ (BOOL)hasEmbeddedMobileProvision {
BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:#"embedded" ofType:#"mobileprovision"];
return hasEmbeddedMobileProvision;
}
+ (BOOL)isAppStoreReceiptSandbox {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (![NSBundle.mainBundle respondsToSelector:#selector(appStoreReceiptURL)]) {
return NO;
}
NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;
NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;
BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:#"sandboxReceipt"];
return isSandboxReceipt;
#endif
}
#end
Source: GitHub - microsoft/appcenter-sdk-apple - MSUtility+Environment.m
If you're talking about your own app, you could add a state that returns true if it was build as part of a Store version (e.g. a compiler conditional) and false in every other case.
If you're talking about another app, it's not easy or straightforward (or maybe not even possible) to query other apps outside of your sandbox.
Since the code by #magma no longer works IOS11.1 Here is a bit of a long winded solution.
We check the app version on the app store and compare it to the version in the Bundle
static func isAppStoreVersion(completion: #escaping (Bool?, Error?) -> Void) throws -> URLSessionDataTask {
guard let info = Bundle.main.infoDictionary,
let currentVersion = info["CFBundleShortVersionString"] as? String,
let identifier = info["CFBundleIdentifier"] as? String else {
throw VersionError.invalidBundleInfo
}
let urlString = "https://itunes.apple.com/gb/lookup?bundleId=\(identifier)"
guard let url = URL(string:urlString) else { throw VersionError.invalidBundleInfo }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else { throw VersionError.invalidResponse }
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let appStoreVersion = result["version"] as? String else {
throw VersionError.invalidResponse
}
completion(appStoreVersion == currentVersion, nil)
} catch {
completion(nil, error)
}
}
task.resume()
return task
}
Called like this
DispatchQueue.global(qos: .background).async {
_ = try? VersionManager.isAppStoreVersion { (appStoreVersion, error) in
if let error = error {
print(error)
} else if let appStoreVersion = appStoreVersion, appStoreVersion == true {
// app store stuf
} else {
// other stuff
}
}
}
enum VersionError: Error {
case invalidResponse, invalidBundleInfo
}
My observation is when a device connected to Xcode, and then we open Organiser, switch to Devices pane it will list all Applications which is not installed from App Store. So what you have to do is download Xcode, then connect your device, go to Devices pane and see which all applications are installed from non-App Store sources. This is the simplest solution.