BaptisteSansierra/NoFlyZone
A SwiftUI modifier that creates an overlay to block user interactions everywhere except in specific authorized zones.
Features
- ✅ Block all screen interactions with a transparent overlay
- ✅ Define multiple authorized zones where interactions are still allowed
- ✅ Get callbacks when users tap inside or outside authorized zones
- ✅ Debug mode with visual zone highlighting
- ✅ Lightweight and easy to integrate
Installation
Swift Package Manager
Add NoFlyZone to your project via Xcode:
- File → Add Package Dependencies
- Enter package URL:
https://github.com/BaptisteSansierra/NoFlyZone - Select version and add to your target
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/BaptisteSansierra/NoFlyZone", from: "1.0.0")
]Usage
Basic Example
import SwiftUI
import NoFlyZone
struct ContentView: View {
@State private var isBlocking = false
@State private var zone: CGRect = .zero
var body: some View {
VStack {
Text("Swipe row to reveal delete")
// Define some zone here
}
.noFlyZone(
enabled: isBlocking,
authorizedZones: [
NoFlyZoneData(id: 0, zone: deleteButtonFrame)
],
onAllowed: { zones in
// User tapped inside an authorized zone
print("Tapped: \(zones.first?.id ?? "unknown")")
handleDelete()
},
onBlocked: {
// User tapped outside authorized zones
isBlocking = false
}
)
}
}Swipe-to-Delete Pattern
struct SwipeToDeleteExample: View {
@State private var items = ["Item 1", "Item 2", "Item 3"]
@State private var revealedIndex: Int? = nil
@State private var deleteButtonFrame: CGRect = .zero
var body: some View {
List {
ForEach(items.indices, id: \.self) { index in
ZStack(alignment: .trailing) {
// Delete button (hidden behind)
Button(action: {
deleteItem(at: index)
}) {
Image(systemName: "trash")
.foregroundColor(.white)
.frame(width: 80, height: 60)
.background(Color.red)
}
.background(
GeometryReader { geo in
Color.clear.onChange(of: revealedIndex) { _, _ in
if revealedIndex == index {
deleteButtonFrame = geo.frame(in: .global)
}
}
}
)
// Row content
Text(items[index])
.frame(maxWidth: .infinity, height: 60)
.background(Color.white)
.offset(x: revealedIndex == index ? -80 : 0)
.gesture(
DragGesture()
.onEnded { value in
if value.translation.width < -50 {
revealedIndex = index
}
}
)
}
}
}
.noFlyZone(
enabled: revealedIndex != nil,
authorizedZones: [
NoFlyZoneData(id: "delete", zone: deleteButtonFrame)
],
onAllowed: { _ in
// Delete button was tapped - handled by button itself
},
onBlocked: {
withAnimation {
revealedIndex = nil
}
}
)
}
private func deleteItem(at index: Int) {
withAnimation {
items.remove(at: index)
revealedIndex = nil
}
}
}Debug Mode
Enable visual debugging to see authorized zones highlighted in green:
.noFlyZone(
enabled: true,
authorizedZones: zones,
onAllowed: { _ in },
onBlocked: { },
coloredDebugOverlay: true // Shows red overlay and green zones
)API Reference
NoFlyZoneData
Defines an authorized zone where interactions are allowed.
public struct NoFlyZoneData: Identifiable, Equatable {
public let viewId: Int
public let itemId: Int
public let zone: CGRect
}viewId: Unique identifier for the child viewitemId: Unique identifier for the zonezone: The CGRect frame in global coordinates
noFlyZone Modifier
func noFlyZone(
enabled: Bool,
authorizedZones: [NoFlyZoneData],
onAllowed: @escaping ([NoFlyZoneData]) -> Void,
onBlocked: @escaping () -> Void,
coloredDebugOverlay: Bool = false
) -> some ViewParameters:
enabled: Whether the no-fly zone overlay is activeauthorizedZones: Array of zones where taps are still allowedonAllowed: Callback with tapped zones when user interacts inside an authorized zoneonBlocked: Callback when user interacts outside all authorized zonescoloredDebugOverlay: Show visual debugging (red overlay, green zones)
How It Works
- When enabled, NoFlyZone places a transparent overlay over your entire view
- The overlay blocks all touch events and gestures
- You define authorized zones using
NoFlyZoneDatawith global CGRect coordinates - When users tap inside an authorized zone,
onAllowedis called with the zone data - When users tap outside all zones (or drag),
onBlockedis called - You handle the callbacks to update your UI state accordingly
Requirements
- iOS 17.0+
- macOS 14.0+
- Swift 5.9+
Example Project
Check out the Example folder for a complete demo app showing various use cases
License
NoFlyZone is available under the MIT license. See the LICENSE file for more info.
⭐️ If you find NoFlyZone useful, please give it a star on GitHub!
Package Metadata
Repository: BaptisteSansierra/NoFlyZone
Stars: 1
Forks: 0
Open issues: 0
Default branch: main
Primary language: swift
License: MIT
Topics: gesture, interaction, interaction-blocker, ios, spm, swift, swiftui
README: README.md