iPhone/iPadアプリケーション開発の教科書が増刷されました。
ジェネラティブ・アートが面白い
久しぶりに Processing でコード書いてます。なんでかと言うと、たまたま本屋さんで見つけた「ジェネラティブ・アート」という本がめちゃくちゃ面白くてそれがきっかけです。
個人的にはChapter 6の「創発」、Chapter 7の「自律性」、Chapter 8の「フラクタル」が特に面白かったです。創発とか自律性の考え方は AI っぽくてゲーム開発にも活かせそうでした。
「ジェネラティブ・アート」は拙著「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」の序章でもプログラミング初心者に最適な本として紹介しています。
プログラミングの楽しさやプロラムが動いたときの感動がダイレクトにわかる良書です。本に掲載されているサンプルプログラムを入力して動かした時に「おお〜」「なるほど!」みたいなわくわくする感じ、ここ最近なかった感覚でした。やっぱり Processing は面白いです。
サンプルプログラムをリファクタリングしてみました
「ジェネラティブ・アート」の最終章「フラクタル」にサトクリフ五角形を作るサンプルがあったのですが、少しプログラムの効率が悪かったので勝手にリファクタリングしてみました。
ちなみにサトクリフ五角形とは数学者であるアラン・サトクリフ氏が考案した五角形です。以下のような五角形のことを指します。
まず五角形を描いて、その 5 つの辺のそれぞれの中点から垂線を引き、この垂 線上の点を結べば別の五角形を作れると言っています。こうすれば、形状の残りの部分も同じ く、さらなる五角形に分けられるので、結局それぞれの五角形がさらに 6 個のサブ五角形に分 けられます。サブ五角形の内にはさらにサブサブ五角形ができるので、この分割を無限小に向 かって繰り返すことができます。
ジェネラティブ・アート 219ページ
再帰的な五角形のコード
まずは227ページの再帰的な五角形(図8.14)から修正してみます。
FractalRoot pentagon; int _maxlevels = 5; float _strutFactor = 0.2; int _numVertex = 5; void setup() { background(255); size(1000, 1000); smooth(); pentagon = new FractalRoot(); pentagon.drawShape(); } /* * 元のサンプルコードではPointObjという名前でした。 * クラス名変更しました。 */ class Point { float x, y; Point(float ex, float why) { x = ex; y = why; } } /* * サンプルコードをそのまま利用する */ class FractalRoot { Point[] points = new Point[_numVertex]; Branch rootBranch; FractalRoot() { float centX = width/2; float centY = height/2; int count = 0; for (int i = 0; i < 360; i += 360/_numVertex) { float x = centX + (400 * cos(radians(i))); float y = centY + (400 * sin(radians(i))); points[count] = new Point(x, y); count++; } rootBranch = new Branch(0, 0, points); } void drawShape() { rootBranch.drawMe(); } } class Branch { int level, num; // 頂点 Point[] vertices; Branch(int lev, int n, Point[] points) { level = lev; num = n; vertices = points; } void drawMe() { int deg = 0; int numVertex = vertices.length; Point[] projPoints = new Point[numVertex]; for (int i = 0; i < numVertex; i++) { strokeWeight(5 - level); int nexti = i + 1; if (nexti == numVertex) { nexti = 0; } // 五角形の一辺を引く Point point = vertices[i]; Point nextPoint = vertices[nexti]; line(point.x, point.y, nextPoint.x, nextPoint.y); // 中点を計算する strokeWeight(0.5); fill(255, 150); Point mid = calcMidPoint(point, nextPoint); ellipse(mid.x, mid.y, 5, 5); // 中点から垂線を引く int destination = i+3; if (destination >= numVertex) { destination -= numVertex; } Point proj = calcProjPoint(mid, vertices[destination]); line(mid.x, mid.y, proj.x, proj.y); ellipse(proj.x, proj.y, 5, 5); projPoints[i] = proj; } strokeWeight(0.5); fill(255, 150); if ((level + 1) < _maxlevels) { // 垂線の先から新たに五角形を描く Branch childBranch = new Branch(level + 1, 0, projPoints); childBranch.drawMe(); } } /* * サンプルコードをそのまま利用する */ Point calcMidPoint(Point end1, Point end2) { : : 省略 : return new Point(mx, my); } Point calcProjPoint(Point mp, Point op) { : : 省略 : return new Point(px, py); } }
もとのコードは Branch クラスが outerPoints, midPoints, projPoints, myBranches という名前の Point 配列をメンバ変数として保持していました。さすがに配列4つもいらないので1つの配列で管理するように改善しました。
calcMidPoint メソッドと calcProjPoint メソッドの処理は省略しました。処理の中身が気になる方は「ジェネラティブ・アート」を購入して確認してみてください。
出力結果は以下のようになります。
完全なサトクリフ五角形のコード
再帰的な五角形のコードをもとに完全なサトクリフ五角形を描画します。修正するのは Branch クラスの drawMe メソッドのみです。
class Branch { : : 省略 : void drawMe() { strokeWeight(5 - level); int deg = 0; int numVertex = vertices.length; Point[] midPoints = new Point[numVertex]; Point[] projPoints = new Point[numVertex]; for (int i = 0; i < numVertex; i++) { int nexti = i + 1; if (nexti == numVertex) { nexti = 0; } // 五角形の一辺を引く Point point = vertices[i]; Point nextPoint = vertices[nexti]; line(point.x, point.y, nextPoint.x, nextPoint.y); Point mid = calcMidPoint(point, nextPoint); midPoints[i] = mid; int destination = i+3; if (destination >= numVertex) { destination -= numVertex; } Point proj = calcProjPoint(mid, vertices[destination]); projPoints[i] = proj; } if ((level + 1) < _maxlevels) { Branch childBranch = new Branch(level + 1, 0, projPoints); childBranch.drawMe(); for (int i = 0; i < numVertex; i++) { int nexti = i-1; if (nexti < 0) { nexti += numVertex; } Point[] newPoints = { projPoints[i], midPoints[i], vertices[i], midPoints[nexti], projPoints[nexti] }; Branch branch = new Branch(level+1, i+1, newPoints); branch.drawMe(); } } } : : 省略 : }
出力結果は以下のようになります。
参考書籍
ポスチュアとユーザインターフェースから見る iOS アプリのトレンド ー iOS7 の登場で変わるアプリのトレンド ー
最近読んだアラン・クーパー氏の著書『About Face 3』ではソフトウェアを「使われ方」と「見た目」の2つの視点で分類していました。拙著『プロの力を身につける iPhone/iPadアプリケーション開発の教科書』の第3章では iOS アプリを実装面から分類しました。実装面からの分類は UI 設計時には役に立ちますがアプリの方向性やターゲットユーザを決めるような UX 設計時にはあまり役に立ちません。UX 設計のようにアプリをマクロの視点から考えて設計するときには『About Face 3』で紹介されている分類のように実装に依存しないユーザ目線から分析したものの方が使えます。というわけでこの記事では『About Face 3』のソフトエェアのパターンを iOS アプリにあてはめて最近のアプリの傾向を分析してみます。
About Face 3におけるソフトウェアのパターン
『About Face 3』では以下のように、ソフトウェアを「使われ方」と「見た目」の2つの視点から分類しています。
使われ方の違いによるパターン
使われ方の違いによるパターンは『About Face 3』の中ではポスチュアパターンとして紹介されています。聞き慣れない単語ですが、ポスチュアについては以下のように説明されています。
製品のポスチュアとは、振る舞いのスタンスであり、ユーザーに対して自分をどのように見せるかということだ。ポスチュアは、ユーザが製品とのインタラクションにどのくらいの注意を払うか、ユーザーの注意の程度に応じて製品がどのように振る舞うかという問題である。
製品全体とユーザとの距離を決めるのを助けてくれるパターンです。ポスチュアには以下の3つのパターンがあります。
- デーモン的なポスチュア
ユーザの気づかないところで動いているソフトウェア。通常ユーザとの間にインタラクションは発生しない。プリンタドライバやネットワーク接続、サーバからの Push 通知など - 単発的なポスチュア
ごくわずかのコントロールで1つの機能をサポートし、すぐに通り過ぎてしまうソフトウェア。必要に応じて起動され、ユーザが目的を達成するとすぐに閉じられる。天気予報アプリや音楽再生アプリ、マップアプリなど - 支配者的なポスチュア
ユーザの注意を長時間に渡って独占するソフトウェア。ユーザはソフトウェアを持続的に起動状態に保つことが多い。電子メールアプリやプレゼンテーション作成アプリ、音楽制作アプリなど
単発的なポスチュアと支配者的なポスチュアはターゲットとするユーザの傾向がちがいます。表にすると以下のようになります。
見た目の違いによる分類
見た目の違いによる分類は『About Face 3』の中ではユーザインターフェースのパラダイムとして紹介されています。ソフトウェアの見た目には以下の3つのパラダイムがあります。
- 実装中心のインターフェース
ソフトウェアがどのように作られているか、どのように組み立てられているかをそのまま表現しているインターフェース。ユーザはソフトウェアの内部的な仕組みやプログラムの動かし方を理解していないと使えない - メタファ(metaphor)的なインターフェース
インンターフェースに含まれているビジュアルな手がかりとその機能をユーザが直感的に結びつけてくれることを頼りとするインターフェース。スキュアモーフィック(skeuomorphic)デザイン*1を採用しているソフトウェアに使用されることが多い。iOS6までのアップル純正アプリはスキュアモーフィックデザインをベースにメタファを巧みに取り入れたものが多い - イディオム(idiom)的なインターフェース
ユーザがイディオムを学んで使うメカニズムを基礎としているインターフェース。実装中心やメタファ的なインターフェースと違い技術的な知識や機能の直感的な理解に依存しない。イディオムとは慣用句や熟語という意味の単語でここでいうイディオムとは単純な操作を組み合わせて意味のある操作を定義することを指します。わかりやすい例では、マウスのダブルクリックや右クリックに特定の動作を割り当てたり(ダブルクリックでアプリが起動する、右クリックでコンテキストメニューが表示される)、スマホのジェスチャー(ダブルタップや画面長押し、フリック)に特定の動作を割り当てるなどがあります。イディオムを操作の中心にしたソフトウェアではメタファがかえってユーザの学習を妨げることがあるため、リアルな見た目とは相性が悪い
近年、直感的にわかるソフトウェアがユーザの為によいとされる傾向があり、メタファ中心のインターフェースを持ったソフトウェアが(特にスマートフォンでは)高い評価を得ていたように思います。『About Face 3』ではメタファの欠点として以下を挙げて、メタファに頼りすぎるソフトウェアに対して警告を発していました。
- メタファは文化的な違いや世代の違いを超えれない
とある国ではメタファになり得るものが別の国ではメタファにならない。またフロッピーディスクのアイコンや黒電話のアイコンのように現物を知っている世代にはメタファになるものが知らない世代にはメタファにならない(参考:保存アイコンでみえてくるアイコンデザインの勘違い) - 初心者から中級者になった後はメタファが操作の邪魔になる
メタファは初めて使うユーザには小さな効果があるが、操作に慣れるとメタファを必要としなくなるためかえって邪魔になることがある - メタファは現実世界に縛り付けてしまいコンピュータ本来の力を発揮できない
メタファを使うと現実世界の制限をそのままソフトウエアの世界に持ち込んでしまう - すべてをメタファで表すことは現実的に不可能
メタファで表せるものとそうでないものがある
逆にイディオムは学習が必要ではあるものの、優れたイディオムは一度学習すれば身に付くため、優れたイディオムがあればメタファに頼る必要がないとありました。とくに支配者的ポスチュアのように頻繁に使うソフトウェアにはメタファ中心のインターフェースよりもイディオム中心のインタフェースの方が相性が良いと思います。
クーパー氏の分類を iOS アプリに当てはめると
『About Face 3』の分類はあくまでデスクトップアプリケーションのための分類なので iOS アプリにあてはめる場合には少しアレンジしてやる必要があります。使われ方のパターンにあるデーモン的ポスチュアはOSの機能であったりアプリの1機能として実装されることがほとんどで単体アプリとして存在することはあまりないため除外して、見た目のパターンにある実装中心のソフトウェアは開発者の未熟さの結果できるものなので除外して考えてみます。
この2つを除くと、単発的と支配者的ポスチュアとメタファ的とイディオム的インターフェースが残ります。
これらはそれぞれ対極的な意味をもっています。そこでメタファ的/イディオム的ソフトウェアと単発的/支配者的ポスチュアをそれぞれXY軸にしてアプリを分類してみます。
2013年7月現在の iOS アプリの傾向
私が独断と偏見で選んだ2013年7月現在のアップル純正アプリとその他開発会社のアプリをグラフに配置すると以下のようになります。
アップルがメタファを多用したアプリを開発しているのに対して、Google がメタファに頼るのをやめてシンプルな見た目のアプリを開発している傾向があります。以下は表で取り上げたアプリの一覧です。
支配者的ポスチュア × イディオム中心
メールや SNS などコミュニケーション系アプリが多い印象です。スマートフォン向けの支配者的ポスチュアなアプリはまだまだ発展途上にあるようです。ただし Figure のような尖ったアプリもあります。iOS 7の登場でこのエリアのアプリがどのように発展するか要チェックです。
単発的ポスチュア × イディオム中心
とても挑戦的なアプリが多い印象です。単機能のアプリが多くチャレンジしやすいのかもしれません。
- Clear
ToDo管理アプリ - Weathercube - Gestural Weather
天気アプリ - Google 検索
Google 検索アプリ - Google Maps
Google が開発した地図アプリ - ClearWeather
天気アプリ
支配者的ポスチュア × メタファ中心
About Face 3では支配者的ポスチュアとメタファは相性が悪いとされています。また支配者的ポスチュアなアプリが全体的に少ないのでこのエリアのアプリもあまりありませんでした。AmpliTube のように現実世界を忠実にシミュレートするためアプリは今後も一定以上のニーズはありそうです。
- AmpliTube
ギターアンプとエフェクターのシミュレータアプリ - GarageBand
アップル純正の音楽制作アプリ - LINE
無料メッセージアプリ
単発的ポスチュア × メタファ中心
アップル純正のアプリがわかりやすかったので集めてみました。
- メモ
メモ帳アプリ、紙の質感やページめくりのメタファを使用して実物のメモ帳に似せています - カメラ
カメラアプリ、カメラのレンズや絞りのメタファを使用しています - カレンダー
カレンダーアプリ、紙の質感はないものの1ヶ月を1ページに配置する実物のカレンダーの配置を使用しています - マップ
地図アプリ、紙の質感を使用しています - 天気
天気予報アプリ、天気を表すアイコンが写実的です
フラットデザインの流行と iOS7
6月に発表された iOS7 は従来のメタファを多用したデザインをやめて見た目がとてもシンプルになりました(iOS7 の概要は以下の YouTube 映像で見ることが出来ます)。
iOS が iPhone 発売以来はじめて大幅な刷新をするということで大きな話題になりニュースやブログで沢山の記事が書かれました。ただどの記事も見た目の話に終始していて「スキュアモーフィックデザイン VS フラットデザイン」という視点の内容が目立ちました。個人的にはスキュアモーフィックとフラットデザインは対極に語るものではないと考えています。見た目だけでなく、この記事で分類したようにユーザからどのように使われるのかという視点を入れてやることで少し違った感じ方が出来ると思います。
アップルは iOS7 で再びイノベーションを起こそうとしているように思います。単純に見た目を変えるだけでは終わらないと思います。個人的には成功するかどうかの鍵は、スキュアモーフィックデザインを捨てて革新的な OS になるのか、それともスキュアモーフィックデザインを捨てきれずに見た目だけシンプルになり、以前より使いにくくなってしまうのかだと考えています。
アップルがデザインを一新したのは、近年 iPhone に限らず Android、Windows Phone などスマートフォンが当たり前になり、大半のユーザがスマホ特有の操作(タップやフリックなど)に慣れたことで、見た目のわかりやすさよりも毎日使っても飽きない実用性の高いアプリが求められるようになったのだと思います。シンプルな見た目でメタファに頼らないフラットデザインが流行している理由もこのあたりにあるのではないかと考えています。
AmpliTube のようにメタファを多用して現実世界を忠実に再現するのが目的のアプリは iOS7 になっても必要とされると思います。とはいえ全体的には下の図の示す方向に流行は徐々に変わっていくと思います。
まとめ
ここまでの内容をまとめると以下の3つが重要になります。
- アプリを見た目(インターフェース)からだけ分類するのではなく、ユーザがアプリとどのように付き合うかという視点(ポスチュア)からも分類する
- ポスチュアの違いがアプリの見た目に影響を与えることがある
支配者的ポスチュアのソフトウェアはメタファが邪魔になる時がある - iOS7 の登場でメタファを多用したアプリよりもシンプルな見た目のアプリがトレンドになる
シンプルな見た目だからデザインが簡単というわけではなくむしろ抽象度が上がるので、デザインの難易度はスキュアモーフィックを前提にしたアプリを開発するよりも難しくなると思います。一歩間違うと「シンプル=何もない」になってしまいそうで非常に怖いです。また見た目をシンプルにしてジェスチャーを多用するアプリがユーザに受け入れられるのか、今のところよくわからない(Clear や Weathercube が必ずしも使いやすいわけではない)です。開発者としてチャレンジしがいはあるものの大変な時代に突入しそうだなぁと、今から少々ビビっています。逆に1アプリユーザの立場から考えると今後、革新的で便利なアプリが増えて面白くなりそうだなとわくわくしています。
参考記事
*1:人工物からコピーされたデザイン。かならずしもリアルな見た目である必要はないので注意。時計の長針と短針をモチーフに使ったフラットな見た目のアプリや電卓のキー配列をそのままコピーしたフラットな見た目のアプリなどもスキュアモーフィックデザインに分類される
アフォーダンスとシグニファイア
最近デザイン系の本を読んでいて「アフォーダンス」という言葉の使われ方が変わってきたなと感じています。
アフォーダンスはD・A・ノーマン氏の著書「誰のためのデザイン」がきっかけに広まった言葉で、もとはアメリカの知覚心理学者のJ・J・ギブソン氏が提唱した概念です。Wikiによると環境が動物に与える意味のことをアフォーダンスと呼ぶようです。私自身はユーザをとある操作に誘導するための重要な概念だなぐらいの認識でした。
最近ではデザイン系、なかでもユーザインターフェースについて書かれたの本やブログで当たり前のように出てきていうように感じます。
私がアフォーダンスという言葉の使われ方が変わったなと感じたのは先の「誰のためのデザイン」を書いたD・A・ノーマン氏の別の著書「複雑さと共に暮らす」を読んだ時です。その中でノーマン氏はアフォーダンスという言葉が一人歩きして本来の意味から離れて使われていることに言及しています。またアフォーダンスにかわる新たな概念としてシグニファイアを新たに提唱していました。以下その引用です。
デザインの用語では、シグニファイアはしばしばアフォーダンス、より正確には「知覚されたアフォーダンス」と呼ばれる。これは私が「誰のためのデザイン?」で紹介した用語なのだが、申し訳ないことに、実際のところ私の失敗だった。アフォーダンスはシグナルという言葉が持つよりもずっと深い意味を持っている。アフォーダンスは必ずしも知覚可能である必要はない。「シグニファイア」という用語を導入するのは、デザインの用語をより正確にするためである。
複雑さと共に暮らす ーー 2.社会的シグニファイア 100ページ
他にもアラン・クーパー氏の著書「About Face 3」では人工物から直感的に知覚する性質を単なるアフォーダンスと使い分けてマニュアルアフォーダンスと呼んでいました。About Face 3ではノーマン氏の「誰のためのデザイン?」を引き合いにアフォーダンスについて以下のように説明しています。
ドナルド・ノーマンは独創的な著書「The Design of Everyday Things」(「誰のためのデザイン?ー認知科学者のデザイン原論」野島久雄訳、1990年新曜社)の中で、アフォーダンスという用語を作り出した。彼は、この言葉を「ものの見かけ上の性質と本当の性質、特に、ものがどのように使えるかを決める根本的な性質を指す」と定義している。
About Face 3 ーー 13.4 マニュアルアフォーダンス 287ページ
ーー中略ーー
私たちの目的では、ノーマンの定義は大切なことを1つ省略している。それは、それらの性質が私たちに与えてくれるものをどのようにして知るかということだ。何かを見て、その使い方を理解したとき、つまり、アフォーダンスを理解した時には、もとの使い方の結びつきを作るために何らかの手段を使ったはずなのだ。
そこで、私たちはノーマンの定義から「と本当の性質」というところを省略することを提案したい。
ーー中略ーー
しかし、話を明確にするために、手でものを操作する方法についての、このような本能的な理解については、マニュアルアフォーダンスと呼ぶことにしよう。手や足に合うような人工物を見ると、私たちはそれを直接操作できるものだと理解し、説明を書いて示す必要はなくなる。
シグニファイア、マニュアルアフォーダンスともに言葉自体はあまり広まっているようには感じませんが、アフォーダンスを本来の意味できちんと使おうする動きは徐々に広まっているように思います。
例えばつい最近発売されたばかりのLukas Mathis氏の著書「インタフェースデザインの実践教室」でもアフォーダンスについて以下のように知覚可能なアフォーダンスと説明していました。
アフォーダンス(affodance)はもともと「環境が(人間を含む)動物に対して与える(affordする)意味」を指す言葉ですが、「人間にとって知覚可能なデザイン上の手がかり」の意味で使われる場合もあります。ドナルド・ノーマンは「複雑さと共に暮らすーデザインの挑戦」では、「知覚可能なデザイン上の手がかり」の意味では「シグニファイア(signifier)」という言葉を使い、曖昧な使われ方をしている「アフォーダンス」という言葉は避けた方がよいとしています。この本では「アフォーダンス」という言葉を用いますが、曖昧にならないよう「知覚可能なアフォーダンス」などと記述します。
インタフェースデザインの実践教室 ーー 9.5.7 指針その7ーアフォーダンス 78ページ
上記のシグニファイア、マニュアルアフォーダンス、知覚可能なアフォーダンス、これらの意味はかなり近いと思います。同じと考えても良さそうです。シグニファイアとアフォーダンスの関係を図にすると以下のようになると思います。
私自身は後藤武氏、佐々木正人氏、深澤直人氏の著書「デザインの生態学」を読んだ時にはじめて知ったくちで、何となくわかった気になって文章を読んだり使っていました。アフォーダンスとシグニファイア意識して使いたいなと思いました。
参考書籍
この記事で紹介した本を出版順に時系列に並べると以下のようになります。
- 誰のためのデザイン(1990年)
- デザインの生態学(2004年)
- About Face 3(2008年)
- 複雑さと共に暮らす(2011年)
- インタフェースデザインの実践教室(2013年)
【訂正】プロの力を身につける iPhone/iPadアプリケーション開発の教科書
「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」の中に誤りがありましたので訂正させていただきます。
3-3 データを活用したアプリの作り方
235ページ下から4行目
訂正前
次のように、UILabel と UITextField
オブジェクトを配置します。Object Libraryから「Label」と「TextField」を選んで、ストーリーボードの「Detail」と表示されているシーンの上に配置してください。
訂正後
次のように、UIScrollView、UILabel 、UITextField オブジェクトを配置します。Object Libraryから「Scroll View」「Label」「Text Field」を選んで、ストーリーボードの「Detail」と表示されているシーンの上に配置してください。
UIScrollViewについての記述が抜けていました。
237ページ最終行
訂正前
次のように、UILabel と UITextField オブジェクトを配置してください
訂正後
次のように、UIScrollView、UILabel 、UITextField オブジェクトを配置してください。
235ページと同じく、UIScrollViewについての記述が抜けていました。
239ページソースコード
訂正前
@synthesize detailItem = _detailItem; - (Person *)detailItem { if (!_detailItem) { _detailItem = [NSEntityDescription insertNewObjectForEntityForName:@"person") inManagedObjectContext:self.managedObjectContext]; _detailItem.address = [NSEntityDescription insertNewObjectForEntityForName:@"address") inManagedObjectContext:self.managedObjectContext]; } return _detailItem; }
訂正後
@synthesize detailItem = _detailItem; - (Person *)detailItem { if (!_detailItem) { _detailItem = [NSEntityDescription insertNewObjectForEntityForName:@"Person") inManagedObjectContext:self.managedObjectContext]; _detailItem.address = [NSEntityDescription insertNewObjectForEntityForName:@"Address") inManagedObjectContext:self.managedObjectContext]; } return _detailItem; }
insertNewObjectForEntityForName: に渡す引数が小文字はじまりになっていました。正しくは大文字はじまりでした。
今後、間違いを見つけ次第この記事に追記していきます。
間違いがないように編集者さんと何度もレビューをしてチェックをしたつもりでしたがもれがありました。この場を借りで読者の皆様にお詫び申し上げます。また間違いを指摘してくださいました読者様ありがとうございました。
今後も本書の間違いや訂正があればこの記事に追記して行きます。
NSManagedObjectでTo-Many関連を使用したときにコードの自動生成がされないバグの解決方法
Core Data の NSManagedObject クラスで以下のように To-Many(One-To-Many) 関連を使ったときにコードの自動生成をすると実装コードが生成されない場合があります。
通常の To-Many 関連では問題が起こらないのですが以下のように Ordered(順序) を指定してから NSManagedObject クラスのサブクラスの自動生成をすると実装コード(.m ファイルのコード)の一部が生成されません。
これは Xcode のバグみたいです。最新の Xcode 4.6.1 でも発生しています。
自動生成されないコードはプログラマ自身が実装する必要があります。以下は Transaction(取引) クラスと Journal(仕訳明細) クラスに Ordered の To-Many 関連を設定する場合のコードの実装例です。
#import "Transaction.h" #import "Journal.h" @implementation Transaction @dynamic abstract; @dynamic date; @dynamic entrepreneurNumber; @dynamic journals; static NSString *const kItemsKey = @"journals"; - (void)insertObject:(Journal *)value inJournalsAtIndex:(NSUInteger)idx { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet insertObject:value atIndex:idx]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)removeObjectFromJournalsAtIndex:(NSUInteger)idx { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet removeObjectAtIndex:idx]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)insertJournals:(NSArray *)values atIndexes:(NSIndexSet *)indexes { [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet insertObjects:values atIndexes:indexes]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)removeJournalsAtIndexes:(NSIndexSet *)indexes { [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet removeObjectsAtIndexes:indexes]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)replaceObjectInJournalsAtIndex:(NSUInteger)idx withObject:(Journal *)value { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet replaceObjectAtIndex:idx withObject:value]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)replaceJournalsAtIndexes:(NSIndexSet *)indexes withJournals:(NSArray *)values { [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet replaceObjectsAtIndexes:indexes withObjects:values]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)addJournalsObject:(Journal *)value { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSUInteger idx = [tmpOrderedSet count]; NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet addObject:value]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)removeJournalsObject:(Journal *)value { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSUInteger idx = [tmpOrderedSet indexOfObject:value]; if (idx != NSNotFound) { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet removeObject:value]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } } - (void)addJournals:(NSOrderedSet *)values { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; NSUInteger valuesCount = [values count]; NSUInteger objectsCount = [tmpOrderedSet count]; for (NSUInteger i = 0; i < valuesCount; ++i) { [indexes addIndex:(objectsCount + i)]; } if (valuesCount > 0) { [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet addObjectsFromArray:[values array]]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } } - (void)removeJournals:(NSOrderedSet *)values { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; for (id value in values) { NSUInteger idx = [tmpOrderedSet indexOfObject:value]; if (idx != NSNotFound) { [indexes addIndex:idx]; } } if ([indexes count] > 0) { [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet removeObjectsAtIndexes:indexes]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } } @end
Xcode の次のバージョンで修正されると良いのですが...。
関連書籍
Core Dataに関する基礎情報はこちらの本を参考にしてください。
本日発売!プロの力を身につける iPhone/iPadアプリケーション開発の教科書
ついにこの日がやってきました。
「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」本日発売です。
先ほど職場近くの「MARUZEN & JUNKUDO」さんに行ってみたらバッチリ置いてありました。感無量です。読者の皆様のアプリ開発助けになれば幸いです。よろしくお願いします。
本の詳しい内容はこちらの記事をご覧下さい。
レビューしていただきました。
パンダピアノで有名なアプリ開発者のムラモトタケシさんにレビューをしていただきました。ありがとうございます!