1

I have a UIStackView with UIImageViews that might be from 1 to 5. Each UIImageView has an image coming from an array (from previously downloaded and cached images) and I'd like to keep the UIImageViews stay in a perfect circle. I change the width constant of the UIStackView along with the spacing between the images in a way to overlap if there are more than 3 images.

I had writen this code in a previous project and it works perfectly fine, but for some reason, when I call the changeNavStackWidth function to change the width of the UIStackView, the width is not being updated and I have no clue why.

var userImages = [String]() 

var navStackView : UIStackView = {
    let stack = UIStackView()
    stack.axis = .horizontal
    stack.alignment = .fill
    stack.distribution = .fillEqually
    stack.translatesAutoresizingMaskIntoConstraints = false
    return stack
}()
override func viewDidLoad() {
    super.viewDidLoad()
    setupNavStack()
    navBarStackTapGesture()
}

func setupNavStack() {
    guard let navController = navigationController else { return }

    navController.navigationBar.addSubview(navStackView)
    // x, y, w, h
    navStackView.widthAnchor.constraint(equalToConstant: 95).isActive = true
    navStackView.centerYAnchor.constraint(equalTo: navController.navigationBar.centerYAnchor).isActive = true
    navStackView.heightAnchor.constraint(equalToConstant: 35).isActive = true
    navStackView.centerXAnchor.constraint(equalTo: navController.navigationBar.centerXAnchor).isActive = true

} 

        func setNavBarImages() {

    for image in userImages {
        let imageView = UIImageView()
        // imageView.image = UIImage(named: image)

        let photoURL = URL(string: image)
        imageView.sd_setImage(with: photoURL)

        imageView.layer.borderColor = UIColor.white.cgColor
        imageView.layer.borderWidth = 1
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true

        navStackView.addArrangedSubview(imageView)
        navStackView.layoutIfNeeded()
    }

    switch userImages.count {
    case 0:
        print("0 images")
    case 1:
        changeNavStackWidth(constant: 35, spacing: 0)
        //changeNavStackWidth(constant: 60, spacing: 0)
    case 2:
        changeNavStackWidth(constant: 80, spacing: 10)
    case 3:
        changeNavStackWidth(constant: 95, spacing: -5)
    case 4:
        changeNavStackWidth(constant: 110, spacing: -10)
    case 5:
        changeNavStackWidth(constant: 95, spacing: -20)
    case 6...1000:
       // changeNavStackWidth(constant: 95, spacing: -20)
    default:
        print("default")

    }
    navigationItem.titleView = navStackView
    navStackView.layoutIfNeeded()
}

func changeNavStackWidth(constant: CGFloat, spacing: CGFloat) {
    navStackView.constraints.forEach { constraint in
        if constraint.firstAttribute == .width {
            constraint.constant = constant
            print("constan is:", constant) // not being printed
        }
    }
    navStackView.spacing = spacing
} 
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    navStackView.subviews.forEach { $0.layer.cornerRadius = $0.frame.height / 2 }
 }
Dani
  • 3,427
  • 3
  • 28
  • 54
  • where did you set navStackView.translatesAutoresizingMaskIntoConstraints = false – Sahil Manchanda Sep 14 '18 at 18:35
  • @SahilManchanda in the declaration of the `navStackView` (I've added it in the code to make it clear) – Dani Sep 14 '18 at 18:37
  • Can you check if your changeNavStackWidth function is getting called – Sahil Manchanda Sep 14 '18 at 18:43
  • @SahilManchanda I did, I've put a breakpoint on it and it's called, but for some reason it doesn't iterate through the constraints and I don't know why. When I step into it, it goes into the declaration of `navStackView` then goes back in the function straight to `navStackView.spacing = spacing` and doesn't loop through the constraints. This is why that `print` statement is never executed. – Dani Sep 14 '18 at 18:46
  • if possible can you put minimum working code in the question so that i can paste and run.. it will help in debug – Sahil Manchanda Sep 14 '18 at 18:52
  • @SahilManchanda I've added more code, I think that's all you need to run it (except the whole API requests, you can use just any local images and store their names in the array) – Dani Sep 14 '18 at 19:01
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/180067/discussion-between-sahil-manchanda-and-dani). – Sahil Manchanda Sep 14 '18 at 19:06

3 Answers3

1

based on our discussion in chat. Here is what you want:

A) if there is only one Image enter image description here B) if there are two Images: enter image description here C) if there are more than 2: enter image description here

Code:

import UIKit

class TestStack: UIViewController{
    var userImages = [#imageLiteral(resourceName: "images-1"),#imageLiteral(resourceName: "images-2"),#imageLiteral(resourceName: "images-4"),#imageLiteral(resourceName: "images-5")]

    let imagesHolder: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        userImages.removeLast(2)
        guard let bar = navigationController?.navigationBar else {return}
        bar.addSubview(imagesHolder)
        let size = 25
        for i in 0..<userImages.count{
            var x = i * size
            x = userImages.count > 2 ? x/2 : x
            x = (i == 1 && userImages.count == 2) ? x + 5 : x
            let imageView = UIImageView(frame: CGRect(x: x, y: 0, width: size, height: size))
            imageView.image = userImages[i]
            imageView.clipsToBounds = true
            imageView.layer.cornerRadius = CGFloat(size / 2)
            imagesHolder.addSubview(imageView)
        }

        let width = CGFloat((userImages.count  * size)/2)
        NSLayoutConstraint.activate([
            imagesHolder.centerYAnchor.constraint(equalTo: bar.centerYAnchor),
            imagesHolder.centerXAnchor.constraint(equalTo: bar.centerXAnchor),
            imagesHolder.widthAnchor.constraint(equalToConstant: width),
            imagesHolder.heightAnchor.constraint(equalToConstant: CGFloat(size))
            ])

    }
}
Sahil Manchanda
  • 9,812
  • 4
  • 39
  • 89
0

Instead of adjusting the StackView's width to adjust to the images:

  1. Create a UIView and add the UIImageView as a subview to the UIView.
  2. Add a top and bottom constraint to the UIImageView with the UIView
  3. Add a center horizontally constraint the UIImageView with the UIView
  4. Add an aspect ratio constraint to the UIImageView with itself and set that ratio to 1:1
  5. Add the UIView to the Stack View

The constraints on the UIImageView in relation to the UIView will keep the images separated out equally, and the aspect ratio constraint on itself will keep the UIImage as a perfect circle.

BlondeSwan
  • 772
  • 5
  • 26
  • What about the `UIView` constraints? Should they be `top`, `bottom`, `leading` & `trailing` to the `UIStackView`? (PS: keep in mind, this `UIStackView` is placed in the middle of the `UINavigationBar`) – Dani Sep 14 '18 at 19:56
  • Just `top` and `bottom` constraints to the UIStackView. Leading/Trailing constraints and widths of views are based on your settings of the StackView (width, spacing, etc.) – BlondeSwan Sep 15 '18 at 01:11
0

Changing your imageView's content mode to .aspectFit will also do the trick without having to mess with your stackView's width.

Change this imageView.contentMode = .scaleAspectFill to this imageView.contentMode = .scaleAspectFit

AspectFill stretches the image to fit the view, and doesn't maintain the scale ratio of the image AspectFit stretches the image until the horizontal bound OR vertical bound hits the view, while maintaining the scale ratio of the image.

see here

BlondeSwan
  • 772
  • 5
  • 26
  • The problem with this is that I cannot set the image to be a circle. Since the `UIImageView` is set dynamically, I have to create a new one for each image, but while doing so, the frame is still not set, so I cant get the `frame.height / 2`. I went through that issue today. This is why I overrride `viewDidLayoutSubviews()`, iterate through the subviews of the `navStackView` and then set it. – Dani Sep 14 '18 at 19:17
  • Okay, I was thinking your actual image was a circle. In that case, check out the other answer I posted. You won't need to mess with the width of the StackView if you implement it that way. – BlondeSwan Sep 14 '18 at 19:27