LoginSignup
0
0

More than 5 years have passed since last update.

ReactNativeでNSLibraryDirectoryに配置したjsbundleを利用して画像を読み込む

Posted at

今の時点でこの情報が役に立つ人はあまりいない気もしますが、とりあえず。

ReactNativeの通常のプロセスでビルドをするとApplication bundleの直下にmain.jsbundleがcopyされ、Imageなどから参照される画像はここを起点とした相対パスとして解釈されて読み込まれます。

別な場所に置いたjsbundleを使ってRCTRootViewを初期化した場合、ImageのsourceのURLはそのjsbundleが設置されている場所を起点とした相対パスとして解釈されるため、通常プロセスでAssetsを読み込んでくれるRCTXCAssetImageLoaderは読み込んでくれません。

そこで、ReactNativeのImageLoaderの仕組みに則って独自に以下の様なImageLoaderコンポーネントを作成することで、NSLibraryDirectory以下に配置されたjsbundleが、あたかもApplication Bundle直下に配置されたかのようにassets URLを書き換える事で、通常のビルドでbundleされたjsbundleであるかのように画像を読み込む事ができます。

#import <libkern/OSAtomic.h>
#import "RCTBridge.h"
#import "RCTUtils.h"
#import "../../node_modules/react-native/Libraries/Image/RCTImageLoader.h"

@interface LibraryAssetsImageLoader : NSObject <RCTImageURLLoader> @end

@implementation LibraryAssetsImageLoader

RCT_EXPORT_MODULE()

+ (NSString*)libraryDirectory
{
  static NSString *dir = nil;
  if(dir == nil){
    dir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
                                                  NSUserDomainMask, YES) firstObject];
  }
  return dir;
}

- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
  if(!requestURL.isFileURL) return NO;
  NSString *path = requestURL.path;
  return [path hasPrefix:[LibraryAssetsImageLoader libraryDirectory]];
}

- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
                                              size:(CGSize)size
                                             scale:(CGFloat)scale
                                        resizeMode:(UIViewContentMode)resizeMode
                                   progressHandler:(RCTImageLoaderProgressBlock)progressHandler
                                 completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
  __block volatile uint32_t cancelled = 0;
  dispatch_async(dispatch_get_main_queue(), ^{

    if (cancelled) {
      return;
    }
    // convert assets in libdir to bundled assets.
    NSString *libdir = [LibraryAssetsImageLoader libraryDirectory];
    NSString *path = imageURL.path;
    path = [path substringFromIndex:libdir.length];
    path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:path];

    NSString *imageName = RCTBundlePathForURL([NSURL fileURLWithPath:path]);
    UIImage *image = [UIImage imageNamed:imageName];
    if (image) {
      if (progressHandler) {
        progressHandler(1, 1);
      }
      completionHandler(nil, image);
    } else {
      NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
      completionHandler(RCTErrorWithMessage(message), nil);
    }
  });

  return ^{
    OSAtomicOr32Barrier(1, &cancelled);
  };
}
@end

これで何が嬉しいかというと、アプリのリリース後にjsbundleをダウンロードしてきて動的に更新できるようになるということですね。
Imageリソースをサーバーからダウンロードする形式にすれば問題なかったのですが、データ転送量とかを気にするとやっぱり最初からbundleしてある画像リソースはそのまま使いたいですしね。

動的にダウンロードしたjsをそのまま使っては危険ですので、jsbundle作成時に秘密鍵でサインして、アプリ側で公開鍵でvalidateするとかしましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0