SwiftUI 中未调用 Obj-C 函数
Obj-C function not being called in SwiftUI
我一直在开发一款可以安装 JSON 列表中的任何应用程序的应用程序。控制台中未打印任何错误,但未调用 Obj-C 文件中的方法。
我被允许使用下面的 Obj-C 代码,同时我已经让它在非 SwiftUI 应用程序中工作。我很困惑为什么它在这里不能像在普通 Swift UIKit 应用程序中那样工作。
代码如下。
主视图
struct ContentView: View {
var installer: AppInstaller?
@State var externalUrl = ""
var body: some View {
ZStack(alignment: .top) {
TrackableScrollView(.vertical, showIndicators: true, contentOffset: $scrollViewContentOffset) {
VStack(alignment: .trailing, spacing: 15) {
VStack(spacing: 10) {
ScrollView(.horizontal, content: {
HStack(spacing: 10) {
ForEach(featuredApps.apps, id: \.id) { app in
FeaturedAppView(image: app.image, download: app.ipa)
.onTapGesture {
UserDefaults.standard.set(app.name, forKey: "apptitle")
print("Installing App")
externalUrl = app.ipa
// Method below doesn't seem to be working
installer?.installApp(withURL: externalUrl) { error in
if error != nil {
print("App Install Failed.")
}
}
}
}
}
.padding(.leading, 10)
}).frame(height: 120)
}
}
}
}
Objective-C 类
//
// Bridging-header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#include "AppInstaller.h"
...
//
// AppInstaller.m
// App Installer
//
// Created by CreatureSurvive on 2017-06-29.
// Copyright © 2017 Low Budget Animation Studios. All rights reserved.
//
#import "AppInstaller.h"
@implementation AppInstaller {
NSString *_url;
}
- (void)installAppWithURL:(NSString *)downloadLink completionHandler:(void (^)(NSError *))completion {
// no need to create multiple URLsessions, lets cache this for the lifetime of the app
static NSURLSession *session;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration];
});
// create our task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[self manifestPostRequestWithURL:downloadLink]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
// the task completed without error do things
if (!error)
{
// parse the headers into a dictionary so we can get the download link from them
NSDictionary *headers = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
// attempt to download our app
[self downloadAppWithManifestURL:[@"https://file.io/" stringByAppendingString:headers[@"key"]]];
}
// return the completion
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}];
// make sure we start the task
[dataTask resume];
}
- (NSData *)manifestDataWithURL:(NSString *)downloadLink {
// setup local variables
NSString *appBundlePath = [[NSBundle mainBundle] pathForResource:@"general" ofType:@"plist"];
NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSFileManager *fileManager = [NSFileManager defaultManager];
// create plist if it doesnt exist in our documents directory;
if (![fileManager fileExistsAtPath:[documentsDirectory stringByAppendingPathComponent:@"general.plist"]])
{
//copy new file to documents directory
[fileManager copyItemAtPath:appBundlePath toPath:[documentsDirectory stringByAppendingPathComponent:@"general.plist"] error:nil];
}
NSString *documentsDirectoryPlistPath = [documentsDirectory stringByAppendingPathComponent:@"general.plist"];
// check if the our last url is equal to the new url
if ((_url != nil) &! [_url isEqualToString:downloadLink])
{
// no need to update the manifest just return it
return [fileManager contentsAtPath:documentsDirectoryPlistPath];
}
// get the existing manifest as a dictionary to edit
NSMutableDictionary *manifestDict = [[NSMutableDictionary alloc] initWithContentsOfFile:documentsDirectoryPlistPath];
// sets the url of the manifest to the requested download link
manifestDict[@"items"][0][@"assets"][0][@"url"] = downloadLink;
NSDictionary *apptitle = [[NSUserDefaults standardUserDefaults] objectForKey:@"apptitle"];
manifestDict[@"items"][0][@"metadata"][@"title"] = apptitle;
[manifestDict writeToFile:documentsDirectoryPlistPath atomically:YES];
// return the path to our updated manifest
return [fileManager contentsAtPath:documentsDirectoryPlistPath];
}
- (NSURLRequest *)manifestPostRequestWithURL:(NSString *)downloadLink
{
NSString *boundary = [[NSUUID UUID] UUIDString];
// set body of the request and insert our manifest data
// this is ugly, but its the only way i've found that works
NSMutableData *body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Disposition:form-data; name=\"file\"; filename=\"general.plist\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/x-plist\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[self manifestDataWithURL:downloadLink]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// setup request
NSMutableURLRequest *request = [NSMutableURLRequest new];
[request setURL:[NSURL URLWithString:@"https://file.io/?expires=1d"]];
[request setHTTPMethod:@"POST"];
[request addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary] forHTTPHeaderField: @"Content-Type"];
[request setHTTPBody:body];
return [request copy];
}
- (void)downloadAppWithManifestURL:(NSString *)downloadLink
{
// NSLog(@"path: %@", downloadLink);
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[@"itms-services://?action=download-manifest&url=" stringByAppendingString:downloadLink]]];
});
}
@end
...
//
// Created by Dana Buehre on 6/29/17.
// Copyright (c) 2017 Low Budget Animation Studios. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppInstaller : NSObject
- (void)installAppWithURL:(NSString *)downloadLink completionHandler:(void (^)(NSError *))completion;
@end
经过进一步检查,我发现我的项目文件中缺少一个 plist。此 plist 是要安装的应用程序的占位符。创建它后,我的问题就解决了。我很抱歉让人们浪费时间试图帮助我。
我一直在开发一款可以安装 JSON 列表中的任何应用程序的应用程序。控制台中未打印任何错误,但未调用 Obj-C 文件中的方法。
我被允许使用下面的 Obj-C 代码,同时我已经让它在非 SwiftUI 应用程序中工作。我很困惑为什么它在这里不能像在普通 Swift UIKit 应用程序中那样工作。
代码如下。
主视图
struct ContentView: View {
var installer: AppInstaller?
@State var externalUrl = ""
var body: some View {
ZStack(alignment: .top) {
TrackableScrollView(.vertical, showIndicators: true, contentOffset: $scrollViewContentOffset) {
VStack(alignment: .trailing, spacing: 15) {
VStack(spacing: 10) {
ScrollView(.horizontal, content: {
HStack(spacing: 10) {
ForEach(featuredApps.apps, id: \.id) { app in
FeaturedAppView(image: app.image, download: app.ipa)
.onTapGesture {
UserDefaults.standard.set(app.name, forKey: "apptitle")
print("Installing App")
externalUrl = app.ipa
// Method below doesn't seem to be working
installer?.installApp(withURL: externalUrl) { error in
if error != nil {
print("App Install Failed.")
}
}
}
}
}
.padding(.leading, 10)
}).frame(height: 120)
}
}
}
}
Objective-C 类
//
// Bridging-header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#include "AppInstaller.h"
...
//
// AppInstaller.m
// App Installer
//
// Created by CreatureSurvive on 2017-06-29.
// Copyright © 2017 Low Budget Animation Studios. All rights reserved.
//
#import "AppInstaller.h"
@implementation AppInstaller {
NSString *_url;
}
- (void)installAppWithURL:(NSString *)downloadLink completionHandler:(void (^)(NSError *))completion {
// no need to create multiple URLsessions, lets cache this for the lifetime of the app
static NSURLSession *session;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration];
});
// create our task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:[self manifestPostRequestWithURL:downloadLink]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
// the task completed without error do things
if (!error)
{
// parse the headers into a dictionary so we can get the download link from them
NSDictionary *headers = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
// attempt to download our app
[self downloadAppWithManifestURL:[@"https://file.io/" stringByAppendingString:headers[@"key"]]];
}
// return the completion
dispatch_async(dispatch_get_main_queue(), ^{
completion(error);
});
}];
// make sure we start the task
[dataTask resume];
}
- (NSData *)manifestDataWithURL:(NSString *)downloadLink {
// setup local variables
NSString *appBundlePath = [[NSBundle mainBundle] pathForResource:@"general" ofType:@"plist"];
NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSFileManager *fileManager = [NSFileManager defaultManager];
// create plist if it doesnt exist in our documents directory;
if (![fileManager fileExistsAtPath:[documentsDirectory stringByAppendingPathComponent:@"general.plist"]])
{
//copy new file to documents directory
[fileManager copyItemAtPath:appBundlePath toPath:[documentsDirectory stringByAppendingPathComponent:@"general.plist"] error:nil];
}
NSString *documentsDirectoryPlistPath = [documentsDirectory stringByAppendingPathComponent:@"general.plist"];
// check if the our last url is equal to the new url
if ((_url != nil) &! [_url isEqualToString:downloadLink])
{
// no need to update the manifest just return it
return [fileManager contentsAtPath:documentsDirectoryPlistPath];
}
// get the existing manifest as a dictionary to edit
NSMutableDictionary *manifestDict = [[NSMutableDictionary alloc] initWithContentsOfFile:documentsDirectoryPlistPath];
// sets the url of the manifest to the requested download link
manifestDict[@"items"][0][@"assets"][0][@"url"] = downloadLink;
NSDictionary *apptitle = [[NSUserDefaults standardUserDefaults] objectForKey:@"apptitle"];
manifestDict[@"items"][0][@"metadata"][@"title"] = apptitle;
[manifestDict writeToFile:documentsDirectoryPlistPath atomically:YES];
// return the path to our updated manifest
return [fileManager contentsAtPath:documentsDirectoryPlistPath];
}
- (NSURLRequest *)manifestPostRequestWithURL:(NSString *)downloadLink
{
NSString *boundary = [[NSUUID UUID] UUIDString];
// set body of the request and insert our manifest data
// this is ugly, but its the only way i've found that works
NSMutableData *body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Disposition:form-data; name=\"file\"; filename=\"general.plist\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/x-plist\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[self manifestDataWithURL:downloadLink]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// setup request
NSMutableURLRequest *request = [NSMutableURLRequest new];
[request setURL:[NSURL URLWithString:@"https://file.io/?expires=1d"]];
[request setHTTPMethod:@"POST"];
[request addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary] forHTTPHeaderField: @"Content-Type"];
[request setHTTPBody:body];
return [request copy];
}
- (void)downloadAppWithManifestURL:(NSString *)downloadLink
{
// NSLog(@"path: %@", downloadLink);
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[@"itms-services://?action=download-manifest&url=" stringByAppendingString:downloadLink]]];
});
}
@end
...
//
// Created by Dana Buehre on 6/29/17.
// Copyright (c) 2017 Low Budget Animation Studios. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppInstaller : NSObject
- (void)installAppWithURL:(NSString *)downloadLink completionHandler:(void (^)(NSError *))completion;
@end
经过进一步检查,我发现我的项目文件中缺少一个 plist。此 plist 是要安装的应用程序的占位符。创建它后,我的问题就解决了。我很抱歉让人们浪费时间试图帮助我。