在iPhone上安assembly置文件 – 以编程方式

我想用我的iPhone应用程序发送一个configuration文件,并根据需要进行安装。

请注意,我们正在讨论configuration文件,而不是configuration文件。

首先,这样的任务是可能的。 如果你在网页上放置一个configuration文件,并从Safari中点击它,它将被安装。 如果您通过电子邮件发送configuration文件并单击附件,它也会安装。 在这种情况下,“安装”的意思是“安装用户界面被调用” – 但我甚至不能得到那么多。

所以我正在根据这个理论进行工作,即启动一个configuration文件安装涉及导航到一个URL。 我添加了configuration文件到我的应用程序包。

A)首先,我使用file:// URL将[sharedApp openURL]放入我的包中。 没有这样的运气 – 没有任何反应

B)然后,我添加了一个HTML页面到我的包中有一个链接到configuration文件,并将其加载到一个UIWebView。 点击链接什么也不做。 然而,在Safari中从Web服务器加载一个相同的页面,工作正常 – 链接是可点击的,configuration文件安装。 我提供了一个UIWebViewDelegate,对每个导航请求都回答YES – 没有任何区别。

C)然后,我尝试从我的包在Safari中加载相同的网页(使用[sharedApp openURL] – 没有任何反应,我猜Safari看不到我的应用程序包内的文件。

D)在Web服务器上上传页面和configuration文件是可行的,但在组织层面上是一个痛苦,更不用说额外的失败来源(如果没有3G覆盖?)等等。

所以我最大的问题是:**如何以编程方式安assembly置文件?

小问题是:什么可以使链接不可点击的UIWebView内? 是否有可能在Safari中加载我的包中的file:// URL? 如果没有,iPhone上有本地位置,我可以放置文件和Safari可以find它们?

编辑B):问题是在某种程度上,我们正在链接到一个configuration文件。 我将它从.mobileconfig重命名为.xml(因为它真的是XML),所以改变了链接。 链接工作在我的UIWebView。 重命名它 – 相同的东西。 它看起来好像UIWebView是不情愿做应用程序的东西 – 因为安assembly置文件closures应用程序。 我试图告诉它,这是可以的 – 通过UIWebViewDelegate的手段 – 但没有说服。 对于mailto:UIWebView中的URLs的相同行为。

对于mailto: URL来说,常用的技术是将它们翻译成[openURL]调用,但是这对我的情况并不适用,请参阅schemeA.

对于itms:URL,但是,UIWebView按预期工作…

编辑2:试图通过[openURL]喂养一个数据url到Safari – 不工作,看到这里: iPhone打开数据:在Safari

EDIT3:发现了很多关于Safari如何不支持file:// URLs的信息。 UIWebView,但是,非常多。 另外,模拟器上的Safari打开它们就好了。 后者是最令人沮丧的。


编辑4:我从来没有find一个解决scheme。 相反,我将两位Web界面放在一起,用户可以通过电子邮件向他们发送configuration文件。

1)像RoutingHTTPServer一样安装本地服务器

2)configuration自定义标题:

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

3)configurationmobileconfig文件(文档)的本地根path:

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

4)为了让Web服务器有时间发送文件,添加:

 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)在您的控制器中,使用存储在Documents中的mobileconfig名称呼叫safari:

 [[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]]; 

malinois的答案对我有效,但是,我想要一个解决scheme,在用户安装mobileconfig后自动回到应用程序。

我花了4个小时,但是这里的解决scheme是build立在malinois关于拥有一个本地http服务器的基础之上的:您将HTML返回到自助刷新的Safari浏览器; 服务器第一次返回mobileconfig,第二次返回自定义url-scheme返回到你的应用程序。 用户体验是我想要的:应用程序调用safari,safari打开mobileconfig,当用户在mobileconfig上点击“完成”,然后Safari再次加载您的应用程序(自定义urlscheme)。

 - (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"]; } } 

…这里是从应用程序(即viewcontroller)调用的代码:

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

希望这有助于某人。

我写了一个类,通过Safari安装mobileconfig文件,然后返回到应用程序。 它依赖于我发现运行良好的http服务器引擎Swifter 。 我想在下面分享我的代码。 它受到我在www中浮动的多个代码源的启发。 所以,如果你find你自己的代码片段,贡献给你。

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

我想使用简单证书注册协议(SCEP)来查找“空中注册”。 查看“ OTA注册指南”和“ 企业部署指南”的SCEP Payload部分。

根据设备configuration概述,您只有四个选项:

  • 通过USB进行桌面安装
  • 电子邮件附件)
  • 网站(通过Safari)
  • 无线注册和分配

本页面介绍如何在UIWebView中使用您的包中的图像。

也许这同样适用于configuration文件。

你有没有尝试只是让应用程序邮件用户configuration文件第一次启动?

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

      [电子邮件setSubject:@“我的应用程序的configuration文件”];

      NSString * filePath = [[NSBundle mainBundle] pathForResource:@“MyAppConfig”ofType:@“mobileconfig”];  
      NSData * configData = [NSData dataWithContentsOfFile:filePath]; 
      [email addAttachmentData:configData mimeType:@“application / x-apple-aspen-config”fileName:@“MyAppConfig.mobileconfig”];

      NSString * emailBody = @“请点击附件为我的应用程序安assembly置文件。
      [email setMessageBody:emailBody isHTML:YES];

      [self presentModalViewController:email animated:YES];
      [电子邮件发布];
 }

如果你想把它绑定到一个button上,那么我可以把它作为一个IBAction,这样用户可以随时把它重新发送给自己。 请注意,我可能没有在上面的例子中正确的MIMEtypes,你应该validation。

我虽然有另一种方式,可以工作(不幸的是我没有一个configuration文件来testing):

 //创build一个包含UIWebView的UIViewController
 - (void)viewDidLoad {
     [super viewDidLoad];
     //告诉webView加载configuration文件
     [self.webView loadRequest:[NSURLRequest requestWithURL:self.cpUrl]];
 }

 //然后在你的代码中,当你看到configuration文件没有被安装时:
 ConfigProfileViewController * cpVC = 
         [[ConfigProfileViewController alloc] initWithNibName:@“MobileConfigView”
                                                      捆绑:无];
 NSString * cpPath = [[NSBundle mainBundle] pathForResource:@“configProfileName”
                                                    ofType:@ “mobileconfig”];
 cpVC.cpURL = [NSURL URLWithString:cpPath];
 //然后,如果你的应用程序有一个导航控制器,你可以推视图 
 / /它会加载您的手机configuration(应该安装它)。
 [self.navigationController pushViewController:controller animated:YES];
 [cpVC发布];

不知道为什么你需要一个configuration文件,但你可以尝试从UIWebView的这个委托破解:

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

否则,您可以考虑从安全服务器启用安装。

只需将文件放置在扩展名为* .mobileconfig的网站上,并将MIMEtypes设置为application / x-apple-aspen-config。 用户将被提示,但如果他们接受configuration文件应该安装。

您不能以编程方式安装这些configuration文件。

这是一个很好的主题,尤其是上面提到的博客。

对于那些做Xamarin的,这是我的2美分。 我将叶证书作为内容embedded到我的应用程序中,然后使用以下代码进行检查:

  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 

(男人,我喜欢这个代码是干净的,而不是苹果的语言)