【uGUI自動レイアウト】ContentSizeFitterとLayoutGroupを正しく設定して、「動的に大きさが変化するTextコンポーネント」を実現する



# TL;DR
子にContentSizeFitterを設定したときは、親のLayoutGroupのControlChildSizeをオフにすること。

# 動機
「Textコンポーネントを使って動的に大きさが変化するUI」（行の増えていくログ機能など）を作りたかった。
しかし、自身のUIサイズを調整するContentSizeFitterを使うとUnityEditorで警告がでて無効になったり、ContentSizeFitterを使わないと求める機能が実現できなかったりした。

公式ドキュメント（[自動レイアウト - Unity マニュアル](https://docs.unity3d.com/ja/current/Manual/UIAutoLayout.html)）を読んでもよくわからず...

こちらを見たところやっと理解できたので、やりたかったことに必要な原理の理解と設定方法をまとめた。
[uGUIの自動レイアウトを根っこのほうから理解する｜fuqunaga｜note](https://note.com/fuqunaga/n/ndef5b45863d4)






# 原理：uGUIのレイアウト処理

> [uGUIの自動レイアウトを根っこのほうから理解する｜fuqunaga｜note](https://note.com/fuqunaga/n/ndef5b45863d4)　　より抜粋・要約
> 
> ## uGUIの２種類のレイアウト処理方法
> |                  | **処理の流れ** | **処理**                                                |
> | ---------------- | -------------- | ------------------------------------------------------- |
> | 通常のレイアウト | 親 -> 子       | 親の矩形(RectTransform)をもとに子の矩形が一意に決まる。 |
> | 自動レイアウト   | 親 <- 子       | 子の情報をもとに自分自身や子のRectTransformを編集する。 |
> 
> 自動レイアウトの例）「[**Text**](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UIVisualComponents.html#text) コンポーネントなど内容によってサイズが変わるUIがはみ出さないように親のUIのサイズを広げる」など
> 
> ## 自動レイアウト
> 自動レイアウトは以下の２つの要素から成る。
> 
> ### LayoutElement：　自身のサイズについての「要望」
> 
> - 要望なので**これ単体では特になにも変化しない**
> - LayoutElementとして扱われるのは **ILayoutElement** **を継承するコンポーネント全て**
>   - つまり、 [**LayoutElement**](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UIAutoLayout.html#layout-element-component) コンポーネント だけでなく、[**Text**](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UIVisualComponents.html#text) や [**Image**](https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UIVisualComponents.html#image) もLayoutElementとして自身のサイズの要望を伝える。
> 
> ### LayoutController：　RectTransform の編集者
> 
> - 「要望」を元に（自身or子の）RectTransformを編集する。
> - 複数のレイアウトコントローラーの編集が衝突しないように Editor 上でのサポートがある。
>   - (つまり、**<font color="red">複数LayoutControllerで１つのRectTransformを編集してはいけない</font>**）
> - 主なLayoutControllerの挙動
>   - ContentSizeFitter：　自身のサイズをレイアウトエレメントの Min または Preferred にする。
>   - LayoutGroup：　Control Child Size が OFF のときは（子の）サイズの変更はなく位置だけ整列する。



# 「Textコンポーネントを使って動的に大きさが変化するUI」の設定例
## GameObjectのヒエラルキー

以下のヒエラルキーを想定して設定した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/97f2f997-c55c-3809-1fd3-91ebfff23e6d.png)

## 各GameObjectの設定値
- TextContainerのVerticalLayoutGroupは、ControlChildSizeをOFFにする。
  - つまり、子（text）のサイズを編集しない。
- Text11はTextとContentSizeFitterを設定。
  - つまり、Textコンポーネントのサイズ要望を、自身のContentSizeFitterで設定する。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186338/ad21077e-80b8-6b2c-9729-b9445d5de24d.png)

※VerticalLayoutGroupは、両方のインターフェースを実装してるので、両方の役割を持つ


# まとめ
子にContentSizeFitterを設定したときは、親のLayoutGroupのControlChildSizeをオフにすること。
複数のLayoutControllerが同時にRectTransformのSizeを編集しないようにするため。









# 備考：ILayoutElement/ILayoutControllerの実装

公式ドキュメント（[Unity - Scripting API: LayoutGroup](https://docs.unity3d.com/2018.3/Documentation/ScriptReference/UI.LayoutGroup.html) など）を見て、厳密にはILayoutElement/ILayoutControllerがどのように実装されているか、クラス図をおこして確認した。

**自動レイアウト周りの実装のクラス図**
```plantuml
@startuml

interface ILayoutController
interface ILayoutElement
interface ILayoutGroup
' interface ILayoutSelfController


ILayoutController <|.. LayoutGroup
ILayoutElement <|.. LayoutGroup
ILayoutGroup <|.. LayoutGroup

LayoutGroup <|-- HorizontalOrVerticalLayoutGroup
HorizontalOrVerticalLayoutGroup <|-- VerticalLayoutGroup
HorizontalOrVerticalLayoutGroup <|-- HorizontalLayoutGroup


' ILayoutSelfController <|.. ContentSizeFitter
ILayoutController <|.. ContentSizeFitter

ILayoutElement <|.. LayoutElement
ILayoutElement <|.. Text
ILayoutElement <|.. Image

Selectable <|-- Button

@enduml
```

VerticalLayoutGroupは、（LayoutGroup, HorizontalOrVerticalLayoutGroupを経由して）ILayoutElement/ILayoutControllerの両方を実装しているのが分かる。

ButtonはLayoutGroup・LayoutElementどちらでもない。

<details><summary>PUMLコード</summary><div>

```
@startuml

interface ILayoutController
interface ILayoutElement
interface ILayoutGroup
' interface ILayoutSelfController


ILayoutController <|.. LayoutGroup
ILayoutElement <|.. LayoutGroup
ILayoutGroup <|.. LayoutGroup

LayoutGroup <|-- HorizontalOrVerticalLayoutGroup
HorizontalOrVerticalLayoutGroup <|-- VerticalLayoutGroup
HorizontalOrVerticalLayoutGroup <|-- HorizontalLayoutGroup


' ILayoutSelfController <|.. ContentSizeFitter
ILayoutController <|.. ContentSizeFitter

ILayoutElement <|.. LayoutElement
ILayoutElement <|.. Text
ILayoutElement <|.. Image

Selectable <|-- Button

@enduml
```
</div></details>