Swift中的Stack View ScaleAspectFit蒙版调整大小 [英] Stack View ScaleAspectFit Mask Resize in Swift

查看:92
本文介绍了Swift中的Stack View ScaleAspectFit蒙版调整大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在遮罩堆栈视图中的图像,并且由于某些奇怪的原因,我的遮罩无法与图像正确对齐/调整大小.

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.

clipsToBoundsmasksToBounds是这些对中的一对(尽管令人讨厌的是它们没有相同的名称).

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆