1
votes

I want to create an array of objects that are horizontally cascaded, I have attempted to create a function to reduce the size of my code however, it seems like there might be an NSLayoutConstraint conflict between the objects that are created maybe?

Here's my code

private func createProfileImageContainers(numberOfFriends: Int) {

    for friends in 1...numberOfFriends {

    let imageViewContainer = UIView()
    imageViewContainer.translatesAutoresizingMaskIntoConstraints = false
    imageViewContainer.backgroundColor = UIColor.blue
    imageViewContainer.frame = CGRect(x: 0, y: 0, width: frame.width / 10, height: frame.width / 10)

        NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: CGFloat((1 / 2) + ((friends - 1) / 50 )), constant: 0).isActive = true

        NSLayoutConstraint(item: imageViewContainer, attribute: .centerY, relatedBy: .equal, toItem: container, attribute: .centerY, multiplier: 1, constant: 0).isActive = true

        addSubview(imageViewContainer)
    }

}

Here's what debugger is saying

A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs.'

Any suggestions?

EDIT:

Thanks to Robs answer I was able to solve the issues with the debugger however only one instance of imageViewContainer is being adding. Probably because they are all being added to the view hierarchy with the same name so each new view takes the place of the last... I thought creating a class would solve this but now I can't get anything to appear.

Here's the updated code...

class profileImageContainer: UIView {

    let imageViewContainer: UIView = {
    let iv = UIView()
    iv.translatesAutoresizingMaskIntoConstraints = false
    iv.backgroundColor = UIColor.blue


    return iv
}()

}


private func createProfileImageContainers(numberOfFriends: Int) {



    for friends in 1...numberOfFriends {


        print(friends)


        let imageViewContainer = profileImageContainer()


        addSubview(imageViewContainer)

        NSLayoutConstraint(item: imageViewContainer, attribute: .width, relatedBy: .equal, toItem: container, attribute: .width, multiplier: 0.1, constant: 0).isActive = true
        NSLayoutConstraint(item: imageViewContainer, attribute: .height, relatedBy: .equal, toItem: container, attribute: .width, multiplier: 0.1, constant: 0).isActive = true

        NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: 0.5 + (CGFloat(friends - 1) / 50.0), constant: 0).isActive = true


        NSLayoutConstraint(item: imageViewContainer, attribute: .centerY, relatedBy: .equal, toItem: container, attribute: .centerY, multiplier: 1, constant: 0).isActive = true



    }


} 
1
prefer UITableView or UIStackView for the same. - luckyShubhra
Shouldn't the centerX multiplier multiply something by the container's width? - NRitH
@NRitH The multiplier is a multiplier it can be any number. The code that's in there is just a placeholder atm, but it still should be valid. I just wanted to test the for in loop. - Stefan
To luckyShubhra's point, if and when you want to consider refactoring this, a stack view (if you don't need scrolling ability) gets you out of defining constraints for all the subviews. You just need constraints for the stack view, specify it to be horizontal stack view and tell it to evenly space its arranged views. Then it will take care of all arranging all of the subviews. Or if you want a horizontally scrolling ability, you can use collection view. But I understand if you want to get this working, first. - Rob
@luckyShubhra All I'm trying to do is create 8 circles with each circles left edge being the previous circles centerx. I could create each view one by one, but that doesn't seem optimal. - Stefan

1 Answers

3
votes
  1. One problem is the expression:

    CGFloat((1 / 2) + ((friends - 1) / 50))
    

    That is doing integer division and then converting the resulting integer into a CGFloat. In practice, your expression will return 0 for the first 50 friends values.

    You want to do floating point math, converting friends - 1 to a CGFloat before you do the division:

    0.5 + CGFloat(friends - 1) / 50.0
    
  2. I'd also suggest that you'll want to add the subview before adding the constraints.

  3. You should specify the width and height constraints and eliminate the setting of the frame. The frame will be discarded when the constraints are applied and in the absence of width and height constraints, the constraints are ambiguous.


There are a couple of problems with your second code sample:

  • Your ProfileImageContainer has an imageViewContainer, but you never do anything with it. So, you're not going to see your ProfileImageContainers (because you didn't set its own backgroundColor). I can imagine that you might eventually do something meaningful with imageViewContainer property of ProfileImageContainer (e.g. add it to the view hierarchy, set the image, etc.). But for now, I'd suggest you remove that as it's only confusing the situation.

  • Even when we fix the above, the subviews are going to overlap because you've defined them to be 1/10th of the width of some container, but you're adjusting the centerX multiplier by 1/50th.

    The net effect of this is that the views will overlap, making it appear that there is only one present. But I believe if you use the view debugger, you’ll see that they’re all there. You need to alter the centerX constraint so that they don’t overlap.

Anyway, here is a rendition that fixes the above issues:

//  SampleView.swift

import UIKit

class ProfileImageContainer: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        configure()
    }

    func configure() {
        translatesAutoresizingMaskIntoConstraints = false
        backgroundColor = .blue
    }

}

class SampleView: UIView {

    var container: UIView!

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)

        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        configure()
    }

    func configure()  {
        container = UIView()
        container.translatesAutoresizingMaskIntoConstraints = false
        addSubview(container)
        NSLayoutConstraint.activate([
            container.leftAnchor.constraint(equalTo: leftAnchor),
            container.rightAnchor.constraint(equalTo: rightAnchor),
            container.topAnchor.constraint(equalTo: topAnchor),
            container.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])

        createProfileImageContainers(numberOfFriends: 5)
    }

    var friends = [UIView]()

    private func createProfileImageContainers(numberOfFriends: Int) {

        // remove old friends in case you called this before

        friends.forEach { $0.removeFromSuperview() }
        friends.removeAll()

        // now add friends 

        for friend in 0 ..< numberOfFriends {     // easier to go from 0 to numberOfFriends-1 than subtract one later

            print(friend)

            let imageViewContainer = ProfileImageContainer()

            container.addSubview(imageViewContainer)

            NSLayoutConstraint.activate([
                imageViewContainer.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.1),
                imageViewContainer.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.1),
                NSLayoutConstraint(item: imageViewContainer, attribute: .centerX, relatedBy: .equal, toItem: container, attribute: .centerX, multiplier: 2 * CGFloat(friend + 1) / CGFloat(numberOfFriends + 1), constant: 0),
                imageViewContainer.centerYAnchor.constraint(equalTo: container.centerYAnchor)
            ])

            friends.append(imageViewContainer)
        }

    } 

}