You'll want to get the frame the image occupies within the image view.
Unfortunately, UIImageView provides no native support for doing this, however you can calculate this fairly simply. I have already created a function that will take a given outer rect, and a given inner rect and return the inner rect after it's been aspect fitted to sit within the outer rect.
A Swift version of the function would look something like this:
func aspectFitRect(outerRect outerRect:CGRect, innerRect:CGRect) -> CGRect {
let innerRectRatio = innerRect.size.width/innerRect.size.height; // inner rect ratio
let outerRectRatio = outerRect.size.width/outerRect.size.height; // outer rect ratio
// calculate scaling ratio based on the width:height ratio of the rects.
let ratio = (innerRectRatio > outerRectRatio) ? outerRect.size.width/innerRect.size.width:outerRect.size.height/innerRect.size.height;
// The x-offset of the inner rect as it gets centered
let xOffset = (outerRect.size.width-(innerRect.size.width*ratio))*0.5;
// The y-offset of the inner rect as it gets centered
let yOffset = (outerRect.size.height-(innerRect.size.height*ratio))*0.5;
// aspect fitted origin and size
let innerRectOrigin = CGPoint(x: xOffset+outerRect.origin.x, y: yOffset+outerRect.origin.y);
let innerRectSize = CGSize(width: innerRect.size.width*ratio, height: innerRect.size.height*ratio);
return CGRect(origin: innerRectOrigin, size: innerRectSize);
}
The other thing you need to do is subclass UIImageView and override the layoutSubviews method. This is because as you're adding your image views to a UIStackView - you're no longer in control of the frames of your image views. Therefore by overriding layoutSubviews, you'll be able to update your mask whenever the stack view alters the frame of the view.
Something like this should achieve the desired result:
class MaskedImageView: UIImageView {
let maskLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override init(image: UIImage?) {
super.init(image: image)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
// configure your common image view properties here
contentMode = .ScaleAspectFit
clipsToBounds = true
// mask your image layer
layer.mask = maskLayer
}
override func layoutSubviews() {
guard let img = image else { // if there's no image - skip updating the mask.
return
}
// the frame that the image itself will occupy in the image view as it gets aspect fitted
let imageRect = aspectFitRect(outerRect: bounds, innerRect: CGRect(origin: CGPointZero, size: img.size))
// update mask frame
maskLayer.frame = imageRect
// half the image's on-screen width or height, whichever is smallest
let radius = min(imageRect.size.width, imageRect.size.height)*0.5
// the center of the image rect
let center = CGPoint(x: imageRect.size.width*0.5, y: imageRect.size.height*0.5)
// your custom masking path
let path = UIBezierPath()
path.moveToPoint(center)
path.addArcWithCenter(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI*2.0*0.8), clockwise: true)
path.closePath()
// update mask layer path
maskLayer.path = path.CGPath
}
}
You can then create your image views from your view controller and add them to your stack view as normal.
let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
stackView.frame = view.bounds
stackView.distribution = .FillProportionally
stackView.spacing = 10
view.addSubview(stackView)
for _ in 0..<5 {
let imageView = MaskedImageView(image:UIImage(named:"foo.jpg"))
stackView.addArrangedSubview(imageView)
stackView.layoutIfNeeded()
}
}
Gives me the following result:

Unrelated Ramblings...
Just noticed in your code that you're doing this:
testPicture.clipsToBounds = true
testPicture.layer.masksToBounds = true
These both do the same thing.
A UIView is no more than a wrapper for an underlying CALayer. However for convenience, some CALayer properties also have a UIView equivalent. All the UIView equivalent does is forward to message down to the CALayer when it is set, and retrieve a value from the CALayer when it is 'get'ed.
clipsToBounds and masksToBounds are one of these pairs (although annoyingly they don't share the same name).
Try doing the following:
view.layer.masksToBounds = true
print(view.clipsToBounds) // output: true
view.layer.masksToBounds = false
print(view.clipsToBounds) // output: false
view.clipsToBounds = true
print(view.layer.masksToBounds) // output: true
view.clipsToBounds = false
print(view.layer.masksToBounds) // output: false
Seeing as you're working with a UIView, clipToBounds is generally the preferred property to update.