_btn.frame = CGRectMake(20, 20, 280, 280);
NSLog(@"%@", NSStringFromCGRect(_btn.frame)); // 輸出仍為 (20, 20, 30, 30) - Storyboard 設定值
在 viewDidLoad 中修改 frame 失效,運行時仍回到 Storyboard 設計值。
frame 成為**只讀計算屬性** (由引擎根據約束解出)。viewDidLoad 階段,Auto Layout 引擎尚未完成最終佈局計算 (通常在 viewDidLayoutSubviews 後)。你手動設的 frame 會被後續的 layoutSubviews 根據約束**強制覆蓋回原值**。**不要設 frame,改設 Constraint (約束)。**
// 1. Storyboard 中:選中按鈕 -> 右側面板 -> 取消勾選 "Constrain to margins" (可選)
// 添加 4 條約束:Leading 20, Top 20, Width 280, Height 280
// 2. 代碼中動態修改:建立 @IBOutlet 連結約束 (NSLayoutConstraint)
@IBOutlet weak var btnWidthConstraint: NSLayoutConstraint!
@IBOutlet weak var btnHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var btnTopConstraint: NSLayoutConstraint!
@IBOutlet weak var btnLeadingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
btnWidthConstraint.constant = 280
btnHeightConstraint.constant = 280
btnTopConstraint.constant = 20
btnLeadingConstraint.constant = 20
// view.layoutIfNeeded() // 如需立即動畫生效
}
優點: 完美適配所有螢幕尺寸 (iPhone SE ~ Pro Max、iPad、Split View)、支援旋轉、支援動畫、國際化 (RTL) 自動翻轉、維護性最高。
Main.storyboard (或 .xib)。後果: 專案退回到 iOS 6 時代的 **Autoresizing Mask (Springs & Struts)** 模式。frame 修改即時生效。但**失去所有自適應能力**,新機型 (刘海屏、iPad 多任務) 需手寫大量判斷邏輯,**不推薦用於正式產品**。
若堅持用 frame 且不動約束,必須在 **Auto Layout 解析完成後** 修改,並**手動禁用該視圖的 Auto Layout 參與**:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// 關鍵:告訴引擎「這個視圖我不想讓你管了,我自己管 frame」
_btn.translatesAutoresizingMaskIntoConstraints = true
_btn.frame = CGRect(x: 20, y: 20, width: 280, height: 280)
}
缺點: 破壞約束體系,易導致佈局衝突警告 (紫色邊框)、旋轉錯亂、內部子視圖佈局失效。**僅限極簡單臨時測試**。
Xcode 6 起,Auto Layout 是預設且強制的主流。
**frame 失效 = 你在跟 Auto Layout 引擎搶佈局權,且你輸了 (在 viewDidLoad 階段)。**
**正解:建立 @IBOutlet 連結約束,修改 constraint.constant。**
這是從「座標編程」進化到「約束編程」的必經之路,越早適應越受益。