3D 空間でのレイヤー レイアウトの基本をいくつか理解したところで、ソリッド 3D オブジェクト (実際には専門的にはボイド オブジェクトと呼ばれるものですが、ソリッドに見えます) を作成してみましょう。 6 つの個別のビューを使用して立方体の各面を構築します。
この例では、Interface Builder を使用して立方体の面を構築します (図 5.19) もちろん、コードで記述することもできますが、Interface Builder を使用する利点は、各面にサブビューを簡単に追加できることです。これらの面は、ビューとコントロールを含む単なる通常のユーザー インターフェイス要素であり、インターフェイスの完全にインタラクティブな部分であり、立方体に折りたたんでもこの性質は変わらないことに注意してください。
図 5.19 Interface Builder を使用して立方体の 6 つの面をレイアウトする
これらの面ビューはメイン ビューには配置されませんが、ルート nib ファイル内に大まかに配置されます。それらをこのコンテナに配置する方法については気にしません。これらは後でレイヤーの変換を使用して再配置され、Interface Builder を使用してコンテナ ビューの外側に配置すると、コンテンツをより明確に確認しやすくなります。すべてをメインビューに重ねて詰め込むと、見苦しくなります。
それらの間の関係を明確に識別するためにビュー内に色付きの UILabel を配置し、後で簡単に説明する 3 番目のサイドビューに UIButton を配置します。
ビューをキューブに整理するための具体的なコードをリスト 5.9 に示し、その結果を図 5.20 に示します。
図 5.20フェイスアップ立方体
From この角度からは立方体がブロックのように見えるだけです。それをよりよく理解するために、別の視点に変更します。
この立方体の回転は、各面を個別に回転する必要があるため、扱いにくくなります。もう 1 つの簡単な解決策は、コンテナ ビューの sublayerTransform を調整してカメラを回転することです。
次の行を追加して、containerView レイヤーの透視変換行列を回転させます:
@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces; @end @implementation ViewController - (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform{ //get the face view and add it to the container UIView *face = self.faces[index]; [self.containerView addSubview:face]; //center the face view within the container CGSize containerSize = self.containerView.bounds.size; face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0); // apply the transform face.layer.transform = transform;} - (void)viewDidLoad{ [super viewDidLoad]; //set up the container sublayer transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; self.containerView.layer.sublayerTransform = perspective; //add cube face 1 CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100); [self addFace:0 withTransform:transform]; //add cube face 2 transform = CATransform3DMakeTranslation(100, 0, 0); transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0); [self addFace:1 withTransform:transform]; //add cube face 3 transform = CATransform3DMakeTranslation(0, -100, 0); transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0); [self addFace:2 withTransform:transform]; //add cube face 4 transform = CATransform3DMakeTranslation(0, 100, 0); transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0); [self addFace:3 withTransform:transform]; //add cube face 5 transform = CATransform3DMakeTranslation(-100, 0, 0); transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0); [self addFace:4 withTransform:transform]; //add cube face 6 transform = CATransform3DMakeTranslation(0, 0, -100); transform = CATransform3DRotate(transform, M_PI, 0, 1, 0); [self addFace:5 withTransform:transform];} @end
これは、カメラ (またはカメラを基準としたシーン全体、このように考えることができます) を Y 軸を中心に回転させます。 X 軸を中心に 45 度回転します。次に、立方体を別の角度から見ると、実際にどのように見えるかがわかります (図 5.21)。
図 5.21 角の 1 つから見た立方体
光と影
これで、確かにより立方体のように見えますが、各面間の接続を区別するのはまだ困難です。 Core Animation はレイヤーを 3D で表示できますが、光の概念はありません。立方体をよりリアルに見せたい場合は、影効果を自分で作成する必要があります。これは、各顔の背景色を変更するか、光効果のある画像を直接使用することで調整できます。
光効果を動的に作成する必要がある場合は、各ビューの方向に応じて異なるアルファ値を適用して半透明のシャドウ レイヤーを作成できますが、シャドウ レイヤーの不透明度を計算するには、各面ベクトルの法線イメージ (表面に垂直なベクトル) を取得し、仮想光源に基づいて 2 つのベクトルの外積を計算する必要があります。外積は光源とレイヤー間の角度を表し、明るさを決定します。
リスト 5.10 では、GLKit フレームワークを使用してベクトル計算を行っています (コードを実行するには GLKit ライブラリを導入する必要があります)。各面の CATransform3D が GLKMatrix4 に変換され、3x が取得されます。 GLKMatrix4GetMatrix3 関数。この回転行列はレイヤーの方向を指定し、これを使用して法線ベクトルの値を取得できます。
結果を図 5.22 に示します。LIGHT_DIRECTION と AMBIENT_LIGHT の値を調整して照明効果を切り替えてみます
リスト 5.10 立方体の表面に動的照明効果を適用します
perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0); perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
図 5.22 ライト効果を動的に計算した後の立方体
3 番目のサーフェスの上部にボタンが表示されていることがわかりますが、それをクリックしても何も起こりません。
これは、iOS が 3D シーンで応答イベントを正しく処理するからではなく、実際に実行できるものです。問題は見る順番です。第 3 章で簡単に説明したように、クリック イベントの処理は、3D 空間内の Z 軸の順序ではなく、親ビュー内のビューの順序によって決まります。キューブにビューを追加するときは、実際には順序に従って追加するため、ビュー/レイヤーの順序に関しては、4、5、6 が 3 の前になります。
4、5、6 の表面が見えなくても (1、2、3 で覆われているため)、iOS はイベント応答で以前の順序を維持します。 Surface 3 のボタンをクリックしようとすると、Surface 4、5、および 6 は、ボタン上のオブジェクトをオーバーレイする通常の 2D レイアウトと同様に、クリック イベントをインターセプトします (クリックの位置に応じて)。
doubleSided を NO に設定すると、ビューの背後にあるコンテンツがレンダリングされなくなるため、この問題は解決されると思われるかもしれませんが、実際には機能しません。カメラに背を向けているために非表示になっているビューでも、クリック イベントに応答します (これは、イベントに応答しない、hidden プロパティを設定するかアルファを 0 に設定することによって非表示にされるビューとは異なります)。したがって、両面レンダリングを無効にしても、この問題は解決されません (ただし、パフォーマンスの問題により、NO に設定する必要があります)。
ここにはいくつかの正しい解決策があります。Surface 3 を除くすべてのビューの userInteractionEnabled プロパティを NO に設定して、イベント配信を無効にします。または、コードを使用してビュー 3 をビュー 6 に単純にオーバーレイします。いずれの場合も、ボタンをクリックすることができます (図 5.23)。
図 5.23 背景ビューでボタンがブロックされなくなり、ボタンをクリックできるようになりました
この章では、いくつかの 2D および 3D 変換について説明しました。マトリックス計算の基本と、Core Animation を使用して 3D シーンを作成する方法を学習しました。レイヤーがバックグラウンドでどのように表示されるかを見てきましたが、平面的な画像を実際の 3 次元効果に変えることはできないことがわかりました。最後に、デモを使用してビュー内でレイヤーを追加する順序を説明しました。表示されている順序よりも高くなります。