#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