簡體   English   中英

自動布局:是什么創建了名為 UIView-Encapsulated-Layout-Width & Height 的約束?

[英]Auto-layout: What creates constraints named UIView-Encapsulated-Layout-Width & Height?

我的布局約束在 Interface Builder 中很好,但是由於框架的某些部分應用了我真正不想要的固定高度和寬度約束,因此在運行時發生了異常。 它們為什么在那里,以及如何關閉它們?

它們是記錄列表中顯示的最后兩個約束:

2014-04-26 09:02:58.687 BBCNews[32058:60b] 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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>",
    "<NSLayoutConstraint:0xbf47190 UIView:0xbf4a3c0.leading == BNMyNewsCell_landscape:0xbf48b10.leading>",
    "<NSLayoutConstraint:0xbf47160 UIView:0xbf4a3c0.trailing == BNMyNewsCell_landscape:0xbf48b10.trailing>",
    "<NSLayoutConstraint:0xbf47130 BNMyNewsCell_landscape:0xbf48b10.bottom == UIView:0xbf4a3c0.bottom>",
    "<NSLayoutConstraint:0xbf47100 UIView:0xbf4a3c0.top == BNMyNewsCell_landscape:0xbf48b10.top>",
    "<NSLayoutConstraint:0xd4c3c40 'UIView-Encapsulated-Layout-Width' H:[BNMyNewsCell_landscape:0xbf48b10(304)]>",
    "<NSLayoutConstraint:0xd4c38a0 'UIView-Encapsulated-Layout-Height' V:[BNMyNewsCell_landscape:0xbf48b10(290)]>"
}
Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>

基於大量的觀察,我相信(但不能確定)名為UIView-Encapsulated-Layout-WidthUIView-Encapsulated-Layout-HeightUICollectionView是由UICollectionView和朋友創建的,並且存在以強制執行返回的大小sizeForItemAtIndexPath委托方法。 我想它是為了確保由UICollectionViewCell設置的cellForItemAtIndexPath最終達到它被告知的大小。

這在這里回答了我最初的問題。 第二個問題是為什么約束不可滿足? 單元格的固有高度應該與UIView-Encapsulated-Layout-Height 同樣,我不確定,但我懷疑這是一個舍入錯誤(即固有高度達到 200.1 像素, UIView-Encapsulated-Layout-Height可能四舍五入為 200。我想出的解決方法是降低相關單元格約束的優先級,以允許UIView-Encapsulated-Layout-Height擁有最后一個字。

這可能無法回答您的問題,但它可以幫助像我這樣從搜索到這里的其他人。

我收到了一個奇怪的 AutoLayout 破壞約束錯誤,並伴隨着UIView-Encapsulated-Layout-Width約束,因為我將tableHeaderView添加到尚未使用 AutoLayout 調整大小的表視圖中。 因此,系統試圖在具有{0,0,0,0}框架的 tableview 中應用我的標題子視圖的約束。 由於 UITableView 喜歡控制其元素的寬度,因此它生成的寬度約束UIView-Encapsulated-Layout-Width被設置為零,導致與我期望 320+pt 寬度的標題元素的各種混淆。

要點:確保在 AutoLayout 調整 tableview 大小后添加/操作您的補充/頁眉/頁腳視圖。

我面臨着同樣奇怪的約束,不知道為什么,直到我想起了該死的translatesAutoresizingMaskIntoConstraints屬性。 將此設置為false解決了問題。 在后台發生的事情是自動調整大小蒙版(iOS 的舊布局引擎)被轉換為約束。 很多時候你不想要這些約束,而是想要你自己的約束。 在這種情況下,您應該將此屬性設置為 false ,您會沒事的:

view.translatesAutoresizingMaskIntoConstraints = false

肯定在UITableViewtableHeaderView上看到了這一點。 通過在設置tableHeaderView后顯式設置等於tableView的寬度,然后在布局傳遞完成后重置它,我能夠讓它與自定義標題視圖一起使用。

iOS 9 的示例代碼,假設您有一個UITableView作為tableView傳遞到您的方法中,以及一個將其配置為item

//Create the header view
self.contentDetailHeaderView = MyCustomHeaderView()

//Turn on autolayout
self.contentDetailHeaderView.translatesAutoresizingMaskIntoConstraints = false

//Add the header to the table view
tableView.tableHeaderView = self.contentDetailHeaderView

//Pin the width  
let widthConstraint = NSLayoutConstraint(item: self.contentDetailHeaderView,
    attribute: .Width,
    relatedBy: .Equal,
    toItem: tableView,
    attribute: .Width,
    multiplier: 1,
    constant: 0)

tableView.addConstraint(widthConstraint)

//Do whatever configuration you need to - this is just a convenience method I wrote on my header view.
self.contentDetailHeaderView.setupForItem(item)

//Lay out the configured view
self.contentDetailHeaderView.layoutIfNeeded()

//Reset the table header view, because ¯\_(ツ)_/¯
tableView.tableHeaderView = self.contentDetailHeaderView

幾個注意事項,主要是當我再次查找時,因為我有一條金魚的記憶:

  • 不必從調用此viewDidLayoutSubviews -我能夠使用這種技術,只要tableView有安裝過程中適當的寬度。
  • 您確實需要確保您的標題視圖設置為自動調整大小。 我通過創建一個.xib做到這一點,然后確保所有項目都被固定,這樣當視圖改變寬度時,高度就會更新。
  • 如果您嘗試為viewForHeaderInSection執行此viewForHeaderInSection ,您可能最好在屏幕外抓取一些可以按照此技術進行布局的內容。 我對自行調整大小的位沒有多少運氣。

我們已經開始在 iOS 11 中看到大量布局沖突,其中包括對這些約束的引用,實際上它們是通過translatesAutoresizingMaskIntoConstraints標志添加的。 似乎在 iOS 11 中,當將視圖添加到層​​次結構而不是僅在布局視圖時(因為它似乎在以前的 iOS 版本中工作),會發生更多的 AutoLayout 魔法。

這是我們遇到的情況:

  • 創建一個視圖,其內部布局有助於定義視圖大小(例如,視圖具有內部約束,包括顯式填充等)
  • *** 將此視圖添加到層​​次結構中。
  • 稍后,在布局傳遞之前,將 translatesAutoresizingMaskIntoConstraints 設置為 false。

第二步(***)會導致沖突,因為系統會在將視圖添加到層​​次結構時向視圖添加零大小約束。 我們稍后設置了translatesAutoresizingMaskIntoConstraints作為使用 PureLayout 框架的結果,該框架會在您約束視圖時自動正確設置此標志......也就是說,在 iOS 11 中,您需要記住在構建時關閉translatesAutoresizingMaskIntoConstraints ,然后再添加視圖到層次結構。

我懷疑 Apple 認為將此標志默認為 YES 會比痛苦更有幫助。 不幸的是,情況並非如此。

我在各種情況下都遇到了這個錯誤(不一定與此處正確答案所建議的 UICollectionView 和朋友有關)。

所以我處理它的方法只是清除所有約束然后再次構建它們(只有這一次我不擔心我的約束與這些預先創建的約束沖突):

所以在代碼中:

UIView *parentView = [viewInQuestion superview];
[parentView clearConstraintsOfSubview:viewInQuestion];

其中clearConstraintsOfSubview是 UIView 上的類別方法:

- (void)clearConstraintsOfSubview:(UIView *)subview
{
    for (NSLayoutConstraint *constraint in [self constraints]) {
        if ([[constraint firstItem] isEqual:subview] || [[constraint secondItem] isEqual:subview]) {
            [self removeConstraint:constraint];
        }
    }
}

我遇到了類似的問題,並通過以下方法解決了它。

  • 環境: Swift 5.0,xcode 10.2.1,以編程方式設置視圖

  • 警告消息:無法同時滿足約束... 'UIView-Encapsulated-Layout-Width' UIView:0x0000000000.width == 0 (active)>" )

  • 帶警告的代碼

    override func loadView() { view = UIView() /// Adds the subviews to the view and sets their properties and constraints setupViews() }
  • 清除警告的代碼

    override func loadView() { /// Needed to set the frame of the root view to the window frame. let window = UIWindow() view = UIView(frame: window.frame) /// Adds the subviews to the view and sets their properties and constraints setupViews() }
  • 關於 loadView() 方法的說明: “如果您使用 Interface Builder 創建視圖並初始化視圖控制器,則不得覆蓋此方法。您可以覆蓋此方法以手動創建視圖。如果您選擇這樣做,將視圖層次結構的根視圖分配給視圖屬性。您創建的視圖應該是唯一的實例,不應與任何其他視圖控制器對象共享。此方法的自定義實現不應調用 super。” - 蘋果文檔

  • 關於根視圖的注意事項:

    “如果您更喜歡以編程方式創建視圖……您可以通過覆蓋視圖控制器的 loadView 方法來實現。此方法的實現應執行以下操作:

    創建一個根視圖對象。 根視圖包含與您的視圖控制器關聯的所有其他視圖。 您通常定義此視圖的框架以匹配應用程序窗口的大小,應用程序窗口本身應填滿屏幕。 但是,框架會根據您的視圖控制器的顯示方式進行調整。 請參閱“視圖控制器視圖調整大小”。

    您可以使用通用 UIView 對象、您定義的自定義視圖或任何其他可以縮放以填充屏幕的視圖。

    創建其他子視圖並將它們添加到根視圖中。” - 舊的蘋果文檔?

敲了一會兒我的頭后,我找到了這個鏈接 就我而言,當我使用 UIVieController 中的 insertRows 或 deleteRows 時,它發生在 UITableViewHeaderFooterView 上。 'estimatedSectionHeaderHeight' 和 'estimatedRowHeight' 在哪里設置,我的約束重做 3 次......顯示的錯誤是:

"<NSLayoutConstraint:0x280648140 H:|-(8)-[UIImageView:0x106e94860]   (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648230 H:[UIImageView:0x106e94860]-(8)-[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...']   (active)>",
"<NSLayoutConstraint:0x280648410 H:[UIButton:0x106ea5a40]-(8)-|   (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648460 H:[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...']-(8)-[UIButton:0x106ea5a40]   (active)>",
"<NSLayoutConstraint:0x2806493b0 'UIView-Encapsulated-Layout-Width' DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50.width == 0   (active)>"

如鏈接所述:

"當你用某些動畫類型做 insertRows 或 deleteRows 時,UIKit 會動畫行高從 0 到全高或后退。在那個動畫的 0 端,如果整個垂直軸設置為優先級,布局方程是不可能解決的= 1000。但是只將一個約束降低到 999——比如說底部空間到超級視圖邊距——一切都很好;內容只會在單元格邊界之外下拉。 ”。

解決方案是將 UIImageView 的領先優先級設置為 999(或更低到 1000)。

就我而言,我無意中設置了兩次編程約束。 一旦我刪除了重復的調用,沖突就消失了。

我在使用 AL create tableviewHeader 時遇到了這個問題

我像下面這樣初始化 tableview

let table = UITableView.init(frame: .zero, style: .grouped)
// set table constraint ...

然后我用 AutoLayout 創建 tableviewHeader。

"<NSLayoutConstraint:0x600003d7d130 'UIView-Encapsulated-Layout-Width' UIView:0x7fe92bc55260.width == 0 (active)>"

出現符號斷點

在我提到@Yerk 之后,答案是。 我在初始化 tableView 時更改了框架

let rect = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 0)
let table = UITableView.init(frame:rect , style: .grouped)

問題好像解決了

最后我找到了 CollectionView 的解決方案! 如果您像我一樣使用 storyBoard,它將對您有所幫助!

界面生成器/故事板

轉到storyBoard -> 選擇你的CollectionView ScreenShot CollectionView轉到 Size Inspector 然后將Estimate Size設置為None ScreenShot Estimate Size

就這樣!

我在 iPad Pro 上測試 Split View 時發現了類似的問題,DesignatedNerd 的回答有效,但我不需要那么多代碼。 這是我使用的:

[self.tableView.tableHeaderView setTranslatesAutoresizingMaskIntoConstraints:NO];

NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
                                                                   attribute:NSLayoutAttributeWidth
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:self.tableView
                                                                   attribute:NSLayoutAttributeWidth
                                                                  multiplier:1
                                                                    constant:0];
NSLayoutConstraint *yConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
                                                               attribute:NSLayoutAttributeTop
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.tableView
                                                               attribute:NSLayoutAttributeTop
                                                              multiplier:1
                                                                constant:0];


[self.tableView addConstraints:@[widthConstraint, yConstraint]];

注意添加了 Y 約束,它將 tableHeaderView 的頂部綁定到 tableView 的頂部。

在向表視圖標題添加約束時,我遇到了同樣的問題。 當標頭的邊界為 (0,0,0,0) 時,添加具有設置常量的約束時似乎會發生這種情況。 當標題的邊界不是 (0,0,0,0) 時,我僅通過在布局子視圖方法中添加約束來解決此問題

    if self.bounds == CGRect.zero {
        return
    }

約束UIView-Encapsulated-Layout-Height是使用您在tableView.estimatedSectionHeaderHeight設置的值創建的

UIView-Encapsulated-Layout-*是 table、collection 和 stack 使用的約束來布局元素,因為你告訴他們這樣做。 如果您稍后設置導致大小更改的約束,則會發生沖突。 此用例的一個示例是下載大小可變的圖像的單元格。

發生這種情況是因為我們習慣於 layoutIfNeeded() 來刷新布局,但是一旦集合經過渲染周期,單元格大小將設置為封裝約束。 您需要手動使您需要刷新的索引路徑無效,例如

let context = UICollectionViewLayoutInvalidationContext()
context.invalidateItems(at: [cellIndexPath])
collectionView.collectionViewLayout.invalidateLayout(with: context)

這將遞歸地創建更多失效以移動其他單元格的位置並騰出空間。

如果你設置,例如一個不同的高度約束來改變單元格的大小,它會立即崩潰,然后在你失效並且單元格再次布局后恢復。 為了避免這種崩潰可以降低高度約束的優先級低於.required ,例如UILayoutPriority(999)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM