Маскировка UIEffectView с пересекающимся UIBezierPath - PullRequest
0 голосов
/ 08 марта 2019

Я хотел применить UIEffectView с размытием поверх табличного представления, но в каждой ячейке просвечивали круглые UIImageView объекты.Я использовал маску и адаптировал решение из этого ответа , чтобы создать метод, который бы итеративно вырезал круги над каждой ячейкой:

func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {

    // Create new path and mask
    let newMask = CAShapeLayer()

    // Create path to clip
    let newClipPath = UIBezierPath(rect: view.bounds)

    let path1 = UIBezierPath(ovalIn: rect1)
    newClipPath.append(path1)

    if let rect2 = rect2 {
        let path2 = UIBezierPath(ovalIn: rect2)
        //FIXME: Need a way to get a union of both paths!
        //newClipPath.append(path2)
    }

    // If view already has a mask
    if let originalMask = view.layer.mask, let originalShape = originalMask as? CAShapeLayer, let originalPath = originalShape.path {

        // Create bezierpath from original mask's path
        let originalBezierPath = UIBezierPath(cgPath: originalPath)

        // Append view's bounds to "reset" the mask path before we re-apply the original
        newClipPath.append(UIBezierPath(rect: view.bounds))

        // Combine new and original paths
        newClipPath.append(originalBezierPath)
    }

    // Apply new mask
    newMask.path = newClipPath.cgPath
    newMask.fillRule = .evenOdd
    view.layer.mask = newMask
}

Эта функция вызывается для UIEffectView длякаждая видимая ячейка таблицы, используя: for cell in tableView.visibleCells().Он добавляет новый круг к маске.

Однако некоторые элементы имеют наложение значка меньшего круга, например:

Icon

Я добавил второй параметр CGRect в метод выше, чтобы условно вырезать этот круг.Тем не менее, маска остается неизменной там, где два круга перекрываются, например:

Icon with blur effect

Я посмотрел несколько ответов здесь, так как мне нужно было найтиспособ получить объединение двух UIBezierPath объектов.Однако это оказалось очень сложно.Я не думаю, что смогу использовать контекст рисования, так как это UIEffectView, а маску нужно сокращать итеративно.

Я пытался изменить правила заливки (.evenOdd, .nonZero), но этоне дает желаемого эффекта.

Есть ли какие-либо советы по объединению двух перекрывающихся UIBezierPath в одну маску?


Общая цель - добиться этого эффекта с помощью последовательных ячеек таблицы, но некоторые значки будут иметь дополнительный круг.

Full effect

Обратите внимание, что нижний значок имеет дополнительный круг, но он обрезан, и моя текущая техникавырезание этого дополнительного круга вызывает проблему, отмеченную выше, когда перекрытие не маскируется, как ожидалось.

Ответы [ 3 ]

1 голос
/ 15 марта 2019

Вы можете использовать метод addArcWithCenter для объединения двух дуг в желаемую форму. Например:

 #define DEGREES_TO_RADIANS(degrees)((M_PI * degrees)/180)

  - (UIBezierPath*)createPath
{
    UIBezierPath* path = [[UIBezierPath alloc]init];
    [path addArcWithCenter:CGPointMake(25, 25) radius:25 startAngle:DEGREES_TO_RADIANS(30) endAngle:DEGREES_TO_RADIANS(60) clockwise:false];
    [path addArcWithCenter:CGPointMake(40, 40) radius:10 startAngle:DEGREES_TO_RADIANS(330) endAngle:DEGREES_TO_RADIANS(120) clockwise:true];
    [path closePath];
    return path;
}

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

1 голос
/ 16 марта 2019

Благодаря принятому ответу (от пользователя Тибин Томас ) я смог адаптировать использование дуг с UIBezierPath, чтобы получить именно то, что мне нужно.Я принял его ответ, но опубликовал свой окончательный код здесь для дальнейшего использования.

Следует отметить, что перед вызовом этого метода я должен преобразовать координаты CGRect из суперпредставления UIImageView s в пространство координатмой UIEffectView.Я также применяю вставку -1 для получения границы 1pt.Таким образом, используемые радиусы на 1 больше, чем радиусы моих UIImageView с.

Masked icons

func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {

    // Create new path and mask
    let newMask = CAShapeLayer()

    // Create path to clip
    let newClipPath = UIBezierPath(rect: view.bounds)

    // Center of rect1
    let x1 = rect1.midX
    let y1 = rect1.midY
    let center1 = CGPoint(x: x1, y: y1)

    // New path
    let newPath: UIBezierPath

    if let rect2 = rect2 {
        // Need to get two arcs - main icon and padlock icon
        // Center of rect2
        let x2 = rect2.midX
        let y2 = rect2.midY
        let center2 = CGPoint(x: x2, y: y2)
        // These values are hard-coded for 25pt radius main icon and bottom-right-aligned 10pt radius padlock icon with a 1pt additional border
        newPath = UIBezierPath(arcCenter: center1, radius: 26, startAngle: 1.2, endAngle: 0.3, clockwise: true)
        newPath.addArc(withCenter: center2, radius: 11, startAngle: 5.8, endAngle: 2.2, clockwise: true)
    } else {
        // Only single circle is needed
        newPath = UIBezierPath(ovalIn: rect1)
    }

    newPath.close()

    newClipPath.append(newPath)

    // If view already has a mask
    if let originalMask = view.layer.mask,
        let originalShape = originalMask as? CAShapeLayer,
        let originalPath = originalShape.path {

        // Create bezierpath from original mask's path
        let originalBezierPath = UIBezierPath(cgPath: originalPath)

        // Append view's bounds to "reset" the mask path before we re-apply the original
        newClipPath.append(UIBezierPath(rect: view.bounds))

        // Combine new and original paths
        newClipPath.append(originalBezierPath)
    }

    // Apply new mask
    newMask.path = newClipPath.cgPath
    newMask.fillRule = .evenOdd
    view.layer.mask = newMask
}
1 голос
/ 14 марта 2019

попробуйте следующий код в вашей функции

 let newRect: CGRect
 if let rect2 = rect2{
    let raw = rect1.union(rect2)
     let size = max(raw.width, raw.height)
     newRect = CGRect(x: raw.minX, y: raw.minX, width: size, height: size)
    }else{
      newRect = rect1
     }

        let path1 = UIBezierPath(ovalIn: newRect)
        newClipPath.append(path1)

Полная функция

func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {

        // Create new path and mask
        let newMask = CAShapeLayer()

        // Create path to clip
        let newClipPath = UIBezierPath(rect: view.bounds)

        let newRect: CGRect
        if let rect2 = rect2{
            let raw = rect1.union(rect2)
            let size = max(raw.width, raw.height) // getting the larger value in order to draw a proper circle
            newRect = CGRect(x: raw.minX, y: raw.minX, width: size, height: size)
        }else{
            newRect = rect1
        }

        let path1 = UIBezierPath(ovalIn: newRect)
        newClipPath.append(path1)



        // If view already has a mask
        if let originalMask = view.layer.mask, let originalShape = originalMask as? CAShapeLayer, let originalPath = originalShape.path {

            // Create bezierpath from original mask's path
            let originalBezierPath = UIBezierPath(cgPath: originalPath)

            // Append view's bounds to "reset" the mask path before we re-apply the original
            newClipPath.append(UIBezierPath(rect: view.bounds))

            // Combine new and original paths
            newClipPath.append(originalBezierPath)
        }

        // Apply new mask
        newMask.path = newClipPath.cgPath
        newMask.fillRule = .evenOdd
        view.layer.mask = newMask
    }

Я использовал встроенную функцию union , чтобы создать необработанный CGRect, а затем я получил максимальное значение, чтобы нарисовать правильный круг.

...