Воссоздание эффекта размытия в маске в SwiftUI

#swiftui #uikit

#swiftui #uikit

Вопрос:

Я создал этот эффект размытия в маске (код ниже), он выполняется в SwiftUI, но использует UIViewRepresentable для маскировки, возможно ли воссоздать тот же эффект, но только в чистом SwiftUI?

Размытие с помощью маски

Вот текущий код, если вы запустите его, проведите пальцем по экрану, при этом маска откроется снизу.

 import SwiftUI
import UIKit

struct TestView: View {

  @State var position: CGPoint = .zero

  var simpleDrag: some Gesture {
    DragGesture()
      .onChanged { value in
        self.position = value.location
      }
  }

  var body: some View {
    ZStack {
  
      Circle()
        .fill(Color.green)
        .frame(height: 200)
  
      Circle()
        .fill(Color.pink)
        .frame(height: 200)
        .offset(x: 50, y: 100)
  
      Circle()
        .fill(Color.orange)
        .frame(height: 100)
        .offset(x: -50, y: 00)
  
      BlurView(style: .light, position: $position)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
  
    }
    .gesture(
      simpleDrag
    )

  }
}

struct BlurView: UIViewRepresentable {

  let style: UIBlurEffect.Style

  @Binding var position: CGPoint

  func makeUIView(context: UIViewRepresentableContext<BlurView>) -> UIView {
    let view = UIView(frame: .zero)
    view.backgroundColor = .clear
    let blurEffect = UIBlurEffect(style: style)
    let blurView = UIVisualEffectView(effect: blurEffect)
    blurView.translatesAutoresizingMaskIntoConstraints = false
    view.insertSubview(blurView, at: 0)
    NSLayoutConstraint.activate([
      blurView.heightAnchor.constraint(equalTo: view.heightAnchor),
      blurView.widthAnchor.constraint(equalTo: view.widthAnchor),
    ])

    let clipPath = UIBezierPath(rect: UIScreen.main.bounds)

    let circlePath = UIBezierPath(ovalIn: CGRect(x: 100, y: 0, width: 200, height: 200))

    clipPath.append(circlePath)

    let layer = CAShapeLayer()
    layer.path = clipPath.cgPath
    layer.fillRule = .evenOdd
    view.layer.mask = layer
    view.layer.masksToBounds = true

    return view
  }

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

    let clipPath = UIBezierPath(rect: UIScreen.main.bounds)

    let circlePath = UIBezierPath(ovalIn: CGRect(x: position.x, y: position.y, width: 200, height: 200))

    clipPath.append(circlePath)

    let layer = CAShapeLayer()
    layer.path = clipPath.cgPath
    layer.fillRule = .evenOdd
    uiView.layer.mask = layer
    uiView.layer.masksToBounds = true

  }

}

struct TestView_Previews: PreviewProvider {
  static var previews: some View {
    TestView()
  }
}
 

Ответ №1:

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

размытие

 import SwiftUI

struct TestView2: View {

  @State var position: CGPoint = .zero

  var simpleDrag: some Gesture {
    DragGesture()
      .onChanged { value in
        self.position = value.location
      }
  }

  var body: some View {
    ZStack {
  
      Circle()
        .fill(Color.green)
        .frame(height: 200)
  
      Circle()
        .fill(Color.pink)
        .frame(height: 200)
        .offset(x: 50, y: 100)
  
      Circle()
        .fill(Color.orange)
        .frame(height: 100)
        .offset(x: -50, y: 00)
    }
    .maskedBlur(position: $position)
    .gesture(
      simpleDrag
    )

  }
}

struct MaskedBlur: ViewModifier {

  @Binding var position: CGPoint

  /// Render the content twice
  func body(content: Content) -> some View {

    ZStack {
      content
  
      content
        .blur(radius: 10)
        .mask(
          Hole(position: $position)
            .fill(style: FillStyle(eoFill: true))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        )
    }

  }
}

extension View {
  func maskedBlur(position: Binding<CGPoint>) -> some View {
    self.modifier(MaskedBlur(position: position))
  }
}

struct Hole: Shape {

  @Binding var position: CGPoint

  func path(in rect: CGRect) -> Path {
    var path = Path()

    path.addRect(UIScreen.main.bounds)

    path.addEllipse(in: CGRect(x: position.x, y: position.y, width: 200, height: 200))

    return path
  }
}


#if DEBUG

struct TestView2_Previews: PreviewProvider {
  static var previews: some View {
    TestView2()
  }
}

#endif