こんにちはHabr!
最新のオープンソース開発CGLayout
制限に基づいた、Autolayoutに続くiOSの2番目のマークアップシステムを紹介したいと思います。
「別の自動レイアウトシステム...なぜ?何のため?」 -あなたはおそらく考えた。
確かに、iOSコミュニティは既にかなりの数のレイアウトライブラリを作成していますが、Autolayoutは言うまでもなく、それらのどれも手動レイアウトの真に大規模な代替手段になりませんでした。
CGLayout
は抽象エンティティと連携します。これにより、UIView、CALayerを同時に使用して、 not rendered
オブジェクトでnot rendered
マークアップを構築not rendered
。 また、単一の座標空間があるため、階層の異なるレベルにある要素間に依存関係を構築できます。 バックグラウンドスレッドでの作業が可能で、キャッシュが容易で、拡張が容易で、さらに多くの機能があります。
CGLayout
大規模なプロジェクトに発展する見込みのCGLayout
機能的な製品です。
しかし、当初は、いつものように、あなたの人生を単純化するという目標が当たり前でした。
Autolayoutのパフォーマンスが低いため、または複雑なロジックのために、誰もが手動レイアウトを作成する必要がある場合があります。 したがって、いくつかの拡張機能は常に書かれていました(la setFrameThatFitsなど)。
このような拡張機能を実装するプロセスでは、より複雑な順序のアイデアが発生しますが、通常のように時間の不足により、これらはすべてTrelloレコードのフレームワーク内に残り、永久に停止します。
しかし、ようやく実現に至り、地平線が見えなくなったとき、あなたは中毒になり、停止することはすでに非現実的です。 それでも、時間が無駄にならなかったことを願っています。フレームワークの機能がなければ、サンプルコードがあれば、私のソリューションが誰かの生活を楽にしてくれることを願っています。
私の決定の話に加えて、他のフレームワークを分析して比較しようとするので、それは退屈ではないと思います。
比較
機能性
必要条件 | フレックスレイアウト | ASDK (テクスチャ) | レイアウトキット | 自動レイアウト | Cglayout |
---|---|---|---|---|---|
性能 | + | + | + | - | + |
キャッシュ可能性 | + | + | + | +- | + |
マルチスレッド | - | + | + | - | + |
階層間レイアウト | - | - | - | + | + |
CALayerおよび「レンダリングなし」ビューのサポート | - | + | - | - | + |
拡張性 | - | + | + | - | + |
テスタビリティ | + | + | + | + | + |
宣言的 | + | + | + | + | + |
いくつかの指標は主観的かもしれません、なぜなら 実稼働環境ではこれらのフレームワークを使用しませんでした。 間違えた場合は、修正してください。
性能
テストには、 LayoutFrameworkBenchmarkが使用されました。
AsyncDisplayKitは、ベンチマーク開発者によって含まれていなかったため、チャートに追加されませんでした。また、ASDKは、バックグラウンドでレイアウトを実行しますが、パフォーマンス測定では完全に公平ではありません。 または、Pinterestアプリを見ることができます。 そこのパフォーマンスは本当に印象的です。
分析
すでに多くのフレームワークに関する多くの情報があります、私は私の意見を共有するだけです。 主にネガティブな側面についてお話します。なぜなら、それらはすぐに明らかになり、プロはフレームワークのページで説明されているからです。
レイアウトキット
Github 25の問題
非常に柔軟ではありませんが、大量のコードを作成し、実装にプログラマを十分に含める必要があります。 LinkedIn以外のアプリケーションでLayoutKitを使用することに関する情報は見つかりませんでした。
それでも、LayoutKitが目標を達成しなかったという意見があり、LinkedInアプリケーションのフィードは依然として遅くなります。
機能:
- 非標準のマークアップごとに、このレイアウトの実装を使用してサブクラスを作成する必要があります。
- システムの中核は、ビューを作成して再利用することです。 すべてのビューがキャッシュされるため、外部ビューリンクを使用すると参照整合性が損なわれる可能性があります。 はい、原則として、ビューはパブリックインターフェイスからは利用できません。
- レイアウトブロック(LabelLayoutなど)は、多くのビュー情報を複製します。 それらを初期化するとき、デフォルト値(テキストなど)を設定する必要があります
- 要素間の相対的な依存関係を作成する柔軟性はありません。
FlexLayout(別名YogaKit)
レイアウトのみを処理する機能を提供します。 他のグッズもチップもありません。
機能:
- レイアウトをキャッシュする方法はありません。 サイズのみ取得できます。
- ネイティブ実装ではありません。 APIは、クロスプラットフォーム開発者、Web開発者により適しています。
- 画面のサイズ変更に関する問題-レイアウトオブジェクトのサイズを手動で変更し、再カウントを行う必要があります。
- レイアウトブロックを整理するときに、追加のビューを作成する必要があります。
AsyncDisplayKit(テクスチャ)
Facebookは、より高いレベルのスレッドセーフな抽象化を作成しました。 UIKitツールスタック全体の実装が必要になった理由。 重いライブラリです。使用することにした場合、後で拒否することはできません。 それでも、これは依然として最も有能で開発されたオープンソースソリューションです。
機能:
- 組み込みのヨガを除く他のレイアウトフレームワークとの互換性はありません。
- ASDisplayNodeサブクラスを使用する必要があります。 UIKitでの作業を本質的に除外します。 ASDKを使用することに決めたので、他のツールを忘れ、UIの代わりに書くことに慣れることができます...、AS ....
- 多数のUIKitメカニズムの独自の実装。これにより、コードの陳腐化とAppleの実装との大きな不一致、Appleリリースに関連しないバグが生じる可能性があります。
- 親愛なるサポート。
- 問題の解決に関連する情報はほとんどありません。 StackOverflowで文字列「AsyncDisplayKit」を使用して検索すると、181個の一致が見つかります(比較のため、「UIKit」は66,550です)。
Cglayout
- UIViewレベルとCALayerレベルの両方で使用します。 レイヤーの位置によってビューを組み合わせたり、制限したりできます。逆も同様です。
not rendered
オブジェクトを使用する機能。 - 異なるオブジェクトのレイアウト仕様を再利用します。
- 階層間の依存関係を作成する機能。
- 厳密なタイピング。
- スナップショットを取得してマークアップをキャッシュします。
- 環境に依存しないカスタム制限の作成。 たとえば、ラベルの行サイズ制限。
- ビューのレイアウトガイドとプレースホルダーの簡単な作成のサポート。
- 使用可能なレイアウト(直接、背景、キャッシュ)をサポートします。
- UIKitとの簡単な統合。
- 簡単に拡張可能。
現在の制限:
- プログラマは、レイアウトスキームを定義して制約を適用する際に一貫性について考える必要があります。
-
layer
プロパティを使用してUIViewのレイアウトを計算すると、UIViewのframe
が暗黙的に変更され、副作用(drawRect、layoutSubviewsなど)が呼び出されないため、未定義の動作が発生します。 同時に、UIViewのレイヤーを別のレイヤーの制限として簡単に使用できます。 - スーパービューの現在の
bounds
値とは異なるフレームのスナップショットを受信し、スーパービューに基づいた制限がある場合、予期しない結果が生じる可能性があります。 - 確率制約のある複雑なレイアウトにはまだあまり適応していません。
- UIViewとCALayerの間に制限があるマークアップの計算は、実装で座標を変換する必要があるため遅くなります。
まだ実装されていないもの:
- RTLサポート。
- 階層からビューを削除するときの動作。
- macOS、tvOSをサポートします。
- 特性コレクションのサポート。
- 再利用可能なビューをマークするための便利なデザインはありません。
- 現在のレイアウト構成を動的に変更します。
CGLayoutの実装
CGLayout
、Swift言語の現代の原則に基づいて構築されています。
CGLayoutでのマークアップ管理の実装は、 RectBasedConstraint
、 RectBasedLayout
、 RectBasedConstraint
3つの基本プロトコルに基づいていLayoutItem
。
LayoutItem
Iを実装するすべてのエンティティはレイアウト要素を呼び出し、他のすべてのエンティティは単なるレイアウトエンティティです。
基本的なマークアップ
public protocol RectBasedLayout { func layout(rect: inout CGRect, in source: CGRect) }
RectBasedLayout
レイアウトを変更するための動作を宣言し、このための1つのメソッドを定義します。利用可能なスペースを基準にした方向付けが可能です。
RectBasedLayout
プロトコルを実装するLayout
構造は、レイアウト要素の完全かつ十分なレイアウトを決定します。 位置決めと寸法。
したがって、 Layout
は2つの要素に分割されます:配置Layout.Alignment
と塗りつぶしLayout.Filling
です。 これらは、水平および垂直レイアウトで構成されています。 すべての構成要素はRectBasedLayout
実装しRectBasedLayout
。 これにより、さまざまなレベルの複雑さのレイアウト要素を使用してマークアップを実装できます。 すべてのエンティティレイアウトは、実装によって簡単に拡張できます。
制限事項
すべての制限はRectBasedConstraint
によって実装されRectBasedConstraint
。 RectBasedLayout
エンティティが利用可能なスペースでマークアップを定義する場合、 RectBasedConstraint
エンティティRectBasedConstraint
この利用可能なスペースをRectBasedConstraint
します。
public protocol RectBasedConstraint { func constrain(sourceRect: inout CGRect, by rect: CGRect) }
LayoutAnchor
は、環境から抽象化された動作を持つ特定の区切り文字(サイド、サイズなど)が含まれます。
現時点では、主なリミッターが実装されています。
レイアウトの制約
public protocol LayoutConstraintProtocol: RectBasedConstraint { var isIndependent: Bool { get } func layoutItem(is object: AnyObject) -> Bool func constrainRect(for currentSpace: CGRect, in coordinateSpace: LayoutItem) -> CGRect }
レイアウト要素またはコンテンツ(テキスト、画像など)への依存を決定します。 これらは、制約のソースと使用される制約に関するすべての情報を含む自己完結型の制約です。
LayoutConstraint
特定の区切り文字セットを持つレイアウト要素に関連付けられた制約。
AdjustLayoutConstraint
レイアウト要素に関連付けられた制約には、サイズベースの制約が含まれます。 AdjustableLayoutItem
プロトコルをサポートするレイアウト要素で使用できます。
レイアウト要素
public protocol LayoutItem: class, LayoutCoordinateSpace { var frame: CGRect { get set } var bounds: CGRect { get set } weak var superItem: LayoutItem? { get } }
UIView、CALayerなどのクラスによって実装され、 not rendered
れてnot rendered
クラスも実装さnot rendered
ます。 スタックビューなどの他のクラスを実装することもできます。
フレームワークにはLayoutGuideの実装があります。 これはUIKitのUILayoutGuideに似ていますが、要素をファクトリ化する機能があります。 これにより、LayoutGuideをプレースホルダーとして使用できます。これは、最新の設計ソリューションに照らして非常に重要です。 特に、ViewPlaceholderクラスはこれらの目的のために作成されています。 UIViewControllerと同じビュー読み込みパターンを実装します。 したがって、彼と一緒に仕事をすることは非常に馴染みのあるものです。
サイズを計算できる要素の場合、プロトコルが宣言されます:
public protocol AdjustableLayoutItem: LayoutItem { func sizeThatFits(_ size: CGSize) -> CGSize }
デフォルトでは、UIViewのみが実装します。
レイアウト座標空間
public protocol LayoutCoordinateSpace { func convert(point: CGPoint, to item: LayoutItem) -> CGPoint func convert(point: CGPoint, from item: LayoutItem) -> CGPoint func convert(rect: CGRect, to item: LayoutItem) -> CGRect func convert(rect: CGRect, from item: LayoutItem) -> CGRect var bounds: CGRect { get } var frame: CGRect { get } }
レイアウトシステムには、 LayoutCoordinateSpace
プロトコルの形式で表示される統合座標システムがあります。
各タイプの基本実装(UIView、CALayer、UICoordinateSpace +相互変換のための独自の実装)を使用しながら、すべてのレイアウト要素に対して単一のインターフェイスを作成します。
レイアウトブロック
public protocol LayoutBlockProtocol { var currentSnapshot: LayoutSnapshotProtocol { get } func layout() func snapshot(for sourceRect: CGRect) -> LayoutSnapshotProtocol func apply(snapshot: LayoutSnapshotProtocol) }
レイアウトブロックは、レイアウトの完全かつ独立したユニットです。 マークアップを実行し、スナップショットを受信/適用するためのメソッドを定義します。
LayoutBlock
は、レイアウト要素、そのメインレイアウト、およびLayoutConstraintProtocolを実装する制約をカプセル化します。
マークアップを更新するプロセスは、制限を使用して使用可能なスペースを決定することから始まります。 システムはまだ競合制限の問題を解決せず、どのような方法でも優先順位を付けないため、制限の適用に慎重に取り組む必要があることに留意してください。 したがって、一般的に、サイズベースの制約(AdjustLayoutConstraint)は、位置ベースの制約の後に配置する必要があります。 スーパービューのスペース(境界)がソーススペースとして使用されます。 各制限により、使用可能なスペースが変更されます(トリム、シフト、ストレッチなど)。 制限が機能すると、結果のスペースがLayout
に転送され、そこで要素の実際のマークアップが計算されます。
LayoutScheme
は、他のレイアウトブロックを組み合わせて、マーキングの正しいシーケンスを決定するブロックです。
レイアウトのスナップショット
public protocol LayoutSnapshotProtocol { var snapshotFrame: CGRect { get } var childSnapshots: [LayoutSnapshotProtocol] { get } }
LayoutSnapshot
一連のフレームとして表示されるスナップショットで、レイアウト要素の階層を保持します。
延長
すべての拡張可能な要素は、拡張プロトコルを実装します。
public protocol Extended { associatedtype Conformed static func build(_ base: Conformed) -> Self }
したがって、機能を拡張するときに、 CGLayout
既に定義されている型を使用して、厳密に型指定されたインターフェイスを構築できます。
使用例
CGLayoutは、一連の更新フレームを構築するという意味で、手動レイアウトとほとんど変わりません。 CGLayoutでマークアップを実装する場合、プログラマーは、作業を開始する前にすべての制限が実際のフレームに適用されなければならないことを覚えておく必要があります。
let leftLimit = LayoutAnchor.Left.limit(on: .outer) let topLimit = LayoutAnchor.Top.limit(on: .inner) let heightEqual = LayoutAnchor.Size.height() ... let layoutScheme = LayoutScheme(blocks: [ distanceLabel.layoutBlock(with: Layout(x: .center(), y: .bottom(50), width: .fixed(70), height: .fixed(30))), separator1Layer.layoutBlock(with: Layout(alignment: separator1Align, filling: separatorSize), constraints: [distanceLabel.layoutConstraint(for: [leftLimit, topLimit, heightEqual])]) ... ]) ... override public func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() layoutScheme.layout() }
この例では、このラベルの位置が決定された後、セパレーターはdistanceLabel
制約を使用します。
まとめ
自動レイアウトは、安定性、優れたAPI、強力なサポートにより、依然としてレイアウトのメインツールです。 しかし、サードパーティのソリューションは、焦点が狭いか柔軟性があるため、特定の問題の解決に役立ちます。
CGLayout
は、レイアウトプロセスを記述するための通常のロジックがまったくないため、ある程度慣れる必要があります。
まだ多くの作業がありますが、これは時間の問題であり、このようなシステムの分野でニッチを占めることができる多くの利点があることはすでに明らかです。 フレームワークの動作はまだ実稼働環境でテストされていないため、試してみる機会があります。 フレームワークはテストでカバーされているため、大きな問題はありません。
フレームワークのさらなる開発に積極的に参加していただければ幸いです。
そして最後に、Habr-usersに質問したいと思います。
レイアウトシステムの要件は何ですか?
レイアウトの構築で最も嫌いなことは何ですか?