0

What I'm trying to do in my project is to transfer images to the screen according to the location data coming from the firebase( for example x = 5 , y = 10, width = 30, height = 50). For example, let's say I have 10 images to display (maybe even 100 images). The first of these images should be at the top. Each incoming image must be below the previous one but image can be different in x and y axis. I tried to assign different constraint with a for loop to the image that I have defined only one. But my problem is I get the following constraint error. Can you help me how can I do this? The image that should be is as follows. The image here. My simulator shows only one view like this image.

Error:

[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:0x600000cd4dc0 UIView:0x12b304a70.height == 50   (active)>",
    "<NSLayoutConstraint:0x600000cd51d0 V:[UIView:0x12b304a70]-(60)-[UIView:0x12b304a70]   (active)>"
)

My code:

let View: UIView = {
    let view = UIView()
    view.backgroundColor = .red
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}()

var count = 1
var topAnchor: NSLayoutYAxisAnchor?
for views in 1...11{
    if count == 1{
        view.addSubview(View)
        View.anchor(top: view.safeAreaLayoutGuide.topAnchor, bottom: nil, leading: nil, trailing: nil, paddingTop: 10, paddingBottom: 0, paddingLeft: 0, paddingRight: 0, width: 50, height: 50)
        View.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        topAnchor = View.bottomAnchor
        count += 1
    }
    else{ // if count
        view.addSubview(View)
        View.anchor(top: topAnchor, bottom: nil, leading: nil, trailing: nil, paddingTop: 60, paddingBottom: 0, paddingLeft: 0, paddingRight: 0, width: 30, height: 30)
        View.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        topAnchor = View.topAnchor
    }
}

With this extension:

extension UIView{
    func anchor(top: NSLayoutYAxisAnchor?,
                bottom: NSLayoutYAxisAnchor?,
                leading: NSLayoutXAxisAnchor?,
                trailing: NSLayoutXAxisAnchor?,
                paddingTop: CGFloat,
                paddingBottom: CGFloat,
                paddingLeft: CGFloat,
                paddingRight: CGFloat,
                width: CGFloat,
                height: CGFloat
    ){
        if let top = top{
            self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
        }
        if let bottom = bottom{
            self.bottomAnchor.constraint(equalTo: bottom, constant: paddingBottom).isActive = true
        }
        if let leading = leading{
            self.leadingAnchor.constraint(equalTo: leading, constant: paddingLeft).isActive = true
        }
        if let trailing = trailing{
            self.trailingAnchor.constraint(equalTo: trailing, constant: paddingRight).isActive = true
        }
        if width != 0{
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }
        if height != 0{
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }
    }
}
5
  • Do not call a property View with a capital V like that. Also, please explain where the method .anchor(top:...) comes from, as this is not built into UIKit. Commented Apr 5, 2023 at 16:22
  • If the goal is to "stack" these views one above the other, why don't you use a UIStackView. The name tells you it's just made for this job. — However, "I cannot use the TableView method because each incoming image will be of a different size", makes no sense, table view cells can have different sizes. Commented Apr 5, 2023 at 16:24
  • I showed extension part. And also the distance of each image from another will be different Commented Apr 5, 2023 at 16:53
  • 2
    Your view property is a single view, not a factory function which creates a new view each time you access/call it. You are repeatedly adding the exact same view which is why you only see one. Commented Apr 5, 2023 at 17:43
  • Geoff is right. And I also think you'd have an easier time if you weren't doing this "by hand". Commented Apr 5, 2023 at 18:28

1 Answer 1

0

A few observations:

  1. As Geoff Hackworth pointed out, you are creating only one subview (one that was instantiated once with a closure). You really want to have a method that creates a new subview every time.

  2. In the else block, you are setting the top anchor of every subsequent view to be the top anchor of the preceding view. You presumably wanted to set the next view’s top anchor to be relative to the preceding view’s bottom anchor.

  3. As a minor detail, you are not setting the bottom anchor of the last view. Generally we want our constraints to be fully defined, so I would suggest adding a bottomAnchor for the last subview. This is especially true in self-sizing cells, that need an unambiguous set of vertical constraints.

So, perhaps:

func createView() -> UIView {
    let view = UIView()
    view.backgroundColor = .red
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
}

func addSubviews() {
    var topAnchor: NSLayoutYAxisAnchor = view.safeAreaLayoutGuide.topAnchor
    var subview: UIView!
    for index in 1...11 {
        subview = createView()
        view.addSubview(subview)
        subview.activateAnchors(top: topAnchor, xCenter: view.centerXAnchor, paddingTop: 10, width: 50, height: 50)
        topAnchor = subview.bottomAnchor
    }
    view.activateAnchors(bottom: subview.bottomAnchor, paddingBottom: 10)
}

And, if you forgive me, I tweaked the UIView extension:

extension UIView{
    func activateAnchors(
        top: NSLayoutYAxisAnchor? = nil,
        bottom: NSLayoutYAxisAnchor? = nil,
        leading: NSLayoutXAxisAnchor? = nil,
        trailing: NSLayoutXAxisAnchor? = nil,
        xCenter: NSLayoutXAxisAnchor? = nil,
        yCenter: NSLayoutYAxisAnchor? = nil,
        paddingTop: CGFloat = 0,
        paddingBottom: CGFloat = 0,
        paddingLeft: CGFloat = 0,
        paddingRight: CGFloat = 0,
        width: CGFloat? = nil,
        height: CGFloat? = nil
    ) {
        if let top {
            topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
        }
        if let bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: paddingBottom).isActive = true
        }
        if let leading {
            leadingAnchor.constraint(equalTo: leading, constant: paddingLeft).isActive = true
        }
        if let trailing {
            trailingAnchor.constraint(equalTo: trailing, constant: paddingRight).isActive = true
        }
        if let xCenter {
            centerXAnchor.constraint(equalTo: xCenter).isActive = true
        }
        if let yCenter {
            centerYAnchor.constraint(equalTo: yCenter).isActive = true
        }
        if let width {
            widthAnchor.constraint(equalToConstant: width).isActive = true
        }
        if let height {
            heightAnchor.constraint(equalToConstant: height).isActive = true
        }
    }
}

In the above, I have also:

  1. Simplified the for loop so we do not need the if-else based upon the counter.

  2. I have given the UIView extension default values for its parameters, so you do not need to supply parameters of nil at the call point.


enter image description here


Needless to say, we often would consider a UIStackView:

func addSubviews() {
    let subviews = (1...11).map { _ in createView() } 

    let stackView = UIStackView(arrangedSubviews: subviews)
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.alignment = .center
    stackView.axis = .vertical
    stackView.spacing = 10
    view.addSubview(stackView)

    stackView.activateAnchors(top: view.topAnchor, bottom: view.bottomAnchor, xCenter: view.centerXAnchor, paddingTop: 10, paddingBottom: -10)
    subviews.forEach { $0.activateAnchors(width: 50, height: 50) }
}

That having been said, stack views within cells don’t always work perfectly, so you might have to resort to the prior technique.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.