如果触摸在自定义形状内,我如何使UIScrollView可滚动?

时间:2015-08-08 22:00:13

标签: swift uiscrollview mask cashapelayer

我正致力于创建图片拼贴应用。而且我将有多个UIScrollView。滚动视图将具有自定义形状的边界,用户将能够动态更改它们相交的形状的角。滚动视图将UIImageView作为子视图。

滚动视图是其他UIView的子视图。我为这些CAShapeLayer中的每一个应用了UIView个掩码。这样我就可以毫无问题地屏蔽滚动视图。

但问题是,我只能滚动添加的最后一个滚动视图的内容。此外,我可以平移和缩放超出蒙版的边界。当我触摸我作为面具的多边形边界内时,我应该只能平移或缩放。

我试过了;

scrollView.clipsToBounds = true
scrollView.layer.masksToBounds = true

但结果是一样的。

不幸的是我无法发布截图,但是,这是我用来为UIViews创建掩码的代码:

func createMask(v: UIView, viewsToMask: [UIView], anchorPoint: CGPoint)
{
    let frame = v.bounds
    var shapeLayer = [CAShapeLayer]()
    var path = [CGMutablePathRef]()

    for i in 0...3 {
        path.append(CGPathCreateMutable())
        shapeLayer.append(CAShapeLayer())
    }

    //define frame constants
    let center = CGPointMake(frame.origin.x + frame.size.width / 2, frame.origin.y + frame.size.height / 2)
    let bottomLeft = CGPointMake(frame.origin.x, frame.origin.y + frame.size.height)
    let bottomRight = CGPointMake(frame.origin.x + frame.size.width, frame.origin.y + frame.size.height)

    switch frameType {
    case 1:
        // First view for Frame Type 1
        CGPathMoveToPoint(path[0], nil, 0, 0)
        CGPathAddLineToPoint(path[0], nil, bottomLeft.x, bottomLeft.y)
        CGPathAddLineToPoint(path[0], nil, anchorPoint.x, bottomLeft.y)
        CGPathAddLineToPoint(path[0], nil, anchorPoint.x, anchorPoint.y)
        CGPathCloseSubpath(path[0])

        // Second view for Frame Type 1
        CGPathMoveToPoint(path[1], nil, anchorPoint.x, anchorPoint.y)
        CGPathAddLineToPoint(path[1], nil, anchorPoint.x, bottomLeft.y)
        CGPathAddLineToPoint(path[1], nil, bottomRight.x, bottomRight.y)
        CGPathAddLineToPoint(path[1], nil, bottomRight.x, anchorPoint.y)
        CGPathCloseSubpath(path[1])

        // Third view for Frame Type 1
        CGPathMoveToPoint(path[2], nil, 0, 0)
        CGPathAddLineToPoint(path[2], nil, anchorPoint.x, anchorPoint.y)
        CGPathAddLineToPoint(path[2], nil, bottomRight.x, anchorPoint.y)
        CGPathAddLineToPoint(path[2], nil, bottomRight.x, 0)
        CGPathCloseSubpath(path[2])

    default:
        break
    }

    for (key, view) in enumerate(viewsToMask) {
        shapeLayer[key].path = path[key]
        view.layer.mask = shapeLayer[key]
    }

}

那么,如何使滚动视图的行为方式使得只有在相应的蒙版边界内发生触摸时,它们才会滚动或缩放内容?

编辑:

根据这个问题的答案:UIView's masked-off area still touchable?面具只会修改您可以看到的内容,而不是您可以触摸的区域。所以我将UIScrollView子类化,并尝试覆盖hitTest:withEvent:方法,如此,

protocol CoolScrollViewDelegate: class {
    var scrollViewPaths: [CGMutablePathRef] { get set }
}

class CoolScrollView: UIScrollView
{
    weak var coolDelegate: CoolScrollViewDelegate?

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView?
    {
        if CGPathContainsPoint(coolDelegate?.scrollViewPaths[tag], nil, point, true) {
            return self
        } else {
            return nil
        }
    }

}

但是通过这个实现,我只能在放大时检查最后一个滚动视图和路径边界。例如,如果我放大图像,hitTest:withEvent:方法返回nil。

1 个答案:

答案 0 :(得分:1)

我同意@Kendel的评论 - 首先,创建一个知道如何用特定形状掩盖自己的UIScrollView子类可能是一种更简单的方法。将形状逻辑保持在滚动视图子类中将使事情保持整洁,并允许您轻松地将触摸限制在形状内(我将在一分钟内完成)。

从您的描述中确切地说出您的塑造视图应该如何表现有点困难,但作为一个简短示例,您的ShapedScrollView可能看起来像这样:

import UIKit

class ShapedScrollView: UIScrollView {

    // MARK: Types

    enum Shape {
        case First  // Choose a better name!
    }

    // MARK: Properties

    private let shapeLayer = CAShapeLayer()

    var shape: Shape = .First {
        didSet { setNeedsLayout() }
    }

    // MARK: Initializers

    init(frame: CGRect, shape: Shape = .First) {
        self.shape = shape
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    // MARK: Layout

    override func layoutSubviews() {
        super.layoutSubviews()
        updateShape()
    }

    // MARK: Updating the Shape

    private func updateShape() {

        // Disable core animation actions to prevent changes to the shape layer animating implicitly
        CATransaction.begin()
        CATransaction.setDisableActions(true)

        if bounds.size != shapeLayer.bounds.size {
            // Bounds size has changed, completely update the shape
            shapeLayer.frame = CGRect(origin: contentOffset, size: bounds.size)
            shapeLayer.path = pathForShape(shape).CGPath
            layer.mask = shapeLayer

        } else {
            // Bounds size has NOT changed, just update origin of shape path to
            // match content offset - makes it appear stationary as we scroll
            var shapeFrame = shapeLayer.frame
            shapeFrame.origin = contentOffset
            shapeLayer.frame = shapeFrame
        }

        CATransaction.commit()
    }

    private func pathForShape(shape: Shape) -> UIBezierPath {
        let path = UIBezierPath()

        switch shape {
        case .First:
            // Build the shape path, whatever that might be...
            // path.moveToPoint(...)
            // ...
        }

        return path
    }
}

因此,使触摸仅在指定形状内工作是很容易的部分。我们已经引用了一个形状图层,它描述了我们想要限制触摸的形状。 UIView提供了一种有用的命中测试方法,可让您指定是否应将某个特定点视为该视图的“内部”:pointInside(_:withEvent:)。只需将以下覆盖添加到ShapedScrollView

override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
    return CGPathContainsPoint(shapeLayer.path, nil, layer.convertPoint(point, toLayer: shapeLayer), false)
}

这只是说:“如果point(转换为形状图层的坐标系)位于形状path内,请将其视为视图内部;否则请将其视为视图外部。”< / p>

如果屏蔽自身的滚动视图不合适,您仍然可以使用具有ShapedScrollContainerView: UIView属性的scrollView来采用此技术。然后,如上所述将形状蒙版应用于容器,并再次使用pointInside(_:withEvent:)来测试它是否应该响应特定的触摸点。