1

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



    }


} 
6
  • prefer UITableView or UIStackView for the same. Commented Jun 29, 2017 at 4:23
  • Shouldn't the centerX multiplier multiply something by the container's width? Commented Jun 29, 2017 at 4:24
  • @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. Commented Jun 29, 2017 at 4:42
  • 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. Commented Jun 29, 2017 at 6:06
  • @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. Commented Jun 29, 2017 at 19:04

1 Answer 1

3
  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)
        }

    } 

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

3 Comments

This clears up the issues in the debugger however I am still unable to create multiple instances of the same object. Please review the updated code.
See revised answer with code sample. FYI, I also put it out at github.com/robertmryan/AddSubviewDemo. See github.com/robertmryan/AddSubviewDemo/releases for notes about the edits I did to your code.
@Stefan - Frankly, rather than building these constraints yourself, I'd suggest using a stack view (see github.com/robertmryan/AddSubviewDemo/tree/UIStackView). It completely gets you out of the business of manually defining constraints for all the subviews. Just define constraints for the stack view, and let it arrange its subviews. Or, if the number of "friends" might exceed what can fit and you want to be able to scroll through them, use a collection view (see github.com/robertmryan/AddSubviewDemo/tree/UICollectionView).

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.