ラベル Objective-C の投稿を表示しています。 すべての投稿を表示
ラベル Objective-C の投稿を表示しています。 すべての投稿を表示

2021年2月7日日曜日

Google AdMobの仕様がいつの間にか変更されてた

過去に‪Objective-C‬で書いたアプリを久しぶりにメンテしようと思い、AdMobのSDKを最新の8.0.0に上げた。(注意:7.69.0でも同じ問題出てほっといたもの)
ところが起動してタイトル画面が出てすぐにスレッドエラーだかが出て止まってしまった。
公式のインストールガイドどおりにやってるのだがダメ。

インストールするFrameworkも少し増えてるし、公式ガイドでGoogleMobileAds.frameworkとなってるのがダウンロードしたフォルダではGoogleMobileAds.xcframeworkになっていて戸惑ったが、後者をそのままインストールしていいようだ。

2017年10月29日日曜日

Objective-CからSwiftへの移行でNSKeyedunarchiverの不具合

Objective-Cで書いてたアプリをSwift4に書き直してる。
カスタムクラスをファイルに読み書きしているので、NSCodingを使ってNSKeyedUnarchiverを使っているんだけど、Objective-Cそのままのやり方でSwiftに書き直したら、カスタムクラスの
dataArray = NSKeyedUnarchiver.unarchiveObject(with:d) as! [incidentDatas]
のところでSIGABRTになって止まってしまい、その先のrequired init?(coder aDecoder: NSCoder) {〜
が実行されなかった。

エラーメッセージは以下。
2017-10-29 23:53:04.817840+0900 SkyReporter[6679:5156962] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (incidentDatas) for key (NS.objects); the class may be defined in source code or a library that is not linked'

調べると、Objective-CでNSObjectを継承しているクラスを継承してないSwiftのクラスに持ってくるとダメだとかなんとか出てきたけど、よくわからない。

結局はこちらのページ(ObjCからSwiftに書きなおす際にNSCodingがうまく動かない(解決済))のとおり、
@objc (incidentDatas)
class incidentDatas:NSObject, NSCoding {〜
というように、カスタムクラス名の前に@objcの一文付け加えてやったところ、
required init?(coder aDecoder: NSCoder) {〜
のところを実行してくれたよ。

その先で引っかかってるけど、とりあえず覚書として。

2017年9月17日日曜日

Objective-CのコードをSwiftに移植

Swift3にもなって、そろそろObjective-Cで作ったアプリをSwift化した方がいいと思うようになった。

メンテナンスのたびにObjective-Cを思い出しながら作業するのも面倒。
いつアップルが「もうObjective-Cのサポートやめるから、これからは審査通さんからね」って言い出しかねない。
なんだかんだ言ってObjective-Cのコードはごちゃごちゃしてることが多い。ヘッダファイルやimportも必要だし。

そんなわけで今さらだが、Swiftに移植してみることにした。

以下、調べたことのメモ

  • 同じプロジェクト内でクラスのファイル(ViewController.h、同.mとか)ごとに移行可能。
  • クラスのファイル名は拡張子が別になるので、Objective-CとSwiftで同居可能。(ViewController.h、同.m、同.swift)
  • 最初にSwiftファイルを追加するときに「アプリ名-Bridging-Header.h」というファイルを作るかどうか聞かれるので、作っておく。

手順

移行するクラスファイルと同名のSwiftファイルを作ってコードを移植。
Objective-Cの.hと.mのファイルを削除。
Storyboardに結びついてるならCustom ClassのModuleがNoneになってるので、そこをプロジェクト名に直す。(最初にCurrentに直すとも?)
IBOutletやIBActionの結びつきを直す。

Objective-C、Swift相互の呼び出し

Objective-CのクラスからSwiftのクラスを呼ぶ

Objective-CのクラスからSwiftのクラスを呼ぶには、呼び出し元のObjective-Cの実装ファイル(.m)に
#import "アプリ名-Swift.h"
を追加。
たとえばアプリ名がMyAppだったら
#import "MyApp-Swift.h"
とする。

ただ、なんかうまく認識してくれないことがあって、よくわかんねえんだ…。(´・ω・`*)
importしたら、すぐにそのクラスを認識してコードのサジェスト(推薦候補)を表示してくれるようになるわけじゃないようで、一度ビルドかけるとようやく認識してくれたり。それでも認識されなかったり。
このあたりはもっとしっかり作ってくれるといいんだけどね、Xcode。どうせSwiftへ移行する過渡期だけの仕様だから、いい加減なのかねえ?

SwiftからObjective-Cを呼ぶ

逆にSwiftからObjective-Cを呼ぶには作っておいた「プロジェクト名-Bridging-Header.h」に呼び出し先のObjective-Cファイルをimportする。

でいいらしい。

AppDelegateの移植

AppDelegateも同様に移植する。

main.mの削除

Objective-CではSupportingFilesフォルダ中にmain.mが作られてるけど、これが残ってるとかえってエラーになるので、削除する。

@UIApplicationMain

Swiftの普通のAppDelegateを参考にすりゃわかるけど、class AppDelegateの前に@UIApplicationMainってのを書いてやる。
多分これで最初に呼ばれるファイルに指定されるんだろう。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

2016年10月20日木曜日

コードで作ったオブジェクトが動かない時はARCを疑え

Storyboardで配置したUI部品(ボタンとかラベルとか)じゃなく、コードの中で作った部品が機能せず、「なんでじゃー!」と発狂したことなら皆さん何回かあると思います。

これって、本来はプロパティ(インスタンス変数)に保存しておかなけりゃいけないものを、関数の中だけのローカル変数に保存したばっかりに、関数を抜けた途端にARCが働いてガベージコレクションされちゃうからなのよね。
(実際にガベージコレクションするタイミングはARC任せだけど)

エラーやウォーニングが出るわけじゃないし、部品自体は画面に表示されたままだから、なんでじゃー! ってなるんだけど、iOSの仕組みを考えるとなるほどということになる。

UI部品ばかりじゃなく、音を鳴らすとかの場合にも、関数の中でサウンド関係のインスタンスを作って鳴らしたはずが鳴らなかったりするのは、やはり鳴り終えるまでガベージコレクションされない変数(要するにプロパティ)として保持しておかなきゃいけないのよね。

Storyboardで作ると自動的にプロパティが作られるので実感わかないけど、要はそういうこと。

2016年9月15日木曜日

iPhoneとiPadでtableViewの高さを変える

iOS10で、AutoLayoutとSizeClassesで中のLabelとかの大きさはiPhoneとiPadで変えられるけど、tableViewCellの高さを個別に変える方法がわからなかった。

viewDidLoadあたりに以下のコードを書いときゃいい。
デバイスがiPadなら70に、iPhoneなら40にという単純なコード。
せっかくSizeClassesの機能があるんだから、Cellの高さに対しても機能させろよ! と思うのは俺だけじゃあるまい。

if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    self.tableView.rowHeight = 70;
}
else {
    self.tableView.rowHeight = 40;
}


なお、以下のコードを使うとセルの内容に応じてセルごとに高さを変えてくれるようになったらしいけど、今回のケースではうまくいかなかった。
self.tableView.rowHeight = UITableViewAutomaticDimension;

変なメッセージでた:Assigning to 'AppDelegate *__strong' from incompatible type 'id _Nullable'

iOS10リリースに伴い、Xcode8にしたら、

AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

って書いたところに以下のWarningが出たよ。

Assigning to 'AppDelegate *__strong' from incompatible type 'id<UIApplicationDelegate> _Nullable'

互換性のないタイプの'id<UIApplicationDelegate> _Nullable'から'AppDelegate *__strong'にアサイン(割り当て)しようとしている
ってことだな。

クラス間で使うグローバル変数みたいなものをAppDelegateクラスに作っといて、それにアクセスするためのおまじないで、iOS9までは問題なかったんだけど。

appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

どうやら型のチェックを厳密にするようになったためらしく、上記のようにキャストしてやったらWarning消えただよ。めでたしめでたし。

2016年9月13日火曜日

TextView内のURLリンクを有効にする

TextViewの中にURLとかが書かれてた場合、それをタップしてSafariとかで表示する方法。


  1. TextViewでURLを検知するチェックをオンにする。(Storyboardもしくはコードで)
  2. TextViewを編集不可能にしておく。(Storyboardもしくはコードで)
  3. TextViewのタップジェスチャーを追加し、タップされたら編集可能にし、キーボードを表示するように設定。
  4. Delegateで編集終了を検知し、編集不可能にし、キーボードを隠す。
  5. URLを押すとSafariでリンクが開き(YouTube動画の場合はYouTubeアプリが)、URL以外を押すと編集が可能になる。


あらかじめdelegateを設定
@interface DetailTableViewController : UITableViewController <UITextViewDelegate>

viewDidLoadあたりで
_detailTextView.delegate = self;
//TextViewのLinkを有効にする
//StoryboardのDetectionのLinksをオンにしてもいい
_detailTextView.dataDetectorTypes = UIDataDetectorTypeLink;
//TextViewにタップジェスチャー追加
//通常は編集不可でリンクを有効にし、タップしたら編集可能にするため
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(textViewTap)];

[_detailTextView addGestureRecognizer:tap];


//textViewがタップされたらエディタブルにしてキーボード表示
-(void)textViewTap {
    [_detailTextView setEditable:YES]; //編集可能に
    [_detailTextView becomeFirstResponder]; //キーボード表示
}


//textView編集終了時、キーボードを隠し、元データを更新
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    [textView setEditable:NO]; //編集不可に(linkを有効にするため)
    [textView endEditing:YES]; //キーボード隠す
    
    return YES;
}


編集不可能な状態じゃないと、Linkを有効に設定していても有効にならない。しかし編集可能になってくれないとTextViewの意味がない。
というわけで、タップジェスチャーとDelegateを使って編集可能、不可能の設定をON/OFFしてやるわけですな。めんどくせぇ。
これくらいOSサイドで一括処理してくんないかしらね?

URLの他にも住所、イベント(日付かな?)、電話番号の認識もあるようだ。いろいろやってみるよろし。

2016年9月2日金曜日

非同期処理を同期処理に(dispatch_semaphore)

時として不便な非同期処理

画像ライブラリから1枚ずつ画像を読み込んで処理するコードを書いていたのだが、読み込み処理がblock構文を使った非同期処理なので、forループばかりどんどん空回りしてblock内の処理が後回しになり、期待通りの結果になってくれなかった。

for (int i = 0; i < imageURL.count; i++) {
      ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
      [library assetForURL:imageURL[i] resultBlock:^(ALAsset *asset) {
            //ここに読み込み成功時の処理を書く
      } failureBlock:^(NSError *error) {
            //ここにエラー処理を書く
      }];
}

非同期処理を同期処理にする技

これを、処理が一件終わるまで待たせるようにするのが同期処理だ。
dispatch_semaphore(ディスパッチ・セマフォ)ってのを使う。

dispatch = 使者や手紙などを送る
semaphore = 手旗信号

ということなので、それこそ処理の終了待ちを意味するフラグみたいなものをセットして並列処理を制限し、終わった時点でそれをクリアしてやるのだな。

for (int i = 0; i < imageURL.count; i++) {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
      [library assetForURL:tempURLArray[i] resultBlock:^(ALAsset *asset) {
          //ここに読み込み成功時の処理を書く
          dispatch_semaphore_signal(semaphore);
      } failureBlock:^(NSError *error) {
          //ここにエラー処理を書く
          dispatch_semaphore_signal(semaphore);
      }];
   });
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

}

dispatch_semaphore_create(0)でセマフォを作り、
dispatch_get_global_queue()内のblock構文の中に同期処理させたいコードを書き、
処理が済んだ時点でdispatch_semaphore_signal(semaphore)を実行して次の処理に移っている。
dispatch_semaphore_wait()はどれくらい処理を待つかだ。ここでは処理が終わるまでずっと待たせるようにしている。
古い参考サイトでは最後に変数semaphoreを解放してやる必要があるってなってたけど、ARC使ってる分には勝手に解放してくれるからいらない。

dispatch_async()ってのはasyncってくらいだから本来非同期処理にするための命令らしい(だから並列処理の優先順位をDISPATCH_QUEUE_PRIORITY_BACKGROUNDって指定してる)。
でもこう書くみたいだからいいの! 動いたからいいの! 他の参考サイトでもそう書いてるからいいの!

これを応用すれば同期処理も非同期処理に変更できるようだけど、それはまたいつか。

Swift3から(だと思う)は、書き方がObjective-Cの書き方とだいぶ変わってしまったので、別にまとめる。

2016年8月26日金曜日

画像相互変換

まだ直接相互変換できるかわからないケースもあるが、一度他の形式に変換して間接的に変換できるよね。
uiにUIImageが入ってるとして

UIImage *ui = [UIImage imageNamed:@"cat"];

UIImage -> CGImage

struct CGImage *cg = [ui CGImage];

CGImage -> UIImage

UIImage *ui = [UIImage imageWithCGImage:cg];

UIImage -> CIImage

CIImage *ci = [ui CIImage];

CIImage -> UIImage

UIImage *ui = [UIImage imageWithCIImage:ci];

CGImage -> CIImage

CIImage *ci = [CIImage imageWithCGImage:cg];

CIImage -> CGImage

CIContext *context = [CIContext contextWithOptions:nil];
struct CGImage *cg = [context createCGImage:ci fromRect:ci.extent];

UIImage -> NSData

NSData *da = UIImageJPEGRepresentation(ui, 1.0); //JPEGとして
NSData *da = UIImagePNGRepresentation(ui); //PNGとして

NSData -> UIImage

UIImage *ui = [UIImage imageWithData:da];

2016年8月7日日曜日

画像をメタデータ付きで読み書き

アプリ内にあるDocumentsディレクトリ内の画像を、メタデータ(EXIFやGPSの位置情報など)付きで読み書きする方法。

準備

プロジェクトにメタデータの付いた画像をインストールしておく。(いきなりDocumentディレクトリに画像入れられないから)
名前は一応gazo.jpgで。
最近はAssetCatalogにインストールすることが多いけど、そこの画像にアクセスする方法がよくわからないので、以前のようにXcodeのプロジェクトナビゲータの所に突っ込むこと。

今回は以下の手順を取る。
  1. リソースとしてインストールされた画像gazo.jpgとメタデータを読み込み
  2. Documentディレクトリにメタデータとともに書き込み
  3. 画像とメタデータを読み込んで表示する
また、ImageIO.frameworkをインポートしとくこと。
#import <ImageIO/ImageIO.h>

書き編

//リソースにある画像を読み、メタデータとともにアプリ内のDocumentディレクトリに保存
- (void)writeImageWithMetadata {
    //🌟準備としてリソース画像とmetadataを読む
    //リソースディレクトリへのパスを得る
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *resourcePath = [bundle resourcePath];
    NSString *imgPath = [resourcePath stringByAppendingPathComponent:@"gazo.jpg"];
    NSLog(@"リソースのpath %@",imgPath);
    //リソースの画像とメタデータを得る
    CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:imgPath]), nil);
    NSDictionary *metadata = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL));
    
    //🌟画像とmetadataをDocumentディレクトリに保存
    //Documentに保存するファイルのpathを作る
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"gazo.jpg"];
    
    //書き込むための画像データを入れるNSMutableData型変数を作る
    NSMutableData *imageData = [[NSMutableData alloc] init];
    //書き込むための情報を入れる変数destRefに、書き込む画像データの変数(imageData)の情報と、データタイプ(JPEGとか?)、画像ファイル中の画像枚数(複数画像を持つフォーマットもあるため)を入れる
    //sourceRefはデータタイプを得る関数で参照しているだけなので、ここでは画像そのものは加えられていない
    CGImageDestinationRef destRef = CGImageDestinationCreateWithData((CFMutableDataRef)imageData, CGImageSourceGetType(sourceRef), 1, nil);
    //引数1のdestRefに、引数2の画像と引数4のメタデータを追加する。引数3の0は複数画像がある場合の画像のインデックスらしい
    CGImageDestinationAddImageFromSource(destRef, sourceRef, 0,(CFDictionaryRef)metadata);
    //書き込むための情報destRefをファイナライズして完成
    CGImageDestinationFinalize(destRef);
    //画像データのimageDataの書き込みをすることで、destRefを参照してメタデータともども書き込んでくれるらしい
    [imageData writeToFile:filePath atomically:YES];
    //使用後は解放
    CFRelease(sourceRef);
    CFRelease(destRef);
}

読み編

//Documentディレクトリ内の画像を読み込んで表示&メタデータ取得して表示
- (void)readImageWithMetadata {
    //Documentディレクトリにあるファイルのpath作成
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"gazo.jpg"];

    //画像読み込み
    NSError *err = nil;
    NSData *imageData = [NSData dataWithContentsOfFile:filePath
                                              options:NSDataReadingMappedAlways
                                                error:&err];
    //エラー処理するならこの辺で
    NSLog(@"Error -> %@",err);
    //NSDataからCIImage経由でUIImageに変換
    CIImage *ciImage = [CIImage imageWithData:imageData];
    UIImage *image = [UIImage imageWithCIImage:ciImage];
    //こっから。UIImage作り直し開始
    UIGraphicsBeginImageContext(image.size);
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    //ここまで。UIImage作り直し終了
    _myImageView.image = [UIImage imageWithData:imageData];

    //メタデータ取得
    //CGImageSourceCreateWithURLを使う場合
    //Documentディレクトリ内の画像とメタデータを得る
    CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:filePath]), nil);
    //sourceRefの情報からメタデータを取り出す
    NSDictionary *metadata = (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, nil));

    //使用後は解放
    CFRelease(sourceRef);

    //確認のためmetadata表示
    NSLog(@"メタデータ %@",metadata);
    //EXIF情報取り出し
    NSDictionary *ExifDictionary = [metadata objectForKey:(NSString*)kCGImagePropertyExifDictionary];
    NSLog(@"EXIFデータ %@",ExifDictionary);
}

各メソッド、関数について

NSBundleはアプリにインストールされてるリソースにアクセスするためのクラス。
resourcePathメソッドでリソースディレクトリへのpathが得られるので、ファイル名を追加。

CGImage〜Ref

CGImageSourceRef、CGImageDestinationRefは画像ファイルに付加されたEXIFなどの情報にアクセスするためのクラスで、ImageIO Frameworkが必要。
  • 画像ファイル読み込み時 
    • CGImageSourceRef 
  • 画像ファイル書き込み時 
    • CGImageDestinationRef

メタデータの中身にアクセス

metadataの辞書からEXIF情報を、キーを使って取り出し表示している。
ちなみにEXIFは入れ子になっている辞書なので、
float ss = [metadata[(NSString *)kCGImagePropertyExifDictionary][(NSString *)kCGImagePropertyExifExposureTime] floatValue];
などとやることで下の階層の情報にアクセスする。

CGImageDestinationCreateWithData

CGImageDestinationRef CGImageDestinationCreateWithDataConsumer ( CGDataConsumerRef consumer,CFStringRef type, size_t count, CFDictionaryRef options );

引数
consumer:書き込みデータのコンシューマー(何じゃそりゃ?)だそうだ。初期化しただけのNSMutableDataを指定してる。多分ここで指定した変数に画像データのsourceを書き込み、さらに結果をdestに返り値を返してるんだと思う。
type:データのタイプ(たとえばJPEGとかPNGとか)らしいが、この場合直接指定せずにCGImageSourceGetType関数でソース画像のタイプを得ている。
count:TIFFなどのように複数の画像を含む画像の場合、何枚あるのかを指定するが、1枚だけなら1でいい。
options:将来のための予約とあるので、nilでよし。

CGImageDestinationAddImageFromSource

void CGImageDestinationAddImageFromSource ( CGImageDestinationRef idst, CGImageSourceRef isrc, size_t index, CFDictionaryRef properties );

引数
idst:画像のdestination
isrc:画像のソース
index:ソース画像で画像の位置を指定するインデックス(0オリジン)。多分複数画像を持つフォーマットの場合に、何枚目の画像かを指定するんじゃないかと思うが、よくわからん。とりあえず1枚だけのフォーマットなら0でいいかと。
properties:ソース画像のプロパティに上書きするか追加するか指定する辞書。プロパティのキーがkCFNullなら画像のdestinationは削除される。わかりづらいが、ここに辞書を指定してやればソース画像(source)+辞書をdestinationに追加してくれるらしい。

変数destは書き込む画像のデータ(imagaData)とメタデータ(metadata)の情報を参照していて、CGImageDestinationFinalizeでファイナライズした後にimagaDataの方をwriteToFileすると、メタデータ付きで書き込まれるのではないか?
最後にsourceとdestはreleaseしてやる必要があるようだ。

destに色々情報を追加しつつ、最後はimageDataを書き込んでるのがわかりづらい。imageDataを書き込むとdestに書かれた参照情報を見てmetadataも一緒に書き込まれるという理解でいいのか?

CGImageにすると向きの情報が消えることがある

読み書き時に止むを得ずCGImageに変換を要する場合があるが、UIImageでは持っている画像の向きの情報であるimageOrientationプロパティがないため、UIImageに復元しても縦長に撮った画像が90度横倒しに表示されてしまうことがあった。
その場合、
[UIImage imageWithCGImage: scale: orientation:]メソッドでorientationを指定しつつ復元してやるが、それでもうまくいかない場合は、
UIGraphicsBeginImageContext()
と
UIGraphicsEndImageContext()
に挟まれた間で、UIImageを作り直してやるといい。
なにやらめんどくさいがしょうがない。
ちなみに前述のコード中、直接CGImageから変換したのではうまくいかなかったため、一度CIImageを経由した。
なにやら本当にめんどくさい。

謎のメタデータ追加メソッド

メタデータを追加するメソッドとしては
CGImageDestinationAddImageAndMetadata(CGImageDestinationRef idst, CGImageRef image, CGImageMetadataRef metadata, CFDictionaryRef options)
というのもあるようなのだが、APIリファレンス見てもちゃんとした説明が書いてないし、よくわからない。
釈然としないけど、CGImageDestinationAddImageFromSourceでうまくいくんならそれでいんじゃね?

アセットカタログ内の画像にアクセスしてみる

結論から言うとうまくいきませんでした。

NSString *imgSuffix = @"-universal-1x";
NSBundle *bundle = [NSBundle mainBundle];
//Assets Catalogのディレクトリへのパスを作る

NSString *imgPath = [bundle pathForResource:[NSStringstringWithFormat:@"%@%@",@"gazo2",imgSuffix] ofType:@"jpg"];

アセットカタログのjsonファイル(Contents.json)を開くと、
{
  "images" : [
    {
      "idiom" : "universal",
      "filename" : "gazo2.jpg",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}
などとなっており、アクセスにはファイル名にidiomやscaleのサフィックスを付けるというような情報を見たのだけど、サフィックスが合ってないのか、そもそも情報自体が間違いなのか、imgPathはnilになってしまい、ダメだった。
メタデータを取るためにアクセスするようなことがなければ、単にUIImageのファイル名で楽にアクセスできるのにねぇ。

感想

CGImageSourceRefとCGImageDestinationRefがどういうものかよくわからず、ひたすら悩んで調べまくった。
今もよくわかってないけどね。
引数1に引数2以降の情報を追加するという仕様や、情報を追加した変数(たとえばdestRef)じゃなく画像本体の方を書き込むとdestRefを参照してメタデータも書き込まれるとかはわかりづらい。

今回はリソースとDocumentディレクトリの間だったけど、イメージライブラリとの間だとALAssetLibraryを使って(最近はPhotosFrameworkなの?)また別のやり方になるはず。
とにかくめんどっちー。
UIImageのプロパティとして簡単に扱えるようにならんもんか…。