AsyncDisplayKit(テクスチャ)記事シリーズの第2部へようこそ!
AsyncDisplayKitビルドシステムを使用すると、非常に高速な宣言型コードを記述できます。
クイック設定に加えて、アプリケーションが実行されているデバイスに自動的に適応します。 アプリケーションのView Controllerで、またはiPad用アプリケーションのポップオーバーとして使用できるノードを作成しようとしているとします。 レイアウトが正しく作成されていれば、レイアウトの基本コードを変更することを心配せずにノードをこの新しい環境に転送できます!
このAsyncDisplayKit 2.0チュートリアルでは、 最初の部分からCardNodeクラスに戻り、作成に使用されたレイアウト仕様について学習します。 レイアウト仕様を作成して、目的の結果を得ることがどれほど簡単かがわかります。
自動レイアウトの問題
「自動レイアウトの何が問題なのですか!」と叫んでいるのが聞こえます。自動レイアウトでは、作成した各コストラインは、連立方程式の方程式として表示されます。 これは、定数を追加するたびに、定数を指数関数的に計算する時間が長くなることを意味します。 この計算は常にメインスレッドで実行されます。
ASDK設計の目標の1つは、UIKit APIに可能な限り忠実に従うことです。 残念ながら、自動レイアウトは不透明なシステムであり、別のスレッドでconstを計算する方法はありません。
さあ始めましょう
開始するには、 ここからスタータープロジェクトをダウンロードします 。 レイアウトの仕様の一部を学習するので、最初のAsyncDisplayKit 2.0チュートリアルの完成品の修正版から始める価値があります。
注 :AsyncDisplayKit 2.0チュートリアルで作業を開始する前に、 最初のパートをお読みください。
ASLayoutSpecの紹介
始める前に、少し背景を説明します。
レイアウト仕様は、レイアウトシステムを一般化したものです。これについては、 Building Paper Eventで簡単に説明しています。 アイデアは、ノードとサブノードの次元と位置の計算と適用を統一し、それらを再利用できるようにすることです。
ASDK 1.9.Xでは、非同期レイアウトを作成できましたが、レイアウトコードはUIKitの事前自動レイアウトに似ていました 。 ノードサブノードのサイズは、 -calculateSizeThatFits:メソッドを使用して計算できます。 これらのサイズをキャッシュし、後で-layoutに追加できます。 また、ノードの位置は、古き良き数学を使用して計算する必要がありました-誰も数学を台無しにしたくない
レイアウト仕様
ASDK 2.0では、ASDisplayNodeのサブクラスは-layoutSpecThatFits:を実装できます。 クラスオブジェクトASLayoutSpecは、すべてのサブノードのサイズと位置を決定します。 レイアウト仕様は、親ノードのサイズも決定します。
ノードは、 -layoutSpecThatFits:からレイアウト仕様オブジェクトを返します。 このオブジェクトは、ノードのサイズ、およびそのすべてのサブノードのサイズと位置を再帰的に決定します。
ThatFits引数は、クラスASSizeRangeのオブジェクトです。 これには、ノードの最小サイズと最大サイズを決定するCGSize型の2つのプロパティ(最小と最大)があります。
ASDKはさまざまな種類の仕様を提供します。 それらのいくつかを次に示します。
- ASStackLayoutSpec :子の垂直または水平スタックを定義します。 justifyContentプロパティは、スタック方向の子間の距離を決定し、 alignItemは反対の軸に沿った子の距離を決定します。 この仕様は、UIKit UIStackViewと同様に構成されます。
- ASOverlayLayoutSpec :あるレイアウト要素を別のレイアウト要素に引き伸ばすことができます。 これをオーバーレイするオブジェクトは、これが機能するために固有のコンテンツサイズを持っている必要があります。
- ASRelativeLayoutSpec :使用可能なスペース内の相対位置にアイテムを配置します。 9つのスライス画像の9つのセクションを考えてください。 これらのセクションのいずれかにオブジェクトを配置できます。
- ASInsetLayoutSpec :既存のオブジェクトのインデントを許可します。 セルの周りに古典的な「iOS 16ピクセルのインデント」が必要ですか? 問題ありません!
ASLayoutElementプロトコル
レイアウト仕様は、1つ以上の子のレイアウトを制御します。 要素は、 ASTextNodeやASImageNodeなどのノードにすることができます。 ノードに加えて、子レイアウト仕様テンプレートも異なるレイアウト仕様になる場合があります。
ねえ、どうやってそれが可能ですか?
レイアウト仕様の子要素は、 ASLayoutElementプロトコルに準拠する必要があります。 ASLayoutSpecとASDisplayNodeはASLayoutElementに対応しているため、型とそのサブクラスの両方を子にすることができます。
このシンプルなコンセプトは非常に強力です。 最も重要なレイアウト機能の1つはASStackLayoutSpecです。 画像とテキストを配置する機能は1つですが、画像と別のスタックを配置する機能は別です。
あなたは絶対に正しいです。 決闘の時が来ました! つまり、コードを書く...
動物の画像を配置する
ですから、あなたは仕事をしていて、デザイナーはあなたが取り組んでいる新しいアプリケーション「動物のための百科事典」のために彼が望むもののスクリーンショットを送ってきました。
最初に行うことは、全体的なレイアウトを理解するために、画面を適切なレイアウト仕様に分割することです。 これは時には混乱を招く可能性がありますが、レイアウトの威力は配置の容易さに依存することを忘れないでください。 シンプルに始めましょう。
今後は、上半分と下半分が一緒にスタック上で完全に機能すると言えます。 これがわかったので、2つの半分を別々に配置して、最後に結合できます。
スタータープロジェクトを解凍し、 RainforestStarter.xcworkspaceを開きます。 CardNode.mの-layoutSpecThatFits:メソッドに移動します。 現在は、空のASLayoutSpecオブジェクトを返すだけです。
プロジェクトをコンパイルして実行すると、次のように表示されます。
さて、これはほんの始まりです。 最初に動物の画像を表示してみてはどうですか?
デフォルトでは、ネットワークイメージノードにはコンテンツがないため、独自のサイズがありません。 スクリーンショットを見ると、動物の画像は画面の幅全体と高さの2/3を占める必要があると言えます。
これを行うには、既存のreturnステートメントを次のステートメントに置き換えます。
//1 CGFloat ratio = constrainedSize.min.height/constrainedSize.min.width; //2 ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:ratio child:self.animalImageNode]; //3 return imageRatioSpec;
各番号のコメントを順番に検討してください。
- 比率の計算 :最初に、画像に適用する比率を決定します。 比率は高さ/幅によって決まります。 ここでは、この画像の高さが最小セルの高さ、つまり画面の高さの2/3であることを示します。
- 比率レイアウト仕様の作成 :次に、計算された関係と子animalImageNodeを使用して、新しいASRatioLayoutSpecを作成します。
- 仕様を返す :返されるimageRatioSpecは、セルの高さと幅を決定します。
コンパイルして実行し、レイアウトがどのように見えるかを確認します。
とても簡単ですね。 画像はサイズを持つ唯一の要素であるため、セルはそれに合わせて伸縮します。
注 :テーブルノードセルに渡されるconstrainedSizeは、最小値(0、0)と最大値( tableNodeWidth、INF )で構成されます。 したがって、 preferredFrameSizeを使用して画像の高さを決定する必要があります。 preferredFrameSizeは、パート1でAnimalPagerControllerに設定されました。
グラデーションを追加
動物の画像ができたので、次のステップは、グラデーションノードをその上に追加することです。
ASOverlayLayoutSpecは、ジョブの単なる仕様です。
imageRatioSpecを初期化した後、最初に次の行を追加します。
ASOverlayLayoutSpec *gradientOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imageRatioSpec overlay:self.gradientNode];
レイアウト仕様を作成すると、残りを含む仕様が常に取得されます。 gradientOverlaySpecの時間です。
現在のリターンを次のものに置き換えます。
return gradientOverlaySpec;
コンパイルして実行し、各imageNodeオブジェクトに広がるグラデーションを確認します。
すべての鳥の勾配は素晴らしいです!
動物名のテキストを追加
あとは、動物の名前を追加するだけです。
タスクは簡単に思えますが、考慮すべき要件がいくつかあります。
- 名前はグラデーションの上に配置する必要があります。
- 名前は動物の画像の左下隅にある必要があります。
- インデント-左に16ポイント、下に8ポイント。
テキストを一番上に配置する方法はすでに知っています。 実証済みの真のオーバーレイ仕様から脱却する時が来ました。
gradientOverlaySpecの直後に次の行を追加します
ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:gradientOverlaySpec overlay:self.animalNameTextNode];
さらに、 returnステートメントを次のように変更する必要があります。
return nameOverlaySpec;
アプリケーションをコンパイルして実行し、画面上のテキストを確認します。
悪くない。 このテキストを下隅に移動するだけです。
あなたが遭遇する一般的なケースに言及する価値があります。 あなたは鳥にテキストを持っているので、自然な欲求は、他の仕様でOverlaySpecをラップして、 好きな場所に置くことです。 原則として、一歩後退して、あなたが表現しようとしていることについて考える必要があります。
この場合、 nameOverlaySpecを使用して、既存のコンテンツの上にある他のものを拡張します。
実際、コンテンツのタイトルを拡張したくありません。 空き領域の左下隅にあることを名前に伝え、このレイアウト仕様を利用可能な領域に拡張します。
ASRelativeLayoutSpecの紹介
実際にはASRelativeLayoutSpecが必要です 。
ASRelativeLayoutSpecはASLayoutElement子を受け入れ、それが使用可能なスペースを考慮して、指示に従ってこの子を配置します。
相対仕様を定義するとき、プロパティverticalPositionおよびhorizontalPositionを設定できます。
これらの2つのプロパティは次のとおりです。
- ASRelativeLayoutSpecPositionStart
- ASRelativeLayoutSpecPositionCenter
- ASRelativeLayoutSpecPositionEnd
この組み合わせにより、オブジェクトをコーナー、エッジのいずれか、または使用可能なスペースの中央に配置できます。
演習として、このカエルを空き領域の右端にどのように配置しますか?
「verticalPositionをASRelativeLayoutSpecPositionCenterに設定し、 horizontalPositionをASRelativeLayoutSpecPositionEndに設定」と言った場合、あなたは正しいです!
練習が終わったので、次の行はもう少し意味があります。 nameOverlaySpecの直前に次の行を追加します 。
ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart verticalPosition:ASRelativeLayoutSpecPositionEnd sizingOption:ASRelativeLayoutSpecSizingOptionDefault child:self.animalNameTextNode];
したがって、子のhorizontalPositionをstartに、verticalPositionを完了に設定します。 カエルの言語では、次のようになります。
相対仕様が設定されたので、 nameOverlaySpec定義を次のように変更します。
ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:gradientOverlaySpec overlay:relativeSpec];
コンパイルして実行し、得られるものを確認します。
いいね! セルのこの半分で行うことがもう1つあります。
ASInsetLayoutSpecの紹介
最後に行う必要があるのは、動物の名前を16ポイント左に、8ポイント下に置くことです。 これにはASInsetLayoutSpecがあります。
オブジェクトの周囲に小さなインデントを追加するには、挿入仕様でオブジェクトをラップし、 UIEdgeInsetsに必要なインデントを決定させます。
nameOverlaySpecの後に次の行を追加します。
ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:nameOverlaySpec];
次に、最も外側の仕様を返すようにreturnステートメントを再度変更します。
return nameInsetSpec;
コンパイルして実行します。
オーバーレイには動物の画像が含まれているため、オーバーレイが覆う領域全体に挿入を適用したくない場合があります。
本当に必要なのは、挿入をrelativeSpecスペースに適用することです。 これを修正するには、まず現在のnameInsetSpec定義を削除します。
次に、 nameOverlaySpec定義の直前に、次の新しい改良バージョンを追加します。
ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:relativeSpec];
ここで、 relativeSpecではなくnameOverlaySpecが新しい挿入をオーバーレイする必要があります。 nameOverlaySpecの定義を次のように置き換えます。
ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:gradientOverlaySpec overlay:nameInsetSpec];
最後に、以下に戻ります。
return nameOverlaySpec;
コンパイルして実行し、何が起こったかを確認します。
半分の作業が完了しました!
下半分
後半は少し簡単です。 これは、その周りに挿入物がある動物の単なる説明です...そしてあなたはすでにそれを行う方法を知っています。
return文の前に次の行を追加して、説明テキストを含む挿入を作成します。
ASInsetLayoutSpec *descriptionTextInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(16.0, 28.0, 12.0, 28.0) child:self.animalDescriptionTextNode];
この挿入を返し、アプリケーションをコンパイルして起動すると、次のように表示されます。
これはまさにあなたが望んでいたものです。 両方の半分がわかったので、それらを組み合わせるのは難しくありません。
コンテンツの内部寸法
テキストがスペースを埋めるためにコンテンツのサイズを持っていることを心配する必要はありません。 これは、 ASTextNodeがテキストと属性に基づいて独自の内部コンテンツサイズを持っているためです。
次のノードにはデフォルトのサイズはありません。
- ASDisplayNodeのサブクラス
- ASNetworkImageNodeおよびASMultiplexImageNode
- ASVideoNodeおよびASVideoPlayerNode
一般的には、これらのノードには元のコンテンツがないため、独自のサイズを決定する方法はありません。 使用する特定のサイズを設定する前に、 preferredFrameSizeを持つか、レイアウト仕様に配置する必要があります。
ASStackLayoutSpecの紹介
これは、スタックレイアウト仕様を使用するのに最適な時期です。 これはUIStackViewと同等のレイアウト仕様としてとらえることができますが、自動後方互換性は非常にエレガントです。
スタックは、垂直と水平の両方で定義できます。 他のすべての人と同様に、レイアウト仕様はノードまたは他のマークアップを子として受け入れることができます。
このスタックを構成するには、挿入の説明を定義した後、次の3行を追加します。
ASStackLayoutSpec *verticalStackSpec = [[ASStackLayoutSpec alloc] init]; verticalStackSpec.direction = ASStackLayoutDirectionVertical; verticalStackSpec.children = @[nameOverlaySpec, descriptionTextInsetSpec];
ここでは、方向を垂直に設定し、上半分と下半分を子として追加して、スタックを作成します。
そして再び、新しいレイアウト仕様を返します。
return verticalStackSpec;
コンパイルして実行します。 ほぼ完了!
注 :前述のように、スタックは最も重要なレイアウト仕様の1つです。 ほとんどのレイアウトは、スタックまたは一連のネストされたスタックとして表現できます。
それぞれが独自のjustifyContentおよびalignItems設定を持っているネストスタックは、非常に表現力があり、非常に失望する可能性があることを意味します 。 より詳細な調査については、 フレックスボックスカエルゲームとAsync Display Kitのドキュメントを確認してください。
ASBackgroundLayoutSpecの紹介
オーバーレイの仕様を覚えていますか? ルールの1つは、それに重ねるオブジェクトは独自のサイズを持たなければならないということです。
後ろの要素がサイズを決定し、前の要素がちょうどその上に伸びます。
バックグラウンドの仕様はまったく逆です。 独自のサイズを定義できる要素と、その要素の後に拡大したい要素がある場合、背景の指定が必要になります。
この場合、背景レイアウト仕様を使用して、ぼやけた動物の画像を引き伸ばす必要があります。
これを行うには、次の行を追加します。
ASBackgroundLayoutSpec *backgroundLayoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:verticalStackSpec background:self.backgroundImageNode];
そして、 returnステートメントを置き換えます
return backgroundLayoutSpec;
コンパイルして実行し、何が起こったかを確認します。
完成したプロジェクトはこちらから入手できます。 Swift用のバージョンもあることを繰り返します。
これらの概念を理解したら、ドキュメントの調査を開始します。 これは、レイアウトシステムで何ができるかについてのほんの小さな概要でした。
このAsyncDisplayKit 2.0チュートリアルをお楽しみください。質問がある場合は、コメントに自由に残してください。
PS記事の翻訳に協力してくれたBeataSunshineとevgenに感謝します。