在iOS上加载NSBundle文件

在iOS上加载NSBundle文件,ios,loading,nsbundle,Ios,Loading,Nsbundle,我想创建一个具有非常灵活的图形用户界面(skinnable)的项目。为了实现这一点,我希望从外部资源(例如网站)加载NSBundle。捆绑包应该包含与主项目中的某些属性和方法(IBOutlets和IBActions)相对应的NIB 苹果似乎限制了以这种方式使用NSBundle的可能性。有什么办法可以让这一切顺利吗?如果传统方法不可行,推荐的替代方法是什么?未经测试 您可以在一个zip文件中分发所有内容,使用解压 正如我所承诺的,这里有一个简短的解释,说明我是如何做到这一点的 在我的AppDele

我想创建一个具有非常灵活的图形用户界面(skinnable)的项目。为了实现这一点,我希望从外部资源(例如网站)加载NSBundle。捆绑包应该包含与主项目中的某些属性和方法(IBOutlets和IBActions)相对应的NIB

苹果似乎限制了以这种方式使用NSBundle的可能性。有什么办法可以让这一切顺利吗?如果传统方法不可行,推荐的替代方法是什么?

未经测试

您可以在一个zip文件中分发所有内容,使用解压


正如我所承诺的,这里有一个简短的解释,说明我是如何做到这一点的

在我的AppDelegate中,我检查服务器上是否有新的捆绑包(打包在zip文件中)。如果它存在,我下载这个包。该捆绑包包含可蒙皮图形界面所需的NIB和其他相关数据(例如图形文件)。代码看起来有点像这样:

TestAppDelegate.m

- (void)downloadBundle 
{      
   NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/~wsc/template.bundle.zip"];
   NSURLRequest *request = [NSURLRequest requestWithURL:url];
   NSURLResponse *response = nil;
   NSError *error = nil;
   NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
   NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
   NSLog(@"%d", [httpResponse statusCode]);

   if ([httpResponse statusCode] == 404) // bundle will be deleted and the default interface will be used ...
   {
      NSString *path = [documentsDirectory stringByAppendingPathComponent:@"template.bundle"];
      [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
      return;
   }
   else if (error) 
   {
      NSLog(@"%@", error);
   }

   BOOL didWriteData = [data writeToFile:zipFile atomically:YES];
   if (didWriteData) 
   {
      BOOL success = [SSZipArchive unzipFileAtPath:zipFile toDestination:documentsDirectory];
      if (!success) 
      {
         NSLog(@"failed to unzip file.");
      }
   }
}
@implementation TestViewController
@synthesize button, label;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

// the -init method is overridden to use nib file from bundle, if bundle exists ...
- (id)init 
{
   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *documentsDirectory = [paths objectAtIndex:0];
   NSString *file = [documentsDirectory stringByAppendingPathComponent:@"template.bundle"];
   NSBundle *bundle = [NSBundle bundleWithPath:file];
   if (!bundle)
   {
      NSLog(@"no bundle found, falling back to default gui ...");
      return [self initWithNibName:nil bundle:nil];
   }

   NSString *nibName = NSStringFromClass([self class]);
   return [self initWithNibName:nibName bundle:bundle];
}

- (void)dealloc
{
   [button release];
   [label release];
   [view release];

   [super dealloc];
}

#pragma mark - View lifecycle

- (void)loadView
{
   if (self.nibName && self.nibBundle) 
   {
      // connect outlets to proxy objects ...
      NSDictionary *objects = [NSDictionary dictionaryWithObjectsAndKeys:
          self.label, @"label", 
          self.button, @"button", 
          nil];
      NSDictionary *proxies = [NSDictionary dictionaryWithObject:objects forKey:UINibExternalObjects];
      NSArray *nibs = [self.nibBundle loadNibNamed:self.nibName owner:self options:proxies]; // connection happens here ...

      NSLog(@"nibs found with name %@: %d", self.nibName, [nibs count]);
      return;
   }

   // show default gui if no nib was found ...

   CGRect frame = [UIScreen mainScreen].applicationFrame;
   self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
   [self.view setBackgroundColor:[UIColor lightGrayColor]];

   self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
   [self.button setFrame:CGRectMake(0.0f, 0.0f, 60.0f, 30.0f)];
   [self.button setCenter:CGPointMake(160.0f, 100.0f)];
   [self.button addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
   [self.view addSubview:self.button];

   self.label = [[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 30.0f)] autorelease];
   [self.label setCenter:CGPointMake(160.0f, 50.0f)];
   [self.label setTextAlignment:UITextAlignmentCenter];
   [self.view addSubview:self.label];
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
   [super viewDidLoad];

   // for my purposes I'll add the localized string from the mainBundle here for the standard controls, 
   // this will override the text set in the nibs, since the nibs are loaded and displayed at this point ...
   [self.button setTitle:NSLocalizedString(@"TestButton", nil) forState:UIControlStateNormal];
   [self.label setText:NSLocalizedString(@"TestLabel", nil)];

}

- (void)viewDidUnload
{
   [super viewDidUnload];
   self.button = nil;
   self.label = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark -

- (IBAction)buttonTouched:(id)sender 
{
   [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"DialogTitle", nil) 
                                message:NSLocalizedString(@"ButtonTouchedText", nil) 
                               delegate:nil 
                      cancelButtonTitle:@"OK" 
                      otherButtonTitles:nil] 
     autorelease] show];
}

@end
请注意,我使用了neoneye建议的SSZipArchive类。需要将捆绑包打包到某种容器中才能高效下载所有内容,因为捆绑包只是一个遵循苹果惯例的目录和文件结构

我的一个类是ViewController,它有一个标签和按钮作为IBOutlets。ViewController中最重要的代码如下所示:

TestViewController.h

@interface TestViewController : UIViewController {
   UIButton *button;
   UILabel *label;
}

@property (nonatomic, retain) IBOutlet UIButton *button;
@property (nonatomic, retain) IBOutlet UILabel *label;

- (IBAction)buttonTouched:(id)sender;

@end
TestViewController.m

- (void)downloadBundle 
{      
   NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/~wsc/template.bundle.zip"];
   NSURLRequest *request = [NSURLRequest requestWithURL:url];
   NSURLResponse *response = nil;
   NSError *error = nil;
   NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
   NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
   NSLog(@"%d", [httpResponse statusCode]);

   if ([httpResponse statusCode] == 404) // bundle will be deleted and the default interface will be used ...
   {
      NSString *path = [documentsDirectory stringByAppendingPathComponent:@"template.bundle"];
      [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
      return;
   }
   else if (error) 
   {
      NSLog(@"%@", error);
   }

   BOOL didWriteData = [data writeToFile:zipFile atomically:YES];
   if (didWriteData) 
   {
      BOOL success = [SSZipArchive unzipFileAtPath:zipFile toDestination:documentsDirectory];
      if (!success) 
      {
         NSLog(@"failed to unzip file.");
      }
   }
}
@implementation TestViewController
@synthesize button, label;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

// the -init method is overridden to use nib file from bundle, if bundle exists ...
- (id)init 
{
   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *documentsDirectory = [paths objectAtIndex:0];
   NSString *file = [documentsDirectory stringByAppendingPathComponent:@"template.bundle"];
   NSBundle *bundle = [NSBundle bundleWithPath:file];
   if (!bundle)
   {
      NSLog(@"no bundle found, falling back to default gui ...");
      return [self initWithNibName:nil bundle:nil];
   }

   NSString *nibName = NSStringFromClass([self class]);
   return [self initWithNibName:nibName bundle:bundle];
}

- (void)dealloc
{
   [button release];
   [label release];
   [view release];

   [super dealloc];
}

#pragma mark - View lifecycle

- (void)loadView
{
   if (self.nibName && self.nibBundle) 
   {
      // connect outlets to proxy objects ...
      NSDictionary *objects = [NSDictionary dictionaryWithObjectsAndKeys:
          self.label, @"label", 
          self.button, @"button", 
          nil];
      NSDictionary *proxies = [NSDictionary dictionaryWithObject:objects forKey:UINibExternalObjects];
      NSArray *nibs = [self.nibBundle loadNibNamed:self.nibName owner:self options:proxies]; // connection happens here ...

      NSLog(@"nibs found with name %@: %d", self.nibName, [nibs count]);
      return;
   }

   // show default gui if no nib was found ...

   CGRect frame = [UIScreen mainScreen].applicationFrame;
   self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
   [self.view setBackgroundColor:[UIColor lightGrayColor]];

   self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
   [self.button setFrame:CGRectMake(0.0f, 0.0f, 60.0f, 30.0f)];
   [self.button setCenter:CGPointMake(160.0f, 100.0f)];
   [self.button addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
   [self.view addSubview:self.button];

   self.label = [[[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 30.0f)] autorelease];
   [self.label setCenter:CGPointMake(160.0f, 50.0f)];
   [self.label setTextAlignment:UITextAlignmentCenter];
   [self.view addSubview:self.label];
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
   [super viewDidLoad];

   // for my purposes I'll add the localized string from the mainBundle here for the standard controls, 
   // this will override the text set in the nibs, since the nibs are loaded and displayed at this point ...
   [self.button setTitle:NSLocalizedString(@"TestButton", nil) forState:UIControlStateNormal];
   [self.label setText:NSLocalizedString(@"TestLabel", nil)];

}

- (void)viewDidUnload
{
   [super viewDidUnload];
   self.button = nil;
   self.label = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark -

- (IBAction)buttonTouched:(id)sender 
{
   [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"DialogTitle", nil) 
                                message:NSLocalizedString(@"ButtonTouchedText", nil) 
                               delegate:nil 
                      cancelButtonTitle:@"OK" 
                      otherButtonTitles:nil] 
     autorelease] show];
}

@end
实际的nib是在一个单独的链接项目中定义的(Cocoa NSBundle项目中的一些参数已更改,以使其适用于iOS设备)。该文件的所有者是TestViewController,因此我可以访问所有出口和操作,并建立适当的连接。请注意,TestViewController中没有定义view(UIView*)属性,因为超类已经有了view属性。确保已在Interface Builder中连接视图。还要注意,为了便于使用,nib文件使用了与实际类相同的名称


在web上找不到关于如何实现这一点的太多信息,因此我希望这将对许多有类似目标的人有所帮助。

好吧,我终于从documentsDirectory获得了一个NSBundle,通过将它从finder复制到应用程序的iPhone Simulator文件夹来加载,因此它可能也可以在真正的设备上运行。要从服务器上实际复制文件,我似乎必须将包存档在一个zip文件中,所以我接受你的回答。编辑:一旦一切正常,我将在本主题中发布完整的代码。我使用SSZipArchive分发html内容,它看起来是一个相当健壮的组件。它可以在iPhone3上提取非常大的zip文件(我测试的文件大约40 mb),而不会耗尽内存。很好。另外,为了下载内容,我可以推荐它在一个单独的线程中完成,允许您在等待完成的同时显示进度条。非常好的代码!不过我有一些问题。如何实例化此视图控制器的实例?如果我尝试使用自定义nib名称初始化,它会出现在主捆绑包中,但不在那里。如果我尝试使用默认nib初始化,它将无法加载捆绑包。介意把最后一块加入这个拼图吗?:)此外,如果在我下载的版本中连接了插座,是否需要像在loadView()中一样重新应用插座?谢谢是的,这是将代码中的代理对象与nib文件中的对象连接起来的方法。我认为如果不在代码中手动建立连接,它将无法正常工作。明白了!谢谢你的帮助,沃尔夫冈!我发现,如果我在编译包之前将它们连接起来,那么操作将继续进行。看起来IB的门店并没有分享到同样的好处。总而言之,这是一种获得“可蒙皮”用户界面的好方法。再次感谢!干杯