[Swift5]CompositonalLayoutのinset管理にlayoutMarginsを使うと楽になる

## TL;DR
* collectionViewにlayoutMarginを設定する。
* compositionalLayoutのsectionでcontentInsetsReferenceをlayoutMarginsにする。
* sectionのinsetをスッキリ統一。

## CompositonalLayoutのsection構築におけるinsetの管理問題。
CompositonalLayoutとはiOS13から登場したUICollectionViewのレイアウト構築用フレームワーク(？)です。

https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/layouts

以下のようにsection、group、itemによってレイアウトを構築できます。

```swift
func createLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { sectionNumber, _ -> NSCollectionLayoutSection in
        let section: NSCollectionLayoutSection
        let sectionType = SectionLayoutKind.allCases[sectionNumber]
        switch sectionType {
            case .name:
                let item = NSCollectionLayoutItem(
                    layoutSize: .init(
                        widthDimension: .fractionalWidth(1),
                        heightDimension: .estimated(44)
                    )
                )
                let group = NSCollectionLayoutGroup.horizontal(
                    layoutSize: .init(
                        widthDimension: .fractionalWidth(1),
                        heightDimension: item.layoutSize.heightDimension
                    ),
                    subitems: [item]
                )
                section = .init(group: group)
                section.contentInsets = .init(top: 8, leading: 16, bottom: 8, trailing: 16) // insetを調整

            case .desc:
                ...
        }
        return section
    }
    return layout
}
```


しかしcaseが増えてくるとsectionごとに以下のようなinsetを指定するのが面倒になってきます。

```swift
section.contentInsets = .init(top: 8, leading: 16, bottom: 8, trailing: 16)
```

sectionを一時変数にしてreturnする前に設定すれば上記のように何度もinsetを指定することは回避できます。

```swift
func createLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { sectionNumber, _ -> NSCollectionLayoutSection in
        let section: NSCollectionLayoutSection
        let sectionType = SectionLayoutKind.allCases[sectionNumber]
        switch sectionType {
            case .name:
                ...
                section = .init(group: group)

            case .desc:
                ...
                section = .init(group: group)
        }
        
        section.contentInsets = .init(top: 8, leading: 16, bottom: 8, trailing: 16)
        return section
    }
    return layout
}
```

しかし、上記の方法だとsectionごとにちょっと上下の幅を増やしたりができません。
一応個別にsectionにinsetを指定し、途中でreturnさせれば特定のsectionだけ別のinsetにできます。

```swift
func createLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { sectionNumber, _ -> NSCollectionLayoutSection in
        let section: NSCollectionLayoutSection
        let sectionType = SectionLayoutKind.allCases[sectionNumber]
        switch sectionType {
            case .name:
                ...
                section = .init(group: group)

            case .desc:
                ...
                section = .init(group: group)
                section.contentInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16)
                return section // 個別にreturn
        }
        
        section.contentInsets = .init(top: 8, leading: 16, bottom: 8, trailing: 16)
        return section
    }
    return layout
}
```

けど他のsectionのinsetと横幅がずれないようにしたりするために結局defaltのinsetの幅を確認しに行ったりするのが手間です。

理想はデフォルトのinsetはそのままにsectionごとに幅のプラスやマイナスを指定できたらいいのになと思いました。

## LayoutMargingsを使えばできた

NSCollectionLayoutSectionにはcontentInsetsReferenceというプロパティが生えています。

https://developer.apple.com/documentation/uikit/uicontentinsetsreference?changes=_5

これに.layoutMargingsを指定すると、CollectionViewのlayoutMarginをデフォルトのinsetとして使うようになります。

```swift
collectionView.layoutMargins = .init(top: 8, left: 16, bottom: 8, right: 16)

section.contentInsetsReference = .layoutMargins
```

~~するとsectionで指定したinsetはこのlayoutMarginからと足された値になります。~~

reference指定なし | referenceにlayoutMargingsを指定
:--: | :--:
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/f2251949-373c-932d-2c1e-ac9cb43370eb.png" width="300" /> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/a61b18cb-ebe2-162d-d919-fd2e4b7eceae.png" width="300" />

例えば、layoutMargingsが以下の場合

```swift
collectionView.layoutMargins = .init(top: 8, left: 16, bottom: 8, right: 16)
```

~~以下のnameというsectionのtopのinsetは~~
~~marginの8とsectionProvider内で指定した8の合計で16になります。~~

#### 2021/4/20: 訂正

すみません。。。後日動作確認していて気づいたのですが、contentInsetsReferenceにlayoutMargings指定することで設定できるデフォルトのinsetは横幅のみでした。
上下のinsetはlayoutMargingの値に関わらずデフォルトだと0になります。
そのため、上下のinset調整は個別に行う必要がありますが、
横幅のinsetにデフォルトを設けつつ、セクションごとに個別にreturnしなくてもよくなるというメリットは変わりません。

```swift
func createLayout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { sectionNumber, _ -> NSCollectionLayoutSection in
        let section: NSCollectionLayoutSection
        let sectionType = SectionLayoutKind.allCases[sectionNumber]
        switch sectionType {
            case .name:
                ...
                section = .init(group: group)
                section.contentInset.top = 8.0
        }
        
        section.contentInsetsReference = .layoutMargins　// (top: 8, left: 16, bottom: 8, right: 16)がデフォルトのinsetになる。
        return section　
    }
    return layout
}
```

これで「デフォルトのinsetは保ちつつ、sectionごとにinsetを調整する」ということが楽にできるようになりました。

## 結論
* contentInsetsReferenceに.layoutMarginsを使うとCollectionViewのlayoutMarginをデフォルトのinsetにできる
* sectionにcontentInsetsを指定すればデフォルトのinsetを増減させられるので個別にinsetを指定したsectionをreturnしなくてよくなる。