Просто замаскируйте UIView прямоугольником

Я хочу знать, как просто замаскировать видимую область UIView любого вида. Все ответы / уроки, которые я читал до сих пор, описывают маскирование с помощью изображения, градиента или создания закругленных углов, что намного более продвинуто, чем то, что я делаю.

Пример: у меня есть UIView с границами (0, 0, 100, 100), и я хочу вырезать правую половину вида с помощью маски. Поэтому моя рамка маски будет (0, 0, 50, 100).

Есть идеи, как это сделать просто? Я не хочу переопределять метод drawrect, поскольку он должен быть применим к любому UIView.

Я пробовал это, но это просто делает весь вид невидимым.

CGRect mask = CGRectMake(0, 0, 50, 100);
UIView *maskView = [[UIView alloc] initWithFrame:mask];
viewToMask.layer.mask = maskView.layer;

6 ответов

Решение

Благодаря ссылке из MSK, я пошел по этому пути, который работает хорошо:

// Create a mask layer and the frame to determine what will be visible in the view.
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = CGRectMake(0, 0, 50, 100);

// Create a path with the rectangle in it.
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);

// Set the path to the mask layer.
maskLayer.path = path;

// Release the path since it's not covered by ARC.
CGPathRelease(path);

// Set the mask of the view.
viewToMask.layer.mask = maskLayer;

Спасибо за ответы, ребята.

В случае, если кто-то не может найти подходящий ответ на SO на этот вопрос в течение нескольких часов, как я только что сделал, я собрал рабочий гист в Swift 2.2 для masking/clippingUIView с CGRect/UIBezierPath:

https://gist.github.com/Flar49/7e977e81f1d2827f5fcd5c6c6a3c3d94

extension UIView {

    func mask(withRect rect: CGRect, inverse: Bool = false) {
        let path = UIBezierPath(rect: rect)
        let maskLayer = CAShapeLayer()

        if inverse {
            path.append(UIBezierPath(rect: self.bounds))
            maskLayer.fillRule = kCAFillRuleEvenOdd
        }

        maskLayer.path = path.cgPath

        self.layer.mask = maskLayer
    }

    func mask(withPath path: UIBezierPath, inverse: Bool = false) {
        let path = path
        let maskLayer = CAShapeLayer()

        if inverse {
            path.append(UIBezierPath(rect: self.bounds))
            maskLayer.fillRule = kCAFillRuleEvenOdd
        }

        maskLayer.path = path.cgPath

        self.layer.mask = maskLayer
    }
}

Использование:

let viewSize = targetView.bounds.size
let rect = CGRect(x: 20, y: 20, width: viewSize.width - 20*2, height: viewSize.height - 20*2)

// Cuts rectangle inside view, leaving 20pt borders around
targetView.mask(withRect: rect, inverse: true)

// Cuts 20pt borders around the view, keeping part inside rect intact
targetView.mask(withRect: rect)

Надеюсь, это сэкономит кому-то время в будущем:)

Не нужно никакой маски вообще. Просто поместите его в оболочку с меньшим кадром и установите clipsToBounds.

wrapper.clipsToBounds = YES;

Очень простой пример в Swift ViewController, основанный на принятом ответе:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let redView = UIView(frame: view.bounds)
        view.addSubview(redView)

        view.backgroundColor = UIColor.greenColor()
        redView.backgroundColor = UIColor.redColor()

        mask(redView, maskRect: CGRect(x: 50, y: 50, width: 50, height: 50))
    }

    func mask(viewToMask: UIView, maskRect: CGRect) {
        let maskLayer = CAShapeLayer()
        let path = CGPathCreateWithRect(maskRect, nil)
        maskLayer.path = path

        // Set the mask of the view.
        viewToMask.layer.mask = maskLayer;
    }
}

Выход

Необязательный слой, альфа-канал которого используется в качестве маски для выбора между фоном слоя и результатом компоновки содержимого слоя с его отфильтрованным фоном.

@property (сохранить) CALayer * маска

Правильный способ сделать то, что вы хотите, это создать maskView того же кадра (0, 0, 100, 100), что и viewToMask какой слой вы хотите замаскировать. Затем вам нужно установить clearColor для пути, который вы хотите сделать невидимым (это заблокирует взаимодействие представления по пути, поэтому будьте осторожны с иерархией представления).

Swift 5, спасибо @Dan Rosenstark

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let red = UIView(frame: view.bounds)
        view.addSubview(red)
        view.backgroundColor = UIColor.cyan
        red.backgroundColor = UIColor.red
        red.mask(CGRect(x: 50, y: 50, width: 50, height: 50))
    }
}

extension UIView{
    func mask(_ rect: CGRect){
        let mask = CAShapeLayer()
        let path = CGPath(rect: rect, transform: nil)
        mask.path = path
        // Set the mask of the view.
        layer.mask = mask
    }
}

Настройка MaskToBounds недостаточно, например, в сценарии, где у вас есть UIImageView который расположен внутри RelativeLayout, В случае, если вы положили UIImageView чтобы быть рядом с верхним краем макета, а затем вы поворачиваете UIImageView, он пройдет макет и не будет обрезан. Если вы установите этот флаг в true, он будет обрезан, но также будет обрезан, если UIImageView находится в центре макета, и это не то, что вы хотите.

Итак, определяя maskRect это правильный подход.

Другие вопросы по тегам