Просто замаскируйте 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
/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)
Надеюсь, это сэкономит кому-то время в будущем:)
Не нужно никакой маски вообще. Просто поместите его в оболочку с меньшим кадром и установите 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
это правильный подход.