2017-01-15 5 views
9

너비가 높이보다 큰지에 따라 레이아웃을 변경하고 싶습니다. 하지만 레이아웃 경고가 계속 나타납니다. 나는 WWDC 비디오를 많이 보았지만 문제를 해결하지 못하는 적응 형 레이아웃을 보았습니다. 다음 코드를 사용하여 간단한 레이아웃의 레이아웃을 만들었습니다. 이것은 사람들이 내 문제를 재현 할 수 있도록하는 것입니다. 코드는 iPhone 7 Plus에서 실행됩니다.iOS Autolayout 적응 형 UI 문제

import UIKit 

class ViewController: UIViewController { 

    var view1: UIView! 
    var view2: UIView! 

    var constraints = [NSLayoutConstraint]() 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     view1 = UIView() 
     view1.translatesAutoresizingMaskIntoConstraints = false 
     view1.backgroundColor = UIColor.red 

     view2 = UIView() 
     view2.translatesAutoresizingMaskIntoConstraints = false 
     view2.backgroundColor = UIColor.green 

     view.addSubview(view1) 
     view.addSubview(view2) 

     layoutPortrait() 
    } 

    func layoutPortrait() { 
     let views = [ 
      "a1": view1, 
      "a2": view2 
     ] 

     constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[a1]|", options: [], metrics: nil, views: views) 
     constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[a2]|", options: [], metrics: nil, views: views) 
     constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1(450)][a2]|", options: [], metrics: nil, views: views) 
     NSLayoutConstraint.activate(constraints) 
    } 

    func layoutLandscape() { 
     let views = [ 
      "a1": view1, 
      "a2": view2 
     ] 

     constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[a1(a2)][a2(a1)]|", options: [], metrics: nil, views: views) 
     constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1]|", options: [], metrics: nil, views: views) 
     constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a2]|", options: [], metrics: nil, views: views) 
    NSLayoutConstraint.activate(constraints) 
    } 

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 
     super.viewWillTransition(to: size, with: coordinator) 

     NSLayoutConstraint.deactivate(constraints) 

     constraints.removeAll() 

     if (size.width > size.height) { 
      layoutLandscape() 
     } else { 
      layoutPortrait() 
     } 

    } 

} 

몇 번 회전하면 xcode에서 경고를 기록합니다. 나는 레이아웃 스위치가 일찍 바뀌는 것 같아요. 여전히 변화하고 있기 때문에 이미 높이가 세로로 설정되어 있습니다. 누군가 내가 뭘 잘못했는지 알아?

제약 경고 : 제약 조건이 너무 늦게이동되고 있기 때문에 지금까지 내가 말할 수있는

[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:0x618000081900 V:|-(0)-[UIView:0x7ff68ee0ac40] (active, names: '|':UIView:0x7ff68ec03f50)>", 
    "<NSLayoutConstraint:0x618000081d60 UIView:0x7ff68ee0ac40.height == 450 (active)>", 
    "<NSLayoutConstraint:0x618000083cf0 V:[UIView:0x7ff68ee0ac40]-(0)-[UIView:0x7ff68ee0ade0] (active)>", 
    "<NSLayoutConstraint:0x618000083d40 V:[UIView:0x7ff68ee0ade0]-(0)-| (active, names: '|':UIView:0x7ff68ec03f50)>", 
    "<NSLayoutConstraint:0x600000085d70 'UIView-Encapsulated-Layout-Height' UIView:0x7ff68ec03f50.height == 414 (active)>" 
) 

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x618000081d60 UIView:0x7ff68ee0ac40.height == 450 (active)> 
+0

이 왜 앵커 레이아웃을 시도하지 않습니다. -> [Apple Doc] (https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html) –

+0

ios 8을 지원해야합니다. – Haagenti

+0

예, iOS 8을 지원하고 있습니다. 나는'스위프트 3.x'에서 해냈다. –

답변

2

제한 조건 작업을 너무 일찍하고 있습니다. <NSLayoutConstraint:0x600000085d70 'UIView-Encapsulated-Layout-Height' UIView:0x7ff68ec03f50.height == 414 ... 은 가로로보기 컨트롤러보기의 높이에 대한 시스템 생성 제약 조건입니다. 가로 세로 414, 정확히 iPhone 5.5 (6/7 Plus) 높이입니다. 회전이 시작되지 않았으므로 450 높이 제한 조건의 수직 구속 조건이 충돌하고 경고 메시지가 나타납니다.

그래서 회전이 완료된 후 제한 조건을 추가해야합니다. viewWillLayoutSubviews은 논리적 인 장소입니다. 뷰 크기가 준비되었지만 화면에 아무 것도 나타나기 전에 호출되기 때문에 시스템에서 레이아웃 패스를 저장할 수 있습니다.

그러나 경고를 나타내지 않으려면 viewWillTransition(to size: with coordinator:)의 제약 조건을 제거해야합니다. 시스템은 viewWillLayoutSubviews()이 호출되기 전에 새로운 자동 레이아웃 계산을 수행하고 세로에서 가로 방향으로 갈 때 경고를 받게됩니다.

EDIT : 주석에서 언급 한대로 다른 것들은 레이아웃을 트리거 할 수 있기 때문에 제약 조건을 추가하기 전에 항상 제거해야합니다. [constraints] 배열을 비우면 효과가 없습니다. 비활성화 후 배열을 비우는 것은 중요한 단계이므로 자체 메서드로 수행해야합니다 (예 : clearConstraints()).

또한 초기 레이아웃에서 방향을 확인하십시오. viewDidLoad()에서 layoutPortrait()을 호출하면 문제가되지 않는다고 생각하십시오.

신규/변경 방법 :

override func viewWillLayoutSubviews() { 
    super.viewWillLayoutSubviews() 

    // Should be inside layoutPortrait/Landscape methods, here to keep code sample short. 
    clearConstraints() 

    if (self.view.frame.size.width > self.view.frame.size.height) { 
     layoutLandscape() 
    } else { 
     layoutPortrait() 
    } 
} 

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 
    super.viewWillTransition(to: size, with: coordinator) 

    clearConstraints() 
} 

/// Ensure array is emptied when constraints are deactivated. Can be called repeatedly. 
func clearConstraints() { 
    NSLayoutConstraint.deactivate(constraints) 
    constraints.removeAll() 
} 
+2

나는 viewWillLayoutSubviews에서 제약 조건을 제거해야한다고 생각한다. – Sulthan

+0

@ 설화 그게 좋은 지적이야. 'viewWillTransition (크기 : 코디네이터와 함께)'는 많은 것을 얻지 만, 다른 것들은 레이아웃 패스를 트리거하고 제약 조건을 제거 할 수 있습니다. 즉, 새로운 제약 조건이 추가되기 전에 실제로 항상 호출되어야합니다. 내 대답을 업데이트했습니다. –

0

를 (가로에서 세로 돌아갈 때 발생), 경고는 사실이다. 이 경고는 가로로 회전 할 때 나타납니다.보기의 높이가 너무 작아서 450 구속 조건이 설정되어 만족할 수 없습니다.

내가 몇 가지 어떤 더 큰 뷰의 높이가 여전히 450

보다는

constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1(450)][a2]|", options: [], metrics: nil, views: views) 
되도록, 대신 작은 뷰에서 제약 조건을 설정하고 가장 적합한 것 같았다을 시도

나는

사용 viewWillTransitionToSize에서 다음
let height = view.frame.height - 450 
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1][a2(\(height))]|", options: [], metrics: nil, views: views) 

과 :

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 
    super.viewWillTransition(to: size, with: coordinator) 
    NSLayoutConstraint.deactivate(self.constraints) 
    self.constraints.removeAll() 

    coordinator.animate(alongsideTransition: { (context) in 

     if (size.width > size.height) { 
      self.layoutLandscape() 
     } else { 
      self.layoutPortrait() 
     } 
    }, completion: nil) 

} 

편집 :

(잘 모르겠어요 비록 그것이 권장, 그리고 아마도 더 일반적인 대답) 나를 위해 일한 다른 것은 : super.viewWillTransition를 호출하기 전에 제약 조건을 비활성화 한 후 전환과 함께 나머지 애니메이션 . 내 추론은 회전이 시작되기 전에 제약 조건이 즉각적으로 제거되고 앱이 가로로 회전하기 시작하면 (이전 높이 제약 조건이 만족스럽지 않은 시점에서) 제약 조건이 이미 비활성화되고 새로운 제약 조건이 적용될 수 있습니다 효과.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 
     NSLayoutConstraint.deactivate(constraints) 
     super.viewWillTransition(to: size, with: coordinator) 
     constraints.removeAll() 

     coordinator.animate(alongsideTransition: { (context) in 
      if (size.width > size.height) { 
       self.layoutLandscape() 
      } else { 
       self.layoutPortrait() 
      } 

     }, completion: nil)    
    } 
+0

나는 당신의 요점을 얻는다. 그러나 이것이 그것을 완전히 풀지는 못합니다. 내 예제가 단순 해지고 답을 얻지 못하면 세로와 가로로 다른 레이아웃을 회전하고 사용할 수있는 방법이 해결되지 않습니다. – Haagenti

+0

제 답변을 편집하여 저에게 효과적이었던 다른 (아마도 좀 더 일반적인) 방법 중 하나를 추가했습니다. 그게 당신이 찾고있는 것이 더 있습니까? – Samantha

0

나는 @Samantha와 거의 같은 대답을 내놓았다. 그러나 super으로 전화하기 전에 deactivate에 전화 할 필요가 없었습니다. 이는 특성 모음을 사용하는 대체 방법이지만 transitionToSize 접근 방식도 비슷하게 작동합니다.

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { 
    super.willTransition(to: newCollection, with: coordinator) 
    NSLayoutConstraint.deactivate(constraints) 

    constraints.removeAll() 

    coordinator.animate(alongsideTransition: { context in 
     if newCollection.verticalSizeClass == .compact { 
      self.layoutLandscape() 
     } else { 
      self.layoutPortrait() 
     } 
    }, completion: nil) 
} 
0

이것은 문제 부분이다

  1. a1 상단까지의 거리가 0이다

    constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1(450)][a2]|", options: [], metrics: nil, views: views) 
    

    이 라인 (4 개) 제한 조건을 생성한다.

  2. 높이가 a1 인 경우는 450입니다.
  3. 거리는 0입니다.
  4. a2 하단 거리가 0입니다.

화면의 전체 높이가 450보다 작기 때문에 네 가지 제약 조건이 함께 가로 모드로 나옵니다.

지금은 가로 모드로 변경하기 전에 제약 조건을 업데이트하고 세로에서 가로로 전환 할 때 작동합니다. 그러나 가로에서 세로로 전환 할 때 너무 일찍 구속 조건을 변경하고 잠시 동안 가로 방향에서 세로 방향으로 구속 조건을 설정하여 경고를 표시합니다.

기본적으로 제약 조건에는 아무런 문제가 없습니다. 경고를 수정하려는 경우 제 생각에는 제약 조건 중 하나의 우선 순위를 낮추는 것이 가장 좋습니다. 높이 제한 :

constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1([email protected])][a2]|", options: [], metrics: nil, views: views) 
+0

이 문제를 해결할 수 있습니다. 나는 Mike Sand의 대답이 이것을하는 더 깨끗한 방법이라고 생각한다. 이후 나는 많은 @ 999 제약을 추가해야합니다. 하지만 덕분에 많이 배웠다 – Haagenti