Skip to content

Instantly share code, notes, and snippets.

@skhavari
Created December 14, 2024 01:51
Show Gist options
  • Save skhavari/b7fbeaf777b75e9125a8d579f7f1cfce to your computer and use it in GitHub Desktop.
Save skhavari/b7fbeaf777b75e9125a8d579f7f1cfce to your computer and use it in GitHub Desktop.
Ignore taps in transparent pixels of images
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