Search code examples
iosxcodexibuistackview

How to set alignments right for dynamic UIStackView with inner XIB


My original ViewController consists of only one scrollView like this:

enter image description here

Now I also have my own xib file (CheckBoxView) which mainly consists of one button, see this screenshot:

enter image description here

I dynamically create some UIStackViews and add them to the ScrollView Inside these UIStackViews I add multiple instances of my xib file.

What I want to achieve is, that the StackViews are just vertically stacked. And inside the StackViews the UIViews from my xib-file should also be vertically stacked.

At the moment it looks like this: enter image description here

So the xib-Views are not in the whole view. Since I am using multi-os-engine I can't provide swift/obj-c code. But here is my Java-Code:

for (ItemConfiguration config : itemInstance.getConfigurations()) {

            List<DLRadioButton> radioButtons = new ArrayList<DLRadioButton>();

            UIStackView configView = UIStackView.alloc().initWithFrame(new CGRect(new CGPoint(0, barHeight), new CGSize(displayWidth, displayHeight - barHeight)));
            configView.setAxis(UILayoutConstraintAxis.Vertical);
            configView.setDistribution(UIStackViewDistribution.EqualSpacing);
            configView.setAlignment(UIStackViewAlignment.Center);
            configView.setSpacing(30);


            for (ConfigurationOption option : config.getOptions()) {
                UIView checkBox = instantiateFromNib("CheckBoxView");
                for (UIView v : checkBox.subviews()) {
                    if (v instanceof DLRadioButton) {
                        ((DLRadioButton) v).setTitleForState(option.getName(), UIControlState.Normal);
                        //((DLRadioButton) v).setIconSquare(true);
                        radioButtons.add((DLRadioButton) v);
                    }
                }
                configView.addArrangedSubview(checkBox);
            }
            // group radiobuttons
            //groupRadioButtons(radioButtons);

            configView.setTranslatesAutoresizingMaskIntoConstraints(false);
            scrollView().addSubview(configView);

            configView.centerXAnchor().constraintEqualToAnchor(scrollView().centerXAnchor()).setActive(true);
            configView.centerYAnchor().constraintEqualToAnchor(scrollView().centerYAnchor()).setActive(true);
        }


private UIView instantiateFromNib(String name) {
    return (UIView) UINib.nibWithNibNameBundle(name, null).instantiateWithOwnerOptions(null, null).firstObject();
}

How do I need to set the Alignments etc. to Achieve what I want. It should look like this:

enter image description here


Solution

  • I don't know if there is a reason to not use UITableView, that i highly recommend for your case. In case it's not possible, below you can find some pieces of advice that should help.


    If you use Auto Layout, you should set constraints for all views instantiated in your code. The constraints must be comprehensive for iOS to know each view's position and size.

    Remove redundant constraints

    configView.centerXAnchor().constraintEqualToAnchor(scrollView().centerXAnchor()).setActive(true);
    configView.centerYAnchor().constraintEqualToAnchor(scrollView().centerYAnchor()).setActive(true);
    

    These two constraint just doesn't make sense to me. You need the stackviews be stacked within you ScrollView, but not centered. If i understand you goal correctly, this should be removed

    Set width/x-position constraints for UIStackViews

    configView.setTranslatesAutoresizingMaskIntoConstraints(false);
    scrollView().addSubview(configView);
    

    Right after a stack view is added to the ScrollView, you need to set up constraints for it. I'll provide my code in swift, but it looks quite similar to what your Java code is doing, so hopefully you'll be able to transpile it without difficulties:

    NSLayoutConstraint.activate([
      configView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor),
      configView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor)
    ]);
    

    Set height constraints for UIStackViews

    StackViews doesn't change their size whenever you add arranged view in it. So you need to calculate a desired stackview size yourself and specify it explicitly via constraints. It should be enough to accommodate items and spaces between them. I suppose that all items should be of the same size, let it be 32 points, then height should be:

    let stackViewHeight = items.count * 32 + stackView.space * (items.count + 1)
    

    And make new height constraint for the stack view:

    configView.heightAnchor.constraint(equalToConstant: stackViewHeight).isActive = true
    

    Set y-position for UIStackView

    This is a little bit more challenging part, but the most important for the views to work properly in a scroll view.

    1) Change loop to know the index of a UIStackView

    A scroll view should always be aware of height of its content, so you need to understand which stack view is the top one, and which is the bottom. In order to do that, you need to change for each loop to be written as for(;;) loop:

    for (int i = 0; i < itemInstance.getConfigurations().length; i++) {
      ItemConfiguration config = itemInstance.getConfigurations()[i]
      ...
    }
    

    I'm not aware of which type your array is, so if it doesn't have subscript functionality, just replace it with corresponding method.

    2) Set top anchor for stack views

    For the first stack view in the array, top anchor should be equal to the scroll view top anchor, for others it should be bottom anchor of the previous stack view + spacing between them (say, 8 points in this example):

    if i == 0 {
      configView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor, constant: 8).isActive = true
    } else {
      let previousConfigView = itemInstance.getConfigurations()[i - 1]
      configView.topAnchor.constraintEqualToAnchor(previousConfigView.bottomAnchor, constant: 8).isActive = true
    }
    

    3) Set bottom anchor for the last stack view

    As was said - for the Scroll View to be aware of content size, we need to specify corresponding constraints:

    if i == itemInstance.getConfigurations() - 1 {
      configView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor, constant: 8).isActive = true
    }
    

    Note: please be advised, that all constraints should be set on views that are already added to the scroll view.