Mamedoff Mamedoff - 3 months ago 38
Swift Question

Removing a UIView from UIStackView Changes its Size

I am trying to build a drag drop action and if I drag a view from stack view and drop it to somewhere and remove the view with "removeArrangedSubview" it changes the dragged items size and makes it bigger. I am using this piece of code to resize dropped item.

sender.view!.frame = CGRectMake(sender.view!.frame.origin.x, sender.view!.frame.origin.y, sender.view!.frame.width * 0.5, sender.view!.frame.height * 0.5)


Here is the image for the comparison.
enter image description here

Answer

You are setting the frame of the view you are moving but when you place it in a stack view the stack view will reset it based on the stack view's constraints and the intrinsic content size of the view that you are adding to it. The reason it is not doing that when you don't call removeArrangedSubview is because it has not triggered an auto layout pass. In general, when you're using auto layout you shouldn't set views frames but rather update constraints intrinsicContentSize and request auto layout updates.

You should play around with the distribution property of your stack view, the constraints you have placed on it, as well as the intrinsicContentSize of the views you are adding to it until you get your desired result.

For example:

class ViewController: UIViewController
{
    let stackView = UIStackView()
    let otherStackView = UIStackView()

    override func viewDidLoad()
    {
        self.view.backgroundColor = UIColor.whiteColor()

        stackView.alignment = .Center
        stackView.axis = .Horizontal
        stackView.spacing = 10.0
        stackView.distribution = .FillEqually
        stackView.translatesAutoresizingMaskIntoConstraints = false

        otherStackView.alignment = .Center
        otherStackView.axis = .Horizontal
        otherStackView.spacing = 10.0
        otherStackView.distribution = .FillEqually
        otherStackView.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(stackView)
        self.view.addSubview(otherStackView)

        stackView.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor).active = true
        stackView.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor).active = true
        stackView.trailingAnchor.constraintEqualToAnchor(self.view.trailingAnchor).active = true
        stackView.heightAnchor.constraintEqualToConstant(150).active = true

        otherStackView.topAnchor.constraintEqualToAnchor(self.view.topAnchor).active = true
        otherStackView.widthAnchor.constraintGreaterThanOrEqualToConstant(50).active = true
        otherStackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
        otherStackView.heightAnchor.constraintEqualToConstant(150).active = true

        self.addSubviews()
    }

    func addSubviews()
    {
        for _ in 0 ..< 5
        {
            let view = View(frame: CGRect(x: 0.0, y: 0.0, width: 100, height: 100))
            view.userInteractionEnabled = true
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panHandler(_:)))
            view.addGestureRecognizer(panGesture)
            view.translatesAutoresizingMaskIntoConstraints = false
            view.backgroundColor = UIColor.redColor()
            self.stackView.addArrangedSubview(view)
        }
    }

    func panHandler(sender: UIPanGestureRecognizer)
    {
        guard let view = sender.view else{ return }
        switch sender.state {
        case .Began:
            self.stackView.removeArrangedSubview(view)
            self.view.addSubview(view)
            let location = sender.locationInView(self.view)
            view.center = location
        case .Changed:
            view.center.x += sender.translationInView(self.view).x
            view.center.y += sender.translationInView(self.view).y
        case .Ended:
            if CGRectContainsPoint(self.otherStackView.frame, view.center)
            {
                self.otherStackView.addArrangedSubview(view)
                self.otherStackView.layoutIfNeeded()
            }
            else
            {
                self.stackView.addArrangedSubview(view)
                self.otherStackView.layoutIfNeeded()
            }
        default:
            self.stackView.addArrangedSubview(view)
            self.otherStackView.layoutIfNeeded()
        }
        sender.setTranslation(CGPointZero, inView: self.view)
    }
}

class View: UIView
{
    override func intrinsicContentSize() -> CGSize
    {
        return CGSize(width: 100, height: 100)
    }
}

The above view controller and UIView subclass does something similar to what you're looking for, but it's not possible to tell from your question. Notice that pinning a stack view to the edges of its super view (like stackView) causes it to stretch subviews to fill all the available space while allowing the stack view to dynamically size based on the intrinsic size of its subviews (like otherStackView) does not.

Update

If you don't want to add the view to another stack view but you want them to retain their frame, you should remove any constraints the view's have on them and then set their translatesAutoresizingMaskIntoConstraints property to true. For example:

func panHandler(sender: UIPanGestureRecognizer)
{
    guard let view = sender.view else{ return }
    switch sender.state {
    case .Began:
        self.stackView.removeArrangedSubview(view)
        self.view.addSubview(view)
        let location = sender.locationInView(self.view)
        view.center = location
    case .Changed:
        view.center.x += sender.translationInView(self.view).x
        view.center.y += sender.translationInView(self.view).y
    default:
        view.removeConstraints(view.constraints)
        view.translatesAutoresizingMaskIntoConstraints = true
        // You can now set the view's frame or position however you want
        view.center.x += sender.translationInView(self.view).x
        view.center.y += sender.translationInView(self.view).y

    }
    sender.setTranslation(CGPointZero, inView: self.view)
}
Comments