如何使用 NSBundle 从下载的包中加载 NIB 并回退到主包
How to use NSBundle to load NIBs from downloaded bundle and fall back to main bundle
这是基本问题:我需要一种视图加载机制,尝试从文档中下载的 NIB 创建视图,如果无法创建视图,则回退到主包。
我经过大量研究和反复试验,才让它发挥作用,所以我想与其他人分享解决方案。
步骤如下:
1) 以正常方式在主包中创建 NIB。我建议使用指向文件夹的文件组,以将所有将用于下载包的资产放在一起。我们称它为 NIB_Resources.
要在项目导航器中的文件夹下创建 NIB:
- 右键单击文件组。
- 选择新文件…
- 选择用户界面,然后select查看。
2) 为资产包添加一个目标。
- 单击目标面板中的 +。
- Select Framework and Library 类别中的 Bundle 模板,在 OS X 下。它属于该类别,因为它是一种资产库。
- 对于产品名称,输入您要调用资产库的名称。保留其他所有内容,select 您要将产品添加到的项目。
- 在新产品的构建设置中,将 Base SDK 从 Latest OS X 更改为 Latest iOS。
3) 将资产添加到资产包中。
- Select 新产品的复制捆绑资源构建阶段。
- 拖放要包含在捆绑包中的资产。如果可以添加资产,光标将显示一个 + 图标。
4) 构建资产包。
- Select 新创建目标的方案。
- Select iOS 设备作为构建目标。
- 建造。
- 如果此操作正确,新捆绑包的产品应该在项目导航器的“产品”文件夹下从红色变为黑色。
5) 压缩资源包
- 在Products文件夹中右键点击新建的产品,select在Finder中显示
- 将包复制到某个位置,例如专用于此项目的目录中的某个文件夹。
- 右键单击包含捆绑包和其他可能的 NIB 文件、图像等的目录
- Select压缩。
6) 将资产包上传到您可以下载的位置。
7) 下载压缩资源包:
下面的代码隐藏在便利函数中,在处理大量低级文件系统操作的便利文件中。 FS 前缀指的是文件系统。
FSDownloadTempFileWithURLString
可以在返回到主线程之前从辅助线程调用。
我使用NSData
同步方法,initWithContentsOfURL:
,因为调用很可能是从辅助线程进行的。基本策略是将 zip 文件下载到一个临时位置(Caches 目录通常是一个很好的选择),然后再进行任何必要的准备并将文件解压缩到 Documents 目录。在头文件中定义内联静态操作的方法是从 Apple 采用的。
//Documents directory
#define FSDocumentsDirectory [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
//Caches directory
#define FSCachesDirectory [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]
/**
* Get the path to a file under the caches directory. The given filename can have
* multiple file separators.
*/
inline static NSString* FSCachesPath(NSString *filename)
{
return [FSCachesDirectory stringByAppendingPathComponent:filename];
}
/**
* Download a file from a specified URL, and copy to the caches directory, to the same filename as the URL's filename.
*
* Returns the result.
*/
inline static BOOL FSDownloadTempFileWithURLString(NSString *urlString)
{
NSData *data = getDataFromURL(urlString);
if (!data) {
//Error already logged
return FALSE;
}
NSString *path = FSCachesDirectory;
NSString *filename = [urlString lastPathComponent];
path = [path stringByAppendingPathComponent:filename];
NSError *error = nil;
if (![data writeToFile:path options:NSDataWritingAtomic error:&error]) {
NSLog(@"Error occurred while trying to write the file to: %@\n", path);
NSLog(@"%@", error);
return FALSE;
}
return TRUE;
}
/**
* Get the data from a specified URL.
*/
inline static NSData* getDataFromURL(NSString *urlString)
{
NSString *escapedUrlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:escapedUrlString];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
if (!data) {
debugLog(@"Could not download file: %@", escapedUrlString);
return nil;
}
return data;
}
8) 使用 SSZipArchive
或类似的东西将下载的文件解压到 Documents 目录:
NSString *cachesPath = FSCachesPath(URL_RESOURCE_FILENAME);
if (![SSZipArchive unzipFileAtPath:cachesPath toDestination:FSDocumentsDirectory delegate:nil]) {
return;
}
9) 最后,尝试从 Documents 目录中的捆绑包中的 NIB 文件加载视图,然后回退到主捆绑包。
可以从尝试从 Nib 加载视图的视图控制器调用下面的 FSResourceNib
操作,如下所示:
UIView *view = FSResourceNib(ResourcesBundle, nibName, self);
/**
* Get a NIB from the documents directory, otherwise fall back to the bundle.
*
* Returns nil, if an error occurs.
*/
inline static UIView* FSResourceNib(NSString *bundleFilename, NSString *nibName, id owner)
{
UIView *resourceView = nil;
//If bundld doesn't exist in the documents path, then use the main bundle
NSString *resourcePath = FSDocumentsPath(bundleFilename);
if ([[NSFileManager defaultManager] fileExistsAtPath:resourcePath]) {
NSBundle *resourceBundle = [NSBundle bundleWithPath:resourcePath];
@try {
//Try to load the NIB from the given bundle
resourceView = [[resourceBundle loadNibNamed:nibName owner:owner options:nil] lastObject];
}
@catch (NSException *exception) {
//do nothing - will try main bundle
}
}
//If loading from the given bundle failed, try loading from the main bundle
if (!resourceView) {
NSBundle *resourceBundle = [NSBundle mainBundle];
@try {
resourceView = [[resourceBundle loadNibNamed:nibName owner:owner options:nil] lastObject];
}
@catch (NSException *exception) {
//do nothing - will return nil, indicating an error occurred
}
}
return resourceView;
}
这是基本问题:我需要一种视图加载机制,尝试从文档中下载的 NIB 创建视图,如果无法创建视图,则回退到主包。
我经过大量研究和反复试验,才让它发挥作用,所以我想与其他人分享解决方案。
步骤如下:
1) 以正常方式在主包中创建 NIB。我建议使用指向文件夹的文件组,以将所有将用于下载包的资产放在一起。我们称它为 NIB_Resources.
要在项目导航器中的文件夹下创建 NIB:
- 右键单击文件组。
- 选择新文件…
- 选择用户界面,然后select查看。
2) 为资产包添加一个目标。
- 单击目标面板中的 +。
- Select Framework and Library 类别中的 Bundle 模板,在 OS X 下。它属于该类别,因为它是一种资产库。
- 对于产品名称,输入您要调用资产库的名称。保留其他所有内容,select 您要将产品添加到的项目。
- 在新产品的构建设置中,将 Base SDK 从 Latest OS X 更改为 Latest iOS。
3) 将资产添加到资产包中。
- Select 新产品的复制捆绑资源构建阶段。
- 拖放要包含在捆绑包中的资产。如果可以添加资产,光标将显示一个 + 图标。
4) 构建资产包。
- Select 新创建目标的方案。
- Select iOS 设备作为构建目标。
- 建造。
- 如果此操作正确,新捆绑包的产品应该在项目导航器的“产品”文件夹下从红色变为黑色。
5) 压缩资源包
- 在Products文件夹中右键点击新建的产品,select在Finder中显示
- 将包复制到某个位置,例如专用于此项目的目录中的某个文件夹。
- 右键单击包含捆绑包和其他可能的 NIB 文件、图像等的目录
- Select压缩。
6) 将资产包上传到您可以下载的位置。
7) 下载压缩资源包:
下面的代码隐藏在便利函数中,在处理大量低级文件系统操作的便利文件中。 FS 前缀指的是文件系统。
FSDownloadTempFileWithURLString
可以在返回到主线程之前从辅助线程调用。
我使用NSData
同步方法,initWithContentsOfURL:
,因为调用很可能是从辅助线程进行的。基本策略是将 zip 文件下载到一个临时位置(Caches 目录通常是一个很好的选择),然后再进行任何必要的准备并将文件解压缩到 Documents 目录。在头文件中定义内联静态操作的方法是从 Apple 采用的。
//Documents directory #define FSDocumentsDirectory [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] //Caches directory #define FSCachesDirectory [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] /** * Get the path to a file under the caches directory. The given filename can have * multiple file separators. */ inline static NSString* FSCachesPath(NSString *filename) { return [FSCachesDirectory stringByAppendingPathComponent:filename]; } /** * Download a file from a specified URL, and copy to the caches directory, to the same filename as the URL's filename. * * Returns the result. */ inline static BOOL FSDownloadTempFileWithURLString(NSString *urlString) { NSData *data = getDataFromURL(urlString); if (!data) { //Error already logged return FALSE; } NSString *path = FSCachesDirectory; NSString *filename = [urlString lastPathComponent]; path = [path stringByAppendingPathComponent:filename]; NSError *error = nil; if (![data writeToFile:path options:NSDataWritingAtomic error:&error]) { NSLog(@"Error occurred while trying to write the file to: %@\n", path); NSLog(@"%@", error); return FALSE; } return TRUE; } /** * Get the data from a specified URL. */ inline static NSData* getDataFromURL(NSString *urlString) { NSString *escapedUrlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url = [NSURL URLWithString:escapedUrlString]; NSData *data = [[NSData alloc] initWithContentsOfURL:url]; if (!data) { debugLog(@"Could not download file: %@", escapedUrlString); return nil; } return data; }
8) 使用 SSZipArchive
或类似的东西将下载的文件解压到 Documents 目录:
NSString *cachesPath = FSCachesPath(URL_RESOURCE_FILENAME); if (![SSZipArchive unzipFileAtPath:cachesPath toDestination:FSDocumentsDirectory delegate:nil]) { return; }
9) 最后,尝试从 Documents 目录中的捆绑包中的 NIB 文件加载视图,然后回退到主捆绑包。
可以从尝试从 Nib 加载视图的视图控制器调用下面的 FSResourceNib
操作,如下所示:
UIView *view = FSResourceNib(ResourcesBundle, nibName, self);
/** * Get a NIB from the documents directory, otherwise fall back to the bundle. * * Returns nil, if an error occurs. */ inline static UIView* FSResourceNib(NSString *bundleFilename, NSString *nibName, id owner) { UIView *resourceView = nil; //If bundld doesn't exist in the documents path, then use the main bundle NSString *resourcePath = FSDocumentsPath(bundleFilename); if ([[NSFileManager defaultManager] fileExistsAtPath:resourcePath]) { NSBundle *resourceBundle = [NSBundle bundleWithPath:resourcePath]; @try { //Try to load the NIB from the given bundle resourceView = [[resourceBundle loadNibNamed:nibName owner:owner options:nil] lastObject]; } @catch (NSException *exception) { //do nothing - will try main bundle } } //If loading from the given bundle failed, try loading from the main bundle if (!resourceView) { NSBundle *resourceBundle = [NSBundle mainBundle]; @try { resourceView = [[resourceBundle loadNibNamed:nibName owner:owner options:nil] lastObject]; } @catch (NSException *exception) { //do nothing - will return nil, indicating an error occurred } } return resourceView; }