我想用我的iPhone应用程序发送配置文件,并在需要时安装它.
请注意,我们谈论的是配置文件,而不是配置文件.
首先,这样的任务是可能的.如果您在网页上放置配置文件并从Safari中单击它,它将被安装.如果您通过电子邮件发送配置文件并单击附件,它也将安装.在这种情况下,"已安装"意味着"调用安装UI" - 但我甚至无法做到这一点.
所以我的理论是,启动配置文件安装涉及以URL的形式导航到它.我将个人资料添加到了我的应用包中.
A)首先,我尝试将[sharedApp openURL]与file:// URL一起放入我的包中.没有这样的运气 - 什么都没发生
B)然后我在我的包中添加了一个HTML页面,其中包含指向配置文件的链接,并将其加载到UIWebView中.点击链接什么都不做.但是,从Safari中的Web服务器加载相同的页面工作正常 - 链接可单击,配置文件安装.我提供了一个UIWebViewDelegate,对每个导航请求回答"是" - 没有区别.
C)然后我尝试从我的包中加载相同的网页在Safari中(使用[sharedApp openURL] - 没有任何反应.我猜,Safari无法看到我的应用包内的文件.
D)在Web服务器上上传页面和配置文件是可行的,但在组织层面上却很痛苦,更不用说额外的失败源(如果没有3G覆盖等等).
所以我的重要问题是:**如何以编程方式安装配置文件?
小问题是:什么可以使UIWebView中的链接不可点击?是否可以在Safari中从我的包中加载file:// URL ?如果没有,iPhone上是否有本地位置可以放置文件,Safari可以找到它们?
编辑B):问题是某种程度上我们正在链接到个人资料.我将它从.mobileconfig重命名为.xml(因为它真的是XML),改变了链接.这个链接在我的UIWebView中工作.重新命名 - 同样的东西.看起来UIWebView似乎不愿意做应用程序范围的事情 - 因为安装配置文件会关闭应用程序.我尝试通过UIWebViewDelegate告诉它没关系 - 但这并没有让人信服.mailto的相同行为:UIWebView中的URL.
对于mailto: URL,常见的技巧是将它们转换为[openURL]调用,但这对我的情况并不适用,请参阅方案A.
对于itms:URL,然而,UIWebView按预期工作...
EDIT2:尝试通过[openURL]向Safari提供数据URL - 不起作用,请参阅此处:iPhone打开数据:Safari中的URL
EDIT3:发现了很多关于Safari如何不支持file:// URL的信息.然而,UIWebView非常有用.此外,模拟器上的Safari打开它们就好了.后一点是最令人沮丧的.
编辑4:我从未找到解决方案.相反,我将一个两位的Web界面组合在一起,用户可以通过电子邮件订购这些配置文件.
1)安装RoutingHTTPServer等本地服务器
2)配置自定义标头:
[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"];
3)配置mobileconfig文件的本地根路径(Documents):
[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的答案对我有用,但是,我想要一个在用户安装mobileconfig后自动返回应用程序的解决方案.
我花了4个小时,但这里是解决方案,建立在malinois的想法,即拥有一个本地的http服务器:你将HTML返回到自我更新的safari; 第一次服务器返回mobileconfig,第二次返回自定义url-scheme以返回到您的应用程序.用户体验是我想要的:应用程序调用safari,safari打开mobileconfig,当用户在mobileconfig上点击"完成",然后safari再次加载你的应用程序(自定义网址方案).
- (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:@"Profile Install \ "]; } - (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"]; } }
...这里是从app调用的代码(即viewcontroller):
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:8000/start/"]];
希望这有助于某人.
我编写了一个类,用于通过Safari安装mobileconfig文件,然后返回到应用程序.它依赖于http服务器引擎Swifter,我发现它运行良好.我想在下面分享我的代码来做这件事.它的灵感来自于我在www中发现的多个代码源.因此,如果您找到自己的代码,贡献给您.
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 = "" + "\(self.configName) " if let component = pathComponent { let script = "function load() { window.location.href='\(self.baseURL(component))'; }window.setInterval(load, 600);" page += "" } page += "" 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 } } }