Konfigurationsprofil auf dem iPhone installieren - programmgesteuert

74

Ich möchte ein Konfigurationsprofil mit meiner iPhone-Anwendung versenden und bei Bedarf installieren.

Wir sprechen von einem Konfigurationsprofil, nicht von einem Bereitstellungsprofil.

Zunächst einmal ist eine solche Aufgabe möglich. Wenn Sie ein Konfigurationsprofil auf einer Webseite platzieren und in Safari darauf klicken, wird es installiert. Wenn Sie ein Profil per E-Mail senden und auf den Anhang klicken, wird es ebenfalls installiert. "Installiert" bedeutet in diesem Fall "Die Installationsoberfläche wird aufgerufen" - aber ich konnte nicht einmal so weit kommen.

Ich arbeitete also unter der Theorie, dass das Initiieren einer Profilinstallation das Navigieren als URL umfasst. Ich habe das Profil meinem App-Bundle hinzugefügt.

A) Zuerst habe ich [sharedApp openURL] mit der Datei: // URL in meinem Bundle ausprobiert. Kein solches Glück - nichts passiert.

B) Ich habe dann meinem Bundle eine HTML-Seite hinzugefügt, die einen Link zum Profil enthält, und diese in eine UIWebView geladen. Ein Klick auf den Link führt zu nichts. Das Laden einer identischen Seite von einem Webserver in Safari funktioniert jedoch einwandfrei - der Link ist anklickbar, das Profil wird installiert. Ich habe ein UIWebViewDelegate bereitgestellt, das auf jede Navigationsanforderung mit JA geantwortet hat - kein Unterschied.

C) Dann habe ich versucht, dieselbe Webseite aus meinem Bundle in Safari zu laden (mit [sharedApp openURL] - nichts passiert. Ich denke, Safari kann keine Dateien in meinem App-Bundle sehen.

D) Das Hochladen der Seite und des Profils auf einen Webserver ist machbar, aber ein Problem auf organisatorischer Ebene, ganz zu schweigen von einer zusätzlichen Fehlerquelle (was ist, wenn keine 3G-Abdeckung vorhanden ist? Usw.).

Meine große Frage lautet also: ** Wie installiere ich ein Profil programmgesteuert?

Und die kleinen Fragen sind: Was kann dazu führen, dass ein Link in einer UIWebView nicht anklickbar ist? Ist es möglich, eine Datei: // URL aus meinem Bundle in Safari zu laden ? Wenn nicht, gibt es einen lokalen Speicherort auf dem iPhone, an dem ich Dateien ablegen und Safari sie finden kann?

BEARBEITEN auf B): Das Problem liegt irgendwie in der Tatsache, dass wir auf ein Profil verlinken. Ich habe es von .mobileconfig in .xml umbenannt (weil es wirklich XML ist) und den Link geändert. Und der Link funktionierte in meinem UIWebView. Umbenannt zurück - das gleiche Zeug. Es sieht so aus, als würde UIWebView nur ungern anwendungsweite Aufgaben ausführen - da die Installation des Profils die App schließt. Ich habe versucht zu sagen, dass es in Ordnung ist - mittels UIWebViewDelegate - aber das hat nicht überzeugt. Gleiches Verhalten für mailto: URLs in UIWebView.

Für mailto: URLs besteht die übliche Technik darin, sie in [openURL] -Aufrufe zu übersetzen, aber das funktioniert in meinem Fall nicht ganz, siehe Szenario A.

Für itms: URLs funktioniert UIWebView jedoch wie erwartet ...

EDIT2: Es wurde versucht, eine Daten-URL über [openURL] an Safari weiterzuleiten - funktioniert nicht, siehe hier: iPhone Open DATA: Url In Safari

EDIT3: Es wurden viele Informationen darüber gefunden, wie Safari file: // URLs nicht unterstützt. UIWebView macht jedoch sehr viel. Auch Safari auf dem Simulator öffnet sie ganz gut. Das letztere ist das frustrierendste.


EDIT4: Ich habe nie eine Lösung gefunden. Stattdessen habe ich eine Zwei-Bit-Weboberfläche zusammengestellt, über die die Benutzer das per E-Mail an sie gesendete Profil bestellen können.

Seva Alekseyev
quelle
2
Hier können Sicherheitsbedenken bestehen. Apple möchte möglicherweise nicht, dass Sie eine Mobilfunkanbieter-Konfigurationsdatei innerhalb einer Anwendung ändern können, wodurch Tethering aktiviert, Voicemail deaktiviert usw. werden kann.
Brad Larson
2
Eine explizite Benutzervereinbarung ist ohnehin erforderlich. Außerdem könnte ich den Benutzer einfach von meiner App aus auf die entsprechende Webseite leiten (Option D), sodass die Steuerung nicht luftdicht ist.
Seva Alekseyev
Safari und Mail haben mehr Berechtigungen als Ihre Anwendung.
gezogen am
HI Seva, ich möchte das auch tun, hast du endlich eine Lösung bekommen?
Iphone_bharat
@Iphone_bharat: Nein, nicht wirklich. Ich habe eine Problemumgehung durchgeführt.
Seva Alekseyev

Antworten:

38

1) Installieren Sie einen lokalen Server wie RoutingHTTPServer.

2) Konfigurieren Sie den benutzerdefinierten Header:

[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"];

3) Konfigurieren Sie den lokalen Stammpfad für die mobileconfig-Datei (Dokumente):

[httpServer setDocumentRoot:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];

4) Fügen Sie Folgendes hinzu, damit der Webserver Zeit zum Senden der Datei hat:

Appdelegate.h

UIBackgroundTaskIdentifier bgTask;

Appdelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil);
    bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [application endBackgroundTask:self->bgTask];
            self->bgTask = UIBackgroundTaskInvalid;
        });
    }];
}

5) Rufen Sie in Ihrem Controller die Safari mit dem Namen der in Documents gespeicherten mobileconfig auf:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]];
Malinois
quelle
@SevaAlekseyev Hat Ihnen diese Lösung jemals geholfen?
Oleg Danu
Nein, wir haben stattdessen die gesamte Architektur überdacht. Es gibt jetzt einen öffentlichen Reverse-Proxy.
Seva Alekseyev
2
Haben Sie dies in einer AppStore-App verwendet? Wurde die .mobileconfig-Datei bereits mit einem vertrauenswürdigen Zertifikat oder mit einem selbstsignierten Zertifikat signiert? Ich würde gerne wissen, ob Apple eine App ablehnen kann, die eine selbstsignierte
mobileconfig
2
Ist es möglich, vom Safari-Browser zurück zur App zu leiten, sobald wir unser Konfigurationsprofil installiert haben?
Abilash Balasubramanian
2
@igenio SmartJoin.us wurde in den App Store aufgenommen und verwendet ein nicht signiertes Konfigurationsprofil, das bis zur Safari von einem Webserver in der App
bereitgestellt wird
28

Die Antwort von malinois hat für mich funktioniert, ABER ich wollte eine Lösung, die automatisch zur App zurückkehrt, nachdem der Benutzer die mobileconfig installiert hat.

Ich habe 4 Stunden gebraucht, aber hier ist die Lösung, die auf der Idee von Malinois basiert, einen lokalen http-Server zu haben: Sie geben HTML an Safari zurück, das sich selbst aktualisiert. Das erste Mal, wenn der Server die mobileconfig zurückgibt, und das zweite Mal, wenn er das benutzerdefinierte URL-Schema zurückgibt, um zu Ihrer App zurückzukehren. Die UX ist das, was ich wollte: Die App ruft Safari auf, Safari öffnet mobileconfig, wenn der Benutzer auf mobileconfig auf "erledigt" klickt, lädt safari Ihre App erneut (benutzerdefiniertes URL-Schema).

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.

    _httpServer = [[RoutingHTTPServer alloc] init];
    [_httpServer setPort:8000];                               // TODO: make sure this port isn't already in use

    _firstTime = TRUE;
    [_httpServer handleMethod:@"GET" withPath:@"/start" target:self selector:@selector(handleMobileconfigRootRequest:withResponse:)];
    [_httpServer handleMethod:@"GET" withPath:@"/load" target:self selector:@selector(handleMobileconfigLoadRequest:withResponse:)];

    NSMutableString* path = [NSMutableString stringWithString:[[NSBundle mainBundle] bundlePath]];
    [path appendString:@"/your.mobileconfig"];
    _mobileconfigData = [NSData dataWithContentsOfFile:path];

    [_httpServer start:NULL];

    return YES;
}

- (void)handleMobileconfigRootRequest:(RouteRequest *)request withResponse:(RouteResponse *)response {
    NSLog(@"handleMobileconfigRootRequest");
    [response respondWithString:@"<HTML><HEAD><title>Profile Install</title>\
     </HEAD><script> \
     function load() { window.location.href='http://localhost:8000/load/'; } \
     var int=self.setInterval(function(){load()},400); \
     </script><BODY></BODY></HTML>"];
}

- (void)handleMobileconfigLoadRequest:(RouteRequest *)request withResponse:(RouteResponse *)response {
    if( _firstTime ) {
        NSLog(@"handleMobileconfigLoadRequest, first time");
        _firstTime = FALSE;

        [response setHeader:@"Content-Type" value:@"application/x-apple-aspen-config"];
        [response respondWithData:_mobileconfigData];
    } else {
        NSLog(@"handleMobileconfigLoadRequest, NOT first time");
        [response setStatusCode:302]; // or 301
        [response setHeader:@"Location" value:@"yourapp://custom/scheme"];
    }
}

... und hier ist der Code, um dies von der App (dh Viewcontroller) aus aufzurufen:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:8000/start/"]];

Hoffe das hilft jemandem.

Xaphod
quelle
1
Wenn das funktioniert, dann großartig. Danke vielmals. Ich habe lange nach der Lösung gesucht.
Aparna
Ich benutze dies erfolgreich. Möglicherweise müssen Sie das Zeitintervall für das Nachladen anpassen - 400 ms hier
xaphod
Gibt es eine andere Möglichkeit, nach der Installation zur App zurückzukehren? Der Benutzer drückt die Schaltfläche "Fertig" und ich frage mich, ob es dafür einen Handler gibt.
EmilDo
Wenn Sie die native Benutzeroberfläche für die Installation eines Profils und die Schaltfläche Fertig oben rechts meinen, dann gibt es keine öffentliche API, die die Kontrolle über das ermöglicht, was mir bekannt ist.
Xaphod
1
das ist großartig. werde das ausprobieren. definitiv ein großer Gewinn für Unternehmensanwendungen.
Ninjaneer
8

Ich habe eine Klasse geschrieben, um eine mobileconfig-Datei über Safari zu installieren und dann zur App zurückzukehren. Es basiert auf der http Server Engine Swifter, die ich als gut funktionierend empfunden habe. Ich möchte meinen Code unten dafür teilen. Es ist inspiriert von mehreren Codequellen, die ich im WWW gefunden habe. Wenn Sie also Teile Ihres eigenen Codes finden, Beiträge zu Ihnen.

class ConfigServer: NSObject {

    //TODO: Don't foget to add your custom app url scheme to info.plist if you have one!

    private enum ConfigState: Int
    {
        case Stopped, Ready, InstalledConfig, BackToApp
    }

    internal let listeningPort: in_port_t! = 8080
    internal var configName: String! = "Profile install"
    private var localServer: HttpServer!
    private var returnURL: String!
    private var configData: NSData!

    private var serverState: ConfigState = .Stopped
    private var startTime: NSDate!
    private var registeredForNotifications = false
    private var backgroundTask = UIBackgroundTaskInvalid

    deinit
    {
        unregisterFromNotifications()
    }

    init(configData: NSData, returnURL: String)
    {
        super.init()
        self.returnURL = returnURL
        self.configData = configData
        localServer = HttpServer()
        self.setupHandlers()
    }

    //MARK:- Control functions

    internal func start() -> Bool
    {
        let page = self.baseURL("start/")
        let url: NSURL = NSURL(string: page)!
        if UIApplication.sharedApplication().canOpenURL(url) {
            var error: NSError?
            localServer.start(listeningPort, error: &error)
            if error == nil {
                startTime = NSDate()
                serverState = .Ready
                registerForNotifications()
                UIApplication.sharedApplication().openURL(url)
                return true
            } else {
                self.stop()
            }
        }
        return false
    }

    internal func stop()
    {
        if serverState != .Stopped {
            serverState = .Stopped
            unregisterFromNotifications()
        }
    }

    //MARK:- Private functions

    private func setupHandlers()
    {
        localServer["/start"] = { request in
            if self.serverState == .Ready {
                let page = self.basePage("install/")
                return .OK(.HTML(page))
            } else {
                return .NotFound
            }
        }
        localServer["/install"] = { request in
            switch self.serverState {
            case .Stopped:
                return .NotFound
            case .Ready:
                self.serverState = .InstalledConfig
                return HttpResponse.RAW(200, "OK", ["Content-Type": "application/x-apple-aspen-config"], self.configData!)
            case .InstalledConfig:
                return .MovedPermanently(self.returnURL)
            case .BackToApp:
                let page = self.basePage(nil)
                return .OK(.HTML(page))
            }
        }
    }

    private func baseURL(pathComponent: String?) -> String
    {
        var page = "http://localhost:\(listeningPort)"
        if let component = pathComponent {
            page += "/\(component)"
        }
        return page
    }

    private func basePage(pathComponent: String?) -> String
    {
        var page = "<!doctype html><html>" + "<head><meta charset='utf-8'><title>\(self.configName)</title></head>"
        if let component = pathComponent {
            let script = "function load() { window.location.href='\(self.baseURL(component))'; }window.setInterval(load, 600);"
            page += "<script>\(script)</script>"
        }
        page += "<body></body></html>"
        return page
    }

    private func returnedToApp() {
        if serverState != .Stopped {
            serverState = .BackToApp
            localServer.stop()
        }
        // Do whatever else you need to to
    }

    private func registerForNotifications() {
        if !registeredForNotifications {
            let notificationCenter = NSNotificationCenter.defaultCenter()
            notificationCenter.addObserver(self, selector: "didEnterBackground:", name: UIApplicationDidEnterBackgroundNotification, object: nil)
            notificationCenter.addObserver(self, selector: "willEnterForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil)
            registeredForNotifications = true
        }
    }

    private func unregisterFromNotifications() {
        if registeredForNotifications {
            let notificationCenter = NSNotificationCenter.defaultCenter()
            notificationCenter.removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil)
            notificationCenter.removeObserver(self, name: UIApplicationWillEnterForegroundNotification, object: nil)
            registeredForNotifications = false
        }
    }

    internal func didEnterBackground(notification: NSNotification) {
        if serverState != .Stopped {
            startBackgroundTask()
        }
    }

    internal func willEnterForeground(notification: NSNotification) {
        if backgroundTask != UIBackgroundTaskInvalid {
            stopBackgroundTask()
            returnedToApp()
        }
    }

    private func startBackgroundTask() {
        let application = UIApplication.sharedApplication()
        backgroundTask = application.beginBackgroundTaskWithExpirationHandler() {
            dispatch_async(dispatch_get_main_queue()) {
                self.stopBackgroundTask()
            }
        }
    }

    private func stopBackgroundTask() {
        if backgroundTask != UIBackgroundTaskInvalid {
            UIApplication.sharedApplication().endBackgroundTask(self.backgroundTask)
            backgroundTask = UIBackgroundTaskInvalid
        }
    }
}
Freshking
quelle
Hat auch für mich gearbeitet! Beachten Sie, dass der hier gezeigte Code nur für eine ältere Version von Swifter funktioniert.
Pieter Meiresone
1
Ich habe den Code in Swift 3 und den neuesten Swifter konvertiert. Funktioniert recht gut, hat jedoch Schwierigkeiten, wenn das System zu Safari zurückkehrt. Die Safari lädt die Seite neu (und gerät in eine Schleife) und der Dialog zum Zurückkehren zur App verschwindet (und Safari muss beendet werden). Vielen Dank
Tom
4
Meine Swift 3-Zusatzbearbeitung wurde von einigen intelligenten Leuten abgelehnt, die nichts über ios und / oder Swift (und den Unterschied zwischen Version 2 und 3) wissen. Ich habe es stattdessen in gist.github.com/3ph/beb43b4389bd627a271b1476a7622cc5 eingefügt . Ich weiß, dass das Posten von Links gegen SO verstößt, aber anscheinend auch einige Leute.
Tom
1
Ihr Kern ist wirklich großartig und hat einmal für mich gearbeitet. Danach muss ich den Safari-Cache zurücksetzen, damit er wieder funktioniert. Meine Tests mit zusätzlichen Metainformationen und Headerfeldern haben es nicht gelöst. Die einzige Lösung, die ich gefunden habe, besteht darin, den "install" -String in setupHandlers () zufällig zu sortieren.
ObjectAlchemist
2
Ok, ich habe mein Problem gelöst. Es funktioniert nur mit einem Schema. Wenn Sie eine leere Zeichenfolge als Rückgabe-URL angeben, wird das beschriebene Verhalten angezeigt. Bei einem gültigen Schema wird der Dialog angezeigt, danach wird die Safari unterbrochen (beim Abbrechen des Dialogs). Anstatt eine .MovedPermanently (self.returnURL) zurückzugeben, schlage ich vor, eine Webseite mit einer Schaltfläche darin zurückzugeben. Im Fehlerfall kann der Benutzer die Seite schließen als.
ObjectAlchemist
4

Ich denke, was Sie suchen, ist "Over the Air Enrollment" mit dem Simple Certificate Enrollment Protocol (SCEP). Lesen Sie den OTA-Registrierungsleitfaden und den Abschnitt SCEP Payload des Enterprise Deployment Guide .

Laut der Gerätekonfigurationsübersicht haben Sie nur vier Optionen:

  • Desktop-Installation über USB
  • Email Anhang)
  • Website (über Safari)
  • Registrierung und Verteilung über Funk
slf
quelle
Sieht aus wie eine schickere Version meiner Option D :) Gute Entdeckung.
Seva Alekseyev
Es gibt Unmengen von Produkten, die dies für Sie tun, ohne dass Sie Ihre eigenen rollen müssen
slf
1

Haben Sie versucht, dass die App dem Benutzer beim ersten Start das Konfigurationsprofil per E-Mail sendet?

- (IBAction) mailConfigProfile {
     MFMailComposeViewController * email = [[MFMailComposeViewController alloc] init];
     email.mailComposeDelegate = self;

     [email setSubject: @ "Konfigurationsprofil meiner App"];

     NSString * filePath = [[NSBundle mainBundle] pathForResource: @ "MyAppConfig" ofType: @ "mobileconfig"];  
     NSData * configData = [NSData dataWithContentsOfFile: filePath]; 
     [E-Mail addAttachmentData: configData mimeType: @ "application / x-apple-aspen-config" Dateiname: @ "MyAppConfig.mobileconfig"];

     NSString * emailBody = @ "Tippen Sie auf den Anhang, um das Konfigurationsprofil für Meine App zu installieren.";
     [email setMessageBody: emailBody isHTML: YES];

     [self presentModalViewController: E-Mail animiert: JA];
     [E-Mail-Veröffentlichung];
}}

Ich habe es zu einer IBAction gemacht, falls Sie es an eine Schaltfläche binden möchten, damit der Benutzer es jederzeit erneut an sich selbst senden kann. Beachten Sie, dass ich im obigen Beispiel möglicherweise nicht den richtigen MIME-Typ habe. Sie sollten dies überprüfen.

Smountcastle
quelle
Ich werde es versuchen. Ich bin nicht sicher, ob ein E-Mail-Anhang während der E-Mail-Erstellung geöffnet werden kann. Außerdem wird es schmerzhaft sein, die Benutzer zu unterweisen. Hat "verzweifelte Problemumgehung" überall geschrieben ...
Seva Alekseyev
Der Benutzer würde den Anhang beim Verfassen nicht öffnen. Der Workflow wäre das Starten Ihrer App. Er stellt fest, dass das Konfigurationsprofil nicht installiert ist. Er führt die oben beschriebenen Schritte aus, um die E-Mail-Komposition zu initiieren. Der Benutzer gibt seine E-Mail-Adresse ein und drückt auf Senden. Dann öffnen sie die Mail-App, laden die E-Mail herunter und klicken auf den Anhang, um sie zu installieren. Ich stimme zu, dass es wie eine verzweifelte Problemumgehung scheint. Alternativ können Sie herausfinden, wie Mail die Datei application / x-apple-aspen-config versendet, und dies einfach tun (obwohl es sich möglicherweise um eine private API handelt, weiß ich nicht).
Smountcastle
1

Hosten Sie die Datei einfach auf einer Website mit der Erweiterung * .mobileconfig und setzen Sie den MIME-Typ auf application / x-apple-aspen-config. Der Benutzer wird aufgefordert, aber wenn er akzeptiert, sollte das Profil installiert werden.

Sie können diese Profile nicht programmgesteuert installieren.

Brandon
quelle
0

Auf dieser Seite wird erläutert, wie Sie Bilder aus Ihrem Bundle in einer UIWebView verwenden.

Vielleicht würde das auch für ein Konfigurationsprofil funktionieren.

Jon-Eric
quelle
1
Nee. Und der lustige Teil ist, dass es irgendwie die Besonderheiten des Profils sind. Wenn ich eine Textdatei mit einem Link dazu bereitstelle, navigiert UIWebView wie erwartet dorthin.
Seva Alekseyev
0

Ich habe mir eine andere Möglichkeit überlegt, wie es funktionieren könnte (leider habe ich kein Konfigurationsprofil zum Testen):

// Erstellen Sie einen UIViewController, der eine UIWebView enthält
- (void) viewDidLoad {
    [super viewDidLoad];
    // Weist das WebView an, das Konfigurationsprofil zu laden
    [self.webView loadRequest: [NSURLRequest requestWithURL: self.cpUrl]];
}}

// Dann in Ihrem Code, wenn Sie sehen, dass das Profil nicht installiert wurde:
ConfigProfileViewController * cpVC = 
        [[ConfigProfileViewController alloc] initWithNibName: @ "MobileConfigView"
                                                      Bündel: Null];
NSString * cpPath = [[NSBundle mainBundle] pathForResource: @ "configProfileName"
                                                   ofType: @ ". mobileconfig"];
cpVC.cpURL = [NSURL URLWithString: cpPath];
// Wenn Ihre App über einen Navigationscontroller verfügt, können Sie die Ansicht einfach verschieben 
// on und es wird Ihre mobile Konfiguration geladen (die es installieren sollte).
[self.navigationController pushViewController: Controller animiert: JA];
[cpVC-Veröffentlichung];
Smountcastle
quelle
Das ist eine leichte Umformulierung von Option B - anstelle von HTML, dann Link zum Profil, sofort Link zum Profil. Ich glaube, ich habe es auf dem Weg erfolglos versucht.
Seva Alekseyev
0

Sie sind sich nicht sicher, warum Sie ein Konfigurationsprofil benötigen, können jedoch versuchen, mit diesem Delegaten aus der UIWebView zu hacken:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    if (navigationType == UIWebViewNavigationTypeLinkClicked) {
        //do something with link clicked
        return NO;
    }
    return YES;
}

Andernfalls können Sie die Installation von einem sicheren Server aus aktivieren.

Hoang Pham
quelle
Ist diese Frage noch unbeantwortet?
Hoang Pham
0

Dies ist ein großartiger Thread und insbesondere der oben erwähnte Blog .

Für diejenigen, die Xamarin machen, sind hier meine 2 Cent. Ich habe das Blattzertifikat als Inhalt in meine App eingebettet und es dann mit dem folgenden Code überprüft:

        using Foundation;
        using Security;

        NSData data = NSData.FromFile("Leaf.cer");
        SecCertificate cert = new SecCertificate(data);
        SecPolicy policy = SecPolicy.CreateBasicX509Policy();
        SecTrust trust = new SecTrust(cert, policy);
        SecTrustResult result = trust.Evaluate();
        return SecTrustResult.Unspecified == result; // true if installed

(Mann, ich liebe es, wie sauber dieser Code im Vergleich zu einer der Apple-Sprachen ist)

Eliot Gillum
quelle
Verwenden Sie private APIs? oder haben Xamarin-Entwickler den Drecksjob gemacht?
Ohad Cohen
Das Konfigurationsprofil übernimmt das schwere Heben, es sind keine privaten APIs erforderlich. Was schmutzig ist, ist der Vorgang, bei dem ein Benutzer durch die Installation eines Profils aus einer App geführt wird. IOS kann die Datei nur aus Safari oder Mail verarbeiten. Dies ist nicht gerade die reibungsloseste Erfahrung, und es macht besonders viel Spaß, den Benutzer anschließend wieder in die App zu integrieren.
Eliot Gillum