Просто замаскируйте 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;

Ответ 1

Благодаря ссылке из 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 not covered by ARC.
CGPathRelease(path);

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

Ответ 2

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

В случае, если кто-то не может найти подходящий ответ по SO на этот вопрос в течение нескольких часов, как я только что сделал, я собрал рабочую суть в Swift 2.2 для masking/clipping UIView с 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)

Надеюсь, это спасет кого-то в будущем :)

Ответ 3

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

wrapper.clipsToBounds = YES;

Ответ 4

Очень простой пример в 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;
    }
}

Выход

введите описание изображения здесь

Ответ 5

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

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

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

Ответ 6

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
    }
}

Ответ 7

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

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