Development Tip

스크롤 뷰 내부의 동적 크기 컨트롤러로 컨테이너 뷰 크기 조정

yourdevel 2020. 11. 23. 20:19
반응형

스크롤 뷰 내부의 동적 크기 컨트롤러로 컨테이너 뷰 크기 조정


UIScrollView 내부에 동적 높이가있는 컨트롤러를 사용하여 컨테이너보기를 만들고 자동 레이아웃을 사용하여 크기를 자동으로 조정하려고합니다.

설정을 보여주는 스토리 보드

뷰 컨트롤러 A는 아래에 더 많은 내용과 함께 컨테이너 뷰가 포함 된 스크롤 뷰입니다.

View Controller B는 동적 크기를 갖고 모든 콘텐츠가 View Controller A의 Scroll View에 전체 높이로 표시되기를 원하는 뷰 컨트롤러입니다.

나는 A의 컨테이너보기에 높이 제약 조건을 설정하지만 경우 A에 자동으로 설정에 컨테이너보기의 크기를 B의 동적 크기를 받고 몇 가지 문제가 있어요 A의 컨테이너보기에서 예를 들어 250,

It would be the expected output if View Controller B would also have 250 height. It also works fine for height 1000, so as far as I know, all the auto layout constraints are properly setup. Unfortunately, since the height should actually be dynamic, I would like to avoid setting a height constraint at all.

I'm not sure if there are any settings for view controller B I can set for it to automatically update its size depending on its contents, or if there are any other tricks I've missed. Any help would be much appreciated!

Is there any way to size the Container View in A according to how big the size of View Controller B is without setting a height constraint?


Yup, there is. I managed to achieve that kind of behavior in one of my own projects.

All you gotta do is to tell the system that it should not add constraints that mimic the fixed frame set for your root view in Interface Builder. The best place to do this is in your container view controller when your embed segue is triggered:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // You might want to check if this is your embed segue here
    // in case there are other segues triggered from this view controller. 
    segue.destinationViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
}

Important:

You gotta make sure that the view that you load into the container is constrained from top to bottom and you need to set the priority of one of the vertical constraints to a value lower than 1000. (It's a good practice to always use the bottom constraint for this.) This is necessary because otherwise Interface Builder will complain — with a good reason:

At design time your root view has a fixed size (height). Now if all your subviews have a fixed height and are connected with fixed constraints that all have the same priority it's impossible to fulfil all these requirements unless the height of your fixed root view coincidentally matches exactly the total height of your subviews and the vertical constraints. If you lower the priority of one of the constraints to 999 Interface Builder knows which constraint to break. At runtime however — when the translatesAutoresizingMaskIntoConstraints property is set as stated above — there is no fixed frame for your root view anymore and the system will use your 999 priority constraint instead.

인터페이스 빌더 스크린 샷


From the answer of @Mischa I was able to make the height of a containerView dynamic depending on its content doing this:

In the viewController of the containerView write:

  override func loadView() {
    super.loadView()
    view.translatesAutoresizingMaskIntoConstraints = false
  }

And taking care that the vertical constraints in IB are all set. Doing this you do not need to set view.translatesAutoresizingMaskIntoConstraints = false from outside the view controller.

In my case I was trying to resize the container to a tableView inside the viewController. Because the tableView has a flexible height depending on its superview (so all OK for IB), I completed the vertical constraints in code by doing this:

  @IBOutlet private var tableView: UITableView! {
    didSet {
      tableView.addConstraint(tableViewHeight)
    }
  }
  private lazy var tableViewHeight: NSLayoutConstraint = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)

And then observe the contentSize height of the tableview and adjust the constant of the tableViewHeight constraint programmatically when needed.


Swift 4, Xcode 9

The accepted answer alone didn't solve the problem for me.

My hierarchy: ScrollView --> Content View (UIView) --> Views | Container View | Other Views.

I had to add the following constraints to make both the ScrollView and Container dynamically adjust:

  1. ScrollView: Top, Bottom, Leading, Trailing to Superview (Safe Areas)
  2. Content View: Top, Bottom, Leading, Trailing, Equal Width to ScrollView, but also with Equal Heights (constraint with a lower priority: 250).
  3. Views: normal auto-layout constraints.
  4. Container View: Top, Bottom to neighbor views, Leading and Trailing to Safe Area.
  5. Container View's embedded VC: Everything constraint connected vertically with the bottom constraint set to a lower priority, but bigger than the Content View's Equal Height one! In this case, a priority of 500 did the trick.
  6. Set view.translatesAutoresizingMaskIntoConstraints = false in either prepareForSegue() or in loadView() as other answers stated.

Now I have a dynamically adjustable Container View inside an auto-resizing Scroll View.


Building on @Mischa's great solution to this problem, if the container embeds a dynamically sized UITableView, then you need to look at overriding it's contentSize and intrinsicContentSize, as well as setting translatesAutoresizingMaskIntoConstraints = false as per the accepted answer.

When the parent view loads and sets up the container view, the intrinsic height of each of the UITableViewCells is inferred to be 0, therefore the UITableViewDelegate method of:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

will not be called. This makes the UITableView appear as though it has no content.

Overriding the intrinsic content size to return the actual content size means that the tableview is displayed with the size it's contents require.

Emilio Peláez 의 훌륭한 기사 가이 주제에 대해 더 자세히 설명합니다.

참고 URL : https://stackoverflow.com/questions/35014362/sizing-a-container-view-with-a-controller-of-dynamic-size-inside-a-scrollview

반응형