Swift中的Stack View ScaleAspectFit蒙版调整大小 [英] Stack View ScaleAspectFit Mask Resize in Swift
问题描述
我正在遮罩堆栈视图中的图像,并且由于某些奇怪的原因,我的遮罩无法与图像正确对齐/调整大小.
I am masking an image within a stack view and for some odd reason, my mask is not aligning/resizing correctly with the image.
这是当我在堆栈视图中动态添加此图像实例,同时在每个子视图的边界和间距内调整其大小时所发生情况的演示.
Here is a demonstration of what's occurring as I'm dynamically adding instances of this image in a stack view while each subview is resized within its boundaries and spacing.
如您所见,遮罩保留图像的原始大小,而不保留调整大小的版本.我尝试了许多不同的宽度和宽度高度变化,包括 bounds.width,layer.frame.width,frame.width,frame.origin.x 等,并且没有运气.
As you can see, the mask retains the original size of the image and not the resized version. I've tried many different width & height variations including the bounds.width, layer.frame.width, frame.width, frame.origin.x, etc, and had no luck.
Swift 2中的当前代码
let testPicture:UIImageView = UIImageView(image: UIImage(named: "myPicture"))
testPicture.contentMode = .ScaleAspectFit
testPicture.layer.borderWidth = 1
testPicture.clipsToBounds = true
testPicture.layer.masksToBounds = true
view.layer.addSublayer(shapeLayer)
var width = testPicture.layer.frame.width
var height = testPicture.layer.frame.height
let center = CGPointMake(width/2, height/2)
let radius = CGFloat(CGFloat(width) / 2)
// Mask
let yourCarefullyDrawnPath = UIBezierPath()
yourCarefullyDrawnPath.moveToPoint(center)
yourCarefullyDrawnPath.addArcWithCenter(center,
radius: radius,
startAngle: 0,
endAngle: CGFloat( (0.80*360.0) * M_PI / 180.0),
clockwise: true)
yourCarefullyDrawnPath.closePath()
let maskPie = CAShapeLayer()
maskPie.frame = testPicture.layer.bounds
testPicture.clipsToBounds = true
testPicture.layer.masksToBounds = true
maskPie.path = yourCarefullyDrawnPath.CGPath
testPicture.layer.mask = maskPie
// Add Into Stackview
self.myStackView.addArrangedSubview(testPicture)
self.myStackView.layoutIfNeeded()
我怀疑我获取了错误的宽度和高度以生成中心和半径变量,尽管在尝试了所有可以找到的宽度和高度后,仍然无法获得正确的尺寸. :-(
I suspect that I'm fetching the wrong width and height in order to generate the center and radius variables although after trying all the different widths and heights I can find, I still cannot achieve the correct sizes. :-(
推荐答案
您将要获取图像在图像视图中占据的帧.
不幸的是,UIImageView
不提供本机支持,但是您可以相当简单地计算出它.我已经创建了一个函数,该函数将采用给定的外部rect和给定的内部rect,并在返回后返回内部rect.外观适合坐在外部直肠内.
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.
该功能的Swift版本如下所示:
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);
}
您需要做的另一件事是子类layoutSubviews
方法.这是因为在将图像视图添加到UIStackView
时-您不再控制图像视图的帧.因此,通过覆盖layoutSubviews
,只要堆栈视图改变了视图的框架,您就可以更新蒙版.
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.
类似的事情应该可以达到预期的效果:
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
}
}
然后,您可以从视图控制器创建图像视图,然后将它们照常添加到堆栈视图中.
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()
}
}
给我以下结果:
只需在您的代码中注意到您正在执行此操作:
Just noticed in your code that you're doing this:
testPicture.clipsToBounds = true
testPicture.layer.masksToBounds = true
这两者都做同样的事情.
UIView
只不过是底层CALayer
的包装.但是,为方便起见,某些CALayer
属性也具有UIView
等效项.等同于UIView
的所有操作都将转发给设置为CALayer
的消息,并在CALayer
被获取"时从CALayer
检索值.
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
和masksToBounds
是这些对中的一对(尽管令人讨厌的是它们没有相同的名称).
clipsToBounds
and masksToBounds
are one of these pairs (although annoyingly they don't share the same name).
尝试执行以下操作:
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
看到您正在使用UIView
时,clipToBounds
通常是要更新的首选属性.
Seeing as you're working with a UIView
, clipToBounds
is generally the preferred property to update.
这篇关于Swift中的Stack View ScaleAspectFit蒙版调整大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!