24

我在試驗如何使用UIScrollView。經過很多麻煩,我終於得到了它的竅門。但現在我似乎又遇到了另一個障礙。以編程方式設置約束

在這個簡單的應用程序,我有一個滾動視圖,爲了它的工作,我必須設置視圖的底部空間滾動視圖約束爲0描述here,它工作正常。我正在通過IB來完成。

現在我遇到了一個場景,我必須以編程方式執行該部分。我在viewDidLoad方法中添加了下面的代碼。

NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:self.view 
                      attribute:NSLayoutAttributeBottom 
                      relatedBy:NSLayoutRelationEqual 
                       toItem:self.scrollView 
                      attribute:NSLayoutAttributeBottom 
                      multiplier:1.0 
                       constant:0.0]; 
[self.view addConstraint:bottomSpaceConstraint]; 

但它似乎沒有工作。它會在控制檯窗口中輸出以下消息,我不知道該如何處理它。

無法同時滿足約束條件。 以下列表中的至少一個約束可能是您不想要的。試試這個:(1)看看每個約束,並試圖找出你不期望的; (2)找到添加不需要的約束或約束並修復它的代碼。 (注意:如果你看到,你不明白NSAutoresizingMaskLayoutConstraints,請參閱文檔UIView的財產translatesAutoresizingMaskIntoConstraints) ( 「」, 「」 )

有人能告訴我該怎麼辦這個?我還附加了一個演示項目here,以便您可以更好地瞭解這個問題。

UPDATE:

首先感謝您的答覆。使用答案中提到的方法,我能夠得到它的工作。然而,略微不同的情況下,它不是。現在我試圖以編程方式將視圖加載到viewcontroller。

如果我可以進一步解釋。有2個視圖控制器。第一個是UITableViewController,第二個是UIViewController。裏面有一個UIScrollView。還有多個UIView s,其中一些視圖的高度超過了屏幕的正常高度。

UITableViewController顯示選項列表。根據用戶的選擇,批次中的特定UIView將與UIScrollView一起加載到UIViewController中。

在這種情況下,上述方法不起作用。滾動沒有發生。因爲我分別加載視圖,所以我必須做些不同的事情嗎?

我已經上傳了一個演示項目here,以便您可以看到它的實際應用。請參閱Email.xib並從表格視圖列表中選擇電子郵件

回答

56

基於你的代碼,進行審查幾點意見:

  1. 你一般不需要視圖顯示時調整限制。如果你發現自己這樣做,通常意味着你沒有正確配置你的故事板(或者至少不是有效的)。你真正需要設置/創建約束的唯一時間是(a)你以編程方式添加視圖(我建議它的工作量比其值得多);或者(b)您需要對約束進行一些運行時間調整(請參閱下面第3點下的第三個項目符號)。

  2. 我並不是說要這樣做,但是你的項目有一堆冗餘代碼。例如。

    • 你被設置滾動視圖的幀,但通過限制的約束,所以什麼也不做(即當施加的限制,任何手動設置frame設置將被替代)。一般來說,在自動佈局中,不要直接嘗試更改frame:編輯約束。但是,無論如何不需要改變約束條件,所以這一點是沒有意義的。

    • 您正在設置滾動視圖的內容大小,但是在自動佈局中,這也受到(子視圖的)約束的約束,因此這是不必要的。

    • 您正在爲scrollview設置約束(已經爲零),但是您並未將NIB視圖添加到滾動視圖中,從而擊敗了其中的任何意圖。最初的問題是如何更改滾動視圖的底部約束。但是底部約束已經爲零,所以我沒有理由再次將其設置爲零。

  3. 我建議你的項目更激進的簡化:

    • 你被存儲在發鈔銀行的意見使生活在自己更難。如果你留在故事板世界裏,它會容易得多。如果你真的需要,我們可以幫助你做NIB的東西,但爲什麼讓自己的生活如此艱難?

    • 使用單元格原型來促進表格中單元格的設計。您也可以定義細胞從細胞到下一個場景。這消除了編寫任何didSelectRowAtIndexPathprepareForSegue代碼的任何需要。顯然,如果你有需要傳遞給下一個場景的東西,儘量使用prepareForSegue,但是目前爲止你沒有提出任何要求,所以我在我的例子中對它進行了評論。

    • 假設您正在尋找一個以編程方式更改約束的實際示例,我已經設置了場景以便文本視圖將基於文本視圖中的文本以編程方式更改其高度。像往常一樣,不是迭代通過約束來找到問題,而是在更改IB爲我創建的現有約束時,我認爲爲約束設置IBOutlet以及編輯約束的constant屬性效率更高直接,這就是我所做的。所以我設置視圖控制器是文本視圖的代表,並寫道,更新文本視圖的高度約束textViewDidChange

      #pragma mark - UITextViewDelegate 
      
      - (void)textViewDidChange:(UITextView *)textView 
      { 
          self.textViewHeightConstraint.constant = textView.contentSize.height; 
          [self.scrollView layoutIfNeeded]; 
      } 
      

      注意,我的文本視圖有兩個高度限制,強制性的最小高度約束,以及根據文本數量改變的中等優先級約束。主要的一點是,它說明了一個以編程方式改變約束的實例。你根本不應該混淆scrollview的底部約束,但是這是一個真實世界的例子,你可能想調整一個約束。


當您添加在IB一個滾動視圖,它會自動得到你所需要的所有約束。您可能不希望以編程方式添加約束(至少在不移除現有底部約束的情況下)。

兩種方法可能比較簡單:

  1. 爲您現有的底部約束創建IBOutlet,說scrollViewBottomConstraint。然後,你可以做

    self.scrollViewBottomConstraint.constant = 0.0; 
    
  2. 或者最初創建在IB的圖,其中底部限制是0.0,那麼你就不必做任何編程的。如果要佈置長滾動視圖和子視圖,請選擇控制器,將其模擬度量標準從「推斷」設置爲「自由格式」。然後,您可以更改視圖的大小,將滾動視圖的頂部和底部約束設置爲零,在滾動視圖中佈置所需的所有內容,然後在運行時呈現視圖時,視圖將適當調整大小,並且由於您已經將scrollview的頂部和底部約束定義爲0.0,它將被正確調整大小。它在IB中看起來有點奇怪,但它在應用運行時就像一個魅力。

  3. 如果您決定添加一個新的約束條件,您可以通過編程方式刪除舊的底部約束條件,或者將舊的底部約束條件的優先級降低到儘可能低的位置,這樣您的新約束條件(具有更高的優先級)將優先,並且優先不適用舊的,低優先級的底部約束。

但是你絕對不想僅僅添加一個新的約束。

+0

喜羅布,感謝您的支持ponse。我更新了現有的約束,它工作正常。然而,現在我試圖從一個單獨的'UIView'加載視圖,然後它不工作。在這種情況下,我必須做些不同的事情嗎?我更新了我的問題。 – Isuru 2013-04-09 06:35:02

+1

@Isuru我已經完成了你的項目,並有很多評論,所以看到我修改後的答案。另外,如果您下載我的示例項目,這些項目是原始代碼庫的簡化版本,您將在源代碼中看到額外的評論。 – Rob 2013-04-09 17:56:16

+1

嗨,羅布,非常感謝你的這一切。這絕對是我獲得問題的最佳答案之一。我經歷了這兩個項目並學到了很多東西。我進入iOS開發4個月,所以我對一些東西很陌生,但都很好。我想使用xib文件的原因是我正在使用這個應用程序工作,老年人堅持使用一個'UIViewController'並擁有郵件類型用戶界面和_templates_ nib文件,因爲它更易於管理,因爲我們已經有了很多場景躺在身邊。 – Isuru 2013-04-10 06:08:56

12

看看控制檯信息,我覺得你添加兩個相同類型的約束時會產生歧義。

因此,不要創建和添加新約束,而是嘗試更新約束數組中已有的約束。

for(NSLayoutConstraint *constraint in self.view.constraints) 
    { 
     if(constraint.firstAttribute == NSLayoutAttributeBottom && constraint.secondAttribute == NSLayoutAttributeBottom && 
      constraint.firstItem == self.view && constraint.secondItem == self.scrollView) 
     { 
      constraint.constant = 0.0; 
     } 
    } 

希望這有助於

即使羅布的回答將工作!

+0

您好,Kunal,感謝您的回覆。我使用你的方法更新了現有的約束,它工作正常。然而,現在我試圖從一個單獨的'UIView'加載視圖,然後它不工作。在這種情況下,我必須做些不同的事情嗎?我更新了我的問題。 – Isuru 2013-04-09 06:35:30

+0

對不起,延遲迴復。在NewMailViewController下面的代碼導致問題如果([self.mailTypeName isEqualToString:@「Email」]){NSArray * nib = [[NSBundle mainBundle] loadNibNamed:@「Email」owner:self options:nil]; self.view = [nib objectAtIndex:0]; } 在這裏,您將self.view重新分配給email.xib視圖(並且email.xib沒有滾動視圖)。希望這可以幫助。 – Kunal 2013-04-09 09:28:46

+0

它沒問題。 :) 我懂了。但是我在_Email.xib_中放了一個'UIScrollView',但它仍然無法工作。我該怎麼辦? – Isuru 2013-04-09 10:05:56

21

可以在視圖控制器中創建插座來表示佈局約束。只需在界面構建器中選擇您想要的約束(例如,通過您正在安排的視圖的測量面板上的「選擇並編輯」)。然後進入插座窗格並將「New Referencing Outlet」拖放到您的代碼文件(.h或.m)中。這將約束條件綁定到一個NSLayoutConstraint實例,您可以從您的控制器訪問該實例,並實時動態調整(通常通過constant屬性,因爲它根本不是常量,所以屬性名稱很差)。

enter image description here

(請注意,在XCode中6,你可以雙擊約束選擇它來進行編輯。)

enter image description here

在Interface Builder調整佈局時,但要小心,因爲你可能最終刪除約束,必須重新將其綁定到出口。

+0

這是非常有用的,完美工作thx – Guy 2015-04-19 09:52:02

2

您可以使用https://github.com/SnapKit/Masonry以編程方式添加約束。

這是AutoLayout NSLayoutConstraints的強大功能,具有簡化,可鏈接和表達的語法。支持iOS和OSX自動佈局。

UIView *superview = self.view; 

UIView *view1 = [[UIView alloc] init]; 
view1.translatesAutoresizingMaskIntoConstraints = NO; 
view1.backgroundColor = [UIColor greenColor]; 
[superview addSubview:view1]; 

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10); 

[superview addConstraints:@[ 
//view1 constraints 
[NSLayoutConstraint constraintWithItem:view1 
          attribute:NSLayoutAttributeTop 
          relatedBy:NSLayoutRelationEqual 
           toItem:superview 
          attribute:NSLayoutAttributeTop 
          multiplier:1.0 
           constant:padding.top], 

[NSLayoutConstraint constraintWithItem:view1 
          attribute:NSLayoutAttributeLeft 
          relatedBy:NSLayoutRelationEqual 
           toItem:superview 
          attribute:NSLayoutAttributeLeft 
          multiplier:1.0 
           constant:padding.left], 

[NSLayoutConstraint constraintWithItem:view1 
          attribute:NSLayoutAttributeBottom 
          relatedBy:NSLayoutRelationEqual 
           toItem:superview 
          attribute:NSLayoutAttributeBottom 
          multiplier:1.0 
           constant:-padding.bottom], 

[NSLayoutConstraint constraintWithItem:view1 
          attribute:NSLayoutAttributeRight 
          relatedBy:NSLayoutRelationEqual 
           toItem:superview 
          attribute:NSLayoutAttributeRight 
          multiplier:1 
           constant:-padding.right],]]; 
在短短几行

繼承人的同樣的限制使用MASConstraintMaker

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10); 

[view1 mas_makeConstraints:^(MASConstraintMaker *make) { 
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler 
    make.left.equalTo(superview.mas_left).with.offset(padding.left); 
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom); 
    make.right.equalTo(superview.mas_right).with.offset(-padding.right); 
}]; 

甚至更​​短的

[view1 mas_makeConstraints:^(MASConstraintMaker *make) { 
    make.edges.equalTo(superview).with.insets(padding); 
}]; 

做你最好是那種創造;)

+0

'TinyConstraints'是我現在最喜歡的:https://github.com/roberthein/TinyConstraints – SushiHangover 2017-02-16 07:12:56