8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UITableViewCell での UIView-Encapsulated-Layout-Height 制約との競合エラーに対処する

Last updated at Posted at 2022-04-05

検証環境

Xcode 13.3 / iOS 15.4 / Swift version 5.6

要点

問題

  • UITableViewCellの中身を、縦のAutoLayout制約を全て1000(UILayoutPriority.required)で組むと、システムが作成するUIView-Encapsulated-Layout-Height制約との競合が発生する。

解決法

→ 縦のAutoLayoutどこかの制約を1000未満(999以下)のプライオリティに変更する。

理由

実行時にシステムはCellのContentViewの高さをセパレータ分を含めた高さを確定し、変更していると思われ、縦の制約のすべてがRequiredの場合、その制約と競合してしまう。

概要

'UIView-Encapsulated-Layout-Height'とは?

UITableViewCellをAutoLayoutでレイアウトした後、実行すると以下のようなエラーが出ることがある。

2022-04-05 23:26:25.295287+0900 CellLayoutSolution[27039:2060062] [LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x281208910 UIView:0x104c07ee0.height == 100   (active)>",
    "<NSLayoutConstraint:0x281208960 V:|-(50)-[UIView:0x104c07ee0]   (active, names: '|':UITableViewCellContentView:0x104c1daf0 )>",
    "<NSLayoutConstraint:0x2812089b0 V:[UIView:0x104c07ee0]-(50)-|   (active, names: '|':UITableViewCellContentView:0x104c1daf0 )>",
    "<NSLayoutConstraint:0x281208e60 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x104c1daf0.height == 200.333   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x281208910 UIView:0x104c07ee0.height == 100   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

特に "<NSLayoutConstraint:0x281208e60 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x104c1daf0.height == 200.333 (active)>"

の部分について、身に覚えのない制約が追加されていることがわかる。

これについて検索すると、Appleのドキュメントなどは見つからず、以下のサイトに言及があった。

StackOverflow - What is NSLayoutConstraint "UIView-Encapsulated-Layout-Height" and how should I go about forcing it to recalculate cleanly?

どうやら、"UIView-Encapsulated-Layout-Height"はシステムが作成するCell(ContentView)の高さ制約のようである。

サンプルプロジェクトで作成した、Cell上の赤いビューは100ptの高さであり、上下のマージンは50ptずつ制約を設定しているため、ContentViewの高さは200ptの高さになるはずである。しかしながら、エラーのログからシステム側で作成した制約がUITableViewCellContentView:0x104c1daf0.height == 200.333とContentViewに200.333ptの高さを設定しようとしていることがわかる。

ビューデバッガで見てみる。スクショはiPhone8で実行したので値は200.5ptと表示されている。

この制約のプライオリティは1000に設定されている。となると、縦のAutoLayout制約を全て1000(UILayoutPriority.required)で組んでいると矛盾が発生する。同じ200ptの制約ならば、複数の制約をかけようとも矛盾が発生しないのでエラーとならないが200.333ptと微妙に違う数値となっているため。矛盾を解消するために、システムが任意で選択した赤いビューの100pt高さ制約を解除して回復を試みる、旨のメッセージが表示されている。

Will attempt to recover by breaking constraint <NSLayoutConstraint:0x281208910 UIView:0x104c07ee0.height == 100 (active)>

この0.333pt分について、ふとセパレータの高さなのではないか? と推測した。
試しに、UITableViewseparatorStylenoneに変更したところ、エラーが発生しなくなった。ビンゴっぽい。

ビューデバッガで確認する。なるほどちゃんと"UIView-Encapsulated-Layout-Height"が200ptになっている。

  • →システムはUIView-Encapsulated-Layout-Height制約をかけることで、CellのContentViewの高さを、セパレータ分を追加した高さに変更していると思われる。

システムがSelf-Sizingなセルの高さを割り出すためには、縦方向の制約が設定されている必要があるのだけれども、UIView-Encapsulated-Layout-Height制約を使ってセパレータを含めた高さに変更する際に、全てRequiredの制約だと衝突するということである。

'UIView-Encapsulated-Layout-Height'との競合を回避するには?

  • (回避法1) セパレータを表示するCellでは、どのようにするかというと、縦のAutoLayoutどこかの制約を1000未満(999以下)のプライオリティに変更する。一箇所でも1000以下の縦の制約があると、エラーは発生しない。

システムは縦方向の制約からCellの高さを計算し、次にセパレータ分を含めてCellの高さを確定させるUIView-Encapsulated-Layout-Height制約(Required)を発行し適用する。高さ計算が終わった後999以下に落とした制約は最終的に無視されるので競合しない、というシナリオになると思う。

  • (回避法1 - 1) 中身のビューに設定する高さ制約を750(defaultHigh)等に変更する
  • (回避法1 - 2) 中身のビューやコンテナとなるビューに設定するマージン制約を750(defaultHigh)等に変更する

  • (回避法1 - 3) intrinsicContentSizeの制約のあるビューを用いることでも回避できる。UILabelや、カスタムクラスでintrinsicContentSizeをoverrideすることで高さを与えた場合なども制約は1000未満になるため有効。

  • (回避法2) 現時点のiOSバージョンでは、UITableViewseparatorStylenoneに変更しセパレータを表示しない場合は、付加分の高さがないため、制約がrequired(1000)であってもUIView-Encapsulated-Layout-Height制約との競合が発生しないと思われる。

サンプルプロジェクト

これらを確認するために作ったサンプルプロジェクトは以下です。

Xcode 13.3 / iOS 15.4 / Swift version 5.6 で作成

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?