SwiftUI добавить инвертированную маску - PullRequest
6 голосов
/ 09 января 2020

Я пытаюсь добавить маску к двум фигурам, чтобы вторая фигура маскировала первую фигуру. Если я сделаю что-то вроде Circle().mask(Circle().offset(…)), это будет иметь противоположный эффект: запретить что-либо за пределами первого круга быть видимым.

Для UIView ответ здесь: iOS инвертировать маску в drawRect

Однако попытка реализовать это в SwiftUI без UIView ускользает от меня. Я попытался реализовать InvertedShape с помощью, который затем мог бы использовать в качестве маски:

struct InvertedShape<OriginalType: Shape>: Shape {
    let originalShape: OriginalType

    func path(in rect: CGRect) -> Path {
        let mutableOriginal = originalShape.path(in: rect).cgPath.mutableCopy()!
        mutableOriginal.addPath(Path(rect).cgPath)
        return Path(mutableOriginal)
            .evenOddFillRule()
    }
}

К сожалению, пути SwiftUI не имеют addPath(Path) (потому что они неизменны) или evenOddFillRule(). Вы можете получить доступ к CGPath пути и создать изменяемую копию, а затем добавить два пути, однако evenOddFillRule должен быть установлен на CGLayer, а не CGPath. Поэтому, если я не смогу добраться до CGLayer, я не уверен, что делать дальше.

Это Swift 5.

Ответы [ 4 ]

5 голосов
/ 09 января 2020

Вот демонстрация возможного подхода к созданию инвертированной маски, только с помощью SwiftUI (на примере, чтобы сделать отверстие в поле зрения)

SwiftUI hole mask, reverse mask

func HoleShapeMask(in rect: CGRect) -> Path {
    var shape = Rectangle().path(in: rect)
    shape.addPath(Circle().path(in: rect))
    return shape
}

struct TestInvertedMask: View {

    let rect = CGRect(x: 0, y: 0, width: 300, height: 100)
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: rect.width, height: rect.height)
            .mask(HoleShapeMask(in: rect).fill(style: FillStyle(eoFill: true)))
    }
}
2 голосов
/ 09 января 2020

Я еще не проверял это, но не могли бы вы сделать что-то вроде этого:

extension UIView {
    func mask(_ rect: CGRect, invert: Bool = false) {
        let maskLayer = CAShapeLayer()
        let path = CGMutablePath()

        if (invert) {
            path.addRect(bounds)
        }
        path.addRect(rect)
        maskLayer.path = path

        if (invert) {
            maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
        }

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

struct MaskView: UIViewRepresentable {
    @Binding var child: UIHostingController<ImageView>
    @Binding var rect: CGRect
    @Binding var invert: Bool

    func makeUIView(context: UIViewRepresentableContext<MaskView>) -> UIView {
        let view = UIView()

        self.child.view.mask(self.rect, invert: self.invert)

        view.addSubview(self.child.view)

        return view
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<MaskView>) {

    }
}

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

struct ImageView: View {
    var body: some View {
        Image("image1")
    }
}

struct ContentView: View {
    @State var child = UIHostingController(rootView: ImageView())
    @State var rect: CGRect = CGRect(x: 50, y: 50, width: 50, height: 50)
    @State var invert: Bool = false

    var body: some View {
        VStack(alignment: .leading) {
            MaskView(child: self.$child, rect: self.$rect, invert: self.$invert)
        }
    }
}
0 голосов
/ 05 мая 2020

Вот еще один способ сделать это, который более быстрый.

Хитрость заключается в использовании:

YourMaskView()
   .compositingGroup()
   .luminanceToAlhpa() 

maskedView.mask(YourMaskView())

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

.compositingView(), аналогично .drawingGroup(), растеризует вид (преобразует его в растровую текстуру ). Кстати, это также происходит, когда вы .blur или выполняете любые другие операции на уровне пикселей.

.luminanceToAlpha() берет уровни яркости RGB (я угадываю путем усреднения значений RGB) и сопоставляет их с Альфа-канал (непрозрачность) растрового изображения.

0 голосов
/ 09 января 2020

Если вы хотите что-то вроде этого:

two circles overlayed

Тогда вы можете просто поместить две фигуры в ZStack и дать маскирующую цвет фона:

struct MaskView: View {

    @Environment(\.colorScheme) var colorScheme: ColorScheme

    var body: some View {
        ZStack {
            Circle()
                .fill(Color.yellow)
            Circle()
                .fill(colorScheme == .dark ? Color.black : Color.white)
                .offset(x: 150.0, y: 10.0)
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...