Created
December 14, 2024 01:51
-
-
Save skhavari/b7fbeaf777b75e9125a8d579f7f1cfce to your computer and use it in GitHub Desktop.
Ignore taps in transparent pixels of images
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct ContentView: View { | |
@State private var backgroundTouched = false | |
@State private var overlayTouched = false | |
@State private var shouldBubble = false | |
var body: some View { | |
ZStack { | |
Image("background") | |
.resizable() | |
.modifier(GradientOverlayEffect(show: backgroundTouched)) | |
.modifier(PopEffect(scale: backgroundTouched ? 1.1 : 1.0)) | |
.onTapGesture { | |
withAnimation(.default.speed(3.0)){ backgroundTouched.toggle() } | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { | |
withAnimation { backgroundTouched = false } | |
} | |
} | |
Img() | |
.border(.red) | |
.modifier(PopEffect(scale: overlayTouched ? 1.1 : 1.0)) | |
.modifier(GlowEffect(intensity: overlayTouched ? 14.0 : 0.0) ) | |
.onTapGesture { | |
withAnimation(.default.speed(3.0)) { overlayTouched.toggle() } | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { | |
withAnimation { overlayTouched = false } | |
} | |
} | |
} | |
.ignoresSafeArea(edges: .all) | |
.safeAreaInset(edge: .top) { | |
HStack { | |
Image(systemName: "bolt.fill") | |
Spacer() | |
Text(shouldBubble ? "Ignore Transparent Pixels" : "Respect All Pixels") | |
.font(.title3) | |
.bold() | |
Spacer() | |
Button{ | |
shouldBubble.toggle() | |
} label: { | |
Image(systemName: "togglepower") | |
.rotationEffect(shouldBubble ? .zero : .degrees(90)) | |
} | |
.tint(.primary) | |
} | |
.padding() | |
.frame(maxWidth: .infinity) | |
.background(.ultraThinMaterial) | |
} | |
} | |
@ViewBuilder | |
private func Img() -> some View { | |
if shouldBubble { | |
AlphaFilteredImage("overlay").fixedSize() | |
} else { | |
Image("overlay") | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
struct AlphaFilteredImage : UIViewRepresentable { | |
var name : String | |
init(_ name: String) { self.name = name } | |
func makeUIView(context: Context) -> UIView { | |
let image = UIImage(named: name) | |
let view = AlphaFilteredUIImageView() | |
view.image = image | |
view.isUserInteractionEnabled = true | |
return view | |
} | |
func updateUIView(_ uiView: UIViewType, context: Context) { | |
} | |
class AlphaFilteredUIImageView: UIImageView { | |
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { | |
guard let image = self.image else { return false } | |
// Ensure the point is within the bounds of the image | |
guard bounds.contains(point) else { return false } | |
// Convert the point to the image's coordinate system | |
let x = Int((point.x / bounds.size.width) * image.size.width) | |
let y = Int((point.y / bounds.size.height) * image.size.height) | |
// Get the pixel data of the image | |
guard let cgImage = image.cgImage, let data = cgImage.dataProvider?.data else { return false } | |
let pixelData = CFDataGetBytePtr(data) | |
let bytesPerPixel = cgImage.bitsPerPixel / 8 | |
let bytesPerRow = cgImage.bytesPerRow | |
// Check alpha values in a 10-pixel radius | |
let radius = 10 | |
for deltaY in -radius...radius { | |
for deltaX in -radius...radius { | |
let checkX = x + deltaX | |
let checkY = y + deltaY | |
// Ensure the coordinates are within the image bounds | |
guard checkX >= 0, checkX < Int(image.size.width), | |
checkY >= 0, checkY < Int(image.size.height) else { | |
continue | |
} | |
// Calculate the pixel index for the surrounding pixel | |
let surroundingPixelIndex = (checkY * bytesPerRow) + (checkX * bytesPerPixel) | |
// Check the alpha component of the pixel | |
let alpha = pixelData?[surroundingPixelIndex + 3] ?? 0 | |
if alpha > 0 { | |
return true // Return true if any surrounding pixel has non-transparent alpha | |
} | |
} | |
} | |
return false // Only register touches for non-transparent pixels in the radius | |
} | |
} | |
} | |
struct GradientOverlayEffect: ViewModifier { | |
var show: Bool | |
func body(content: Content) -> some View { | |
content | |
.overlay { | |
if show { | |
let o = 0.2 | |
LinearGradient( | |
gradient: Gradient(colors: [.red.opacity(o), .orange.opacity(o), .yellow.opacity(o), .green.opacity(o), .blue.opacity(o), .purple.opacity(o), .red.opacity(o),]), | |
startPoint: .topLeading, | |
endPoint: .bottomTrailing | |
) | |
} | |
} | |
} | |
} | |
struct PopEffect: GeometryEffect { | |
var scale: CGFloat | |
var animatableData: CGFloat { | |
get { scale } | |
set { scale = newValue } | |
} | |
func effectValue(size: CGSize) -> ProjectionTransform { | |
let xOffset = size.width * (1 - scale) / 2 | |
let yOffset = size.height * (1 - scale) / 2 | |
return ProjectionTransform(CGAffineTransform(translationX: xOffset, y: yOffset).scaledBy(x: scale, y: scale)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment