Not able to lay out Subviews on a custom UIView in

2019-08-17 18:13发布

问题:

So I have created this custom container view which I am laying out using autolayout constraint.

    func configureSegmentContainerView() {
    segmentContainerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
    segmentContainerView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true
    segmentContainerView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8).isActive = true
    segmentContainerView.heightAnchor.constraint(equalToConstant: 3).isActive = true 
    }

In the view controller, the viewDidLoad() is this:

    setupDataSource()
    segmentContainerView = ATCStorySegmentsView()
    view.addSubview(segmentContainerView)
    configureSegmentContainerView()
    segmentContainerView.translatesAutoresizingMaskIntoConstraints = false
    segmentContainerView.numberOfSegments = friendStory.count

Now once the data source is setup and I have the friendStory count, I am assigning it to segmentContainerView.numberofSegments

In segmentContainerview class this is what is happening:

    var numberOfSegments: Int? {
    didSet {
        addSegments()
    } 
  }

In addSegments(), I am adding UIViews depending upon the numberOfSegments this is the code for that:

 private func addSegments() {
    guard let numberOfSegments = numberOfSegments else { return }
    layoutIfNeeded()
    setNeedsLayout()
    for i in 0..<numberOfSegments {
        let segment = Segment()
        addSubview(segment.bottomSegment)
        addSubview(segment.topSegment)
        configureSegmentFrame(index: i, segmentView: segment)
        segmentsArray.append(segment)
    }
}

private func configureSegmentFrame(index: Int, segmentView: Segment) {
    guard let numberOfSegments = numberOfSegments else { return }
    let widthOfSegment : CGFloat = (self.frame.width - (padding * CGFloat(numberOfSegments - 1))) / CGFloat(numberOfSegments)

    let i = CGFloat(index)

    let segmentFrame = CGRect(x: i * (widthOfSegment + padding), y: 0, width: widthOfSegment, height: self.frame.height)
    segmentView.bottomSegment.frame = segmentFrame
    segmentView.topSegment.frame = segmentFrame
    segmentView.topSegment.frame.size.width = 0
}

**Question and Issue: ** Instead of getting 4 UIViews, I am getting 3, but the third one is not correctly placed and is going outside the parent container. How can I get these uiviews aligned correctly. I am guessing there is some issue with where setNeedsLayout() and layoutIfNeeded() needs to be called. Please help.

Segment is a struct with two properties - bottomSegment and topSegment. Both being UIView

You can see how just three UIView segments appear. I needs to 4 (numberOfSegments = 4) of these. Also I am giving the parent container constant of 8 and -8 for right and leftAnchor. so all 4 segments need to be placed within this view. As you can see in the picture above the last segment is going outside of the parent container.

回答1:

Try to call addSegments at onViewDidAppear. If it works, it means that in your code the view does not still have the correct frame.

For this to work you need to adapt the frames of the views, when the view controller's view changed:

override func viewDidLayoutSubviews() {
     super.viewDidLayoutSubviews()
     guard let numberOfSegments = numberOfSegments else { return }
     for i in 0..<numberOfSegments {
        configureSegmentFrame(index: i, segmentView: segment)
    }
}

You will probably have to do it cleaner, but it should work.



回答2:

The problem is that

let widthOfSegment : CGFloat = (self.frame.width ...

is being called too soon. You cannot do anything in that depends upon self having its frame until after it has its real frame.

For a UIView, that moment is after layoutSubviews has been called for the first time.