【问题标题】:loading NSBundle files on iOS在 iOS 上加载 NSBundle 文件
【发布时间】:2011-09-06 09:11:04
【问题描述】:

我想创建一个具有非常灵活的图形用户界面(可换肤)的项目。为了使这成为可能,我想从外部资源加载 NSBundle,例如一个网站。捆绑包应包含与主项目中的某些属性和方法相对应的 nib(IBOutlets 和 IBActions)

Apple 似乎限制了以这种方式使用 NSBundle 的可能性。有什么办法可以使这项工作?如果以传统方式不可能,那么推荐的替代方法是什么?

【问题讨论】:

    标签: ios loading nsbundle


    【解决方案1】:

    未经测试。

    您可以将所有内容分发到一个 zip 文件中,使用 SSZipArchive 解压缩。

    NSBundle* bundle = [NSBundle bundleWithPath:absolutePathToNibFile].
    
    UIViewController* vc = [[[MyViewController alloc] initWithNibName:@"mycustomnib" bundle:bundle] autorelease];
    

    【讨论】:

    • 好吧,我终于得到了一个 NSBundle 来从documentsDirectory 加载,方法是将它从finder 复制到应用程序的iPhone Simulator 文件夹,所以它可能也应该在真实设备上工作。要实际从服务器复制文件,我似乎必须将包存档在 zip 中,所以我会接受你的回答。 编辑:一切正常后,我将在此主题中发布完整代码。
    • 我使用 SSZipArchive 来分发 html 内容,它看起来是一个相当强大的组件。它可以在 iPhone 3 上提取非常大的 zip 文件(我测试过的文件大小约为 40 mb),而不会耗尽内存。
    【解决方案2】:

    正如所承诺的,这里简要解释一下我是如何做到的。

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

    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.");
          }
       }
    }
    

    请注意,我使用 neoneye 建议的 SSZipArchive 类。需要将捆绑包打包在某种容器中以有效下载所有内容,因为捆绑包只是遵循 Apple 约定的目录和文件结构。

    我的一个类是一个 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

    @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 文件使用与实际类相同的名称以方便使用。

    在网上找不到太多关于如何做到这一点的信息,所以我希望这对许多有类似目标的人有所帮助。

    【讨论】:

    • 不错。另外要下载内容,我可以推荐github.com/pokeb/asi-http-request/tree,它在单独的线程中完成,允许您在等待完成时显示进度条。
    • 优秀的代码!我有一些问题。你如何实例化这个视图控制器的实例?如果我尝试使用自定义 nib 名称进行初始化,它会在主包中查找,但不存在。如果我尝试使用默认笔尖进行初始化,则无法加载捆绑包。介意在这个拼图中添加最后一块吗? :)
    • 另外,如果我在下载的版本中连接了插座,我是否需要像在 loadView() 中那样重新应用它们?谢谢!
    • 是的,这是将代码中的代理对象与 nib 文件中的对象连接起来的方法。如果不在代码中手动建立连接,我认为它不会很好地工作。
    • 知道了!感谢沃尔夫冈的帮助!我发现如果我在编译捆绑包之前将它们连接起来,这些动作就会继续存在。似乎 IBOutlets 并没有分享同样的好处。总而言之,获得“可换肤”用户界面的方法并不坏。再次感谢!干杯!
    猜你喜欢
    • 2016-08-19
    • 1970-01-01
    • 2014-01-24
    • 1970-01-01
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多