We discussed the previous post about using a dialogue box based pop-up, while this particular pop-up method is very irritating, if you are not on the correct network, it does not look as professional as a full-screen blocking interface that prevents app usage until users connect to approved corporate networks.
Visual : Full Screen Block
Full Screen Block
When I set out to implement corporate network validation for iOS applications, I needed to decide how to handle unauthorized access attempts. I chose to implement a full-screen blocking (unlike the last post which was dialogue based blocking) interface for several reasons:
- Complete visual control - The interface takes over the entire screen, making the security requirement immediately clear
- Professional appearance - A custom-designed screen provides a polished, branded experience
- Comprehensive instructions - Full-screen space allows detailed, step-by-step guidance for users
- Clear severity communication - The design immediately conveys this is a security requirement, not a casual notification
The solution I developed uses a full-screen SwiftUI view that completely blocks app access until network validation passes.
Designing the Full-Screen Blocking Interface
The blocking interface completely replaces the app's normal interface when network validation fails. Users cannot access any app functionality until they connect to an approved corporate network.
Creating the Blocking View
I created a new file called NetworkBlockedView.swift
that contains a purpose-built SwiftUI interface. The view uses a red gradient background to immediately communicate that access is restricted:
struct NetworkBlockedView: View {
let onRetry: () -> Void
let onQuit: () -> Void
var body: some View {
ZStack {
LinearGradient(
gradient: Gradient(colors: [
Color(red: 0.8, green: 0.2, blue: 0.2),
Color(red: 0.9, green: 0.3, blue: 0.3),
Color(red: 0.7, green: 0.1, blue: 0.1)
]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
// Content goes here
}
}
}
The view accepts two closures as parameters: onRetry
and onQuit
. This design pattern allows the calling code to define what happens when users tap each button, making the component reusable across different contexts.
Visual Hierarchy
The blocking screen includes several key components arranged vertically:
Warning Icon: A large, prominent icon immediately draws attention:
ZStack {
Circle()
.fill(Color.white.opacity(0.2))
.frame(width: 120, height: 120)
Image(systemName: "wifi.exclamationmark")
.font(.system(size: 50, weight: .medium))
.foregroundColor(.white)
}
Clear Messaging: The title and description explain the situation without technical jargon:
VStack(spacing: 24) {
Text("Corporate Connection Required")
.font(.system(size: 32, weight: .bold, design: .rounded))
.foregroundColor(.white)
.multilineTextAlignment(.center)
Text("This application requires a secure connection to the corporate network.")
.font(.system(size: 18, weight: .medium))
.foregroundColor(.white.opacity(0.8))
.multilineTextAlignment(.center)
}
Step-by-Step Instructions: I created a reusable component to display numbered instructions:
struct InstructionRow: View {
let icon: String
let text: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
Image(systemName: icon)
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)
.frame(width: 24)
Text(text)
.font(.system(size: 15, weight: .medium))
.foregroundColor(.white.opacity(0.9))
.multilineTextAlignment(.leading)
}
}
}
This component displays instructions like "Connect to corporate WiFi" and "Activate your VPN (Twingate)" in a clear, scannable format.
Action Buttons
The interface provides two distinct buttons with different visual weights:
Button(action: onRetry) {
HStack {
Image(systemName: "arrow.clockwise")
Text("Retry Connection")
}
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.frame(height: 54)
.background(
LinearGradient(
gradient: Gradient(colors: [
Color.blue.opacity(0.8),
Color.blue
]),
startPoint: .top,
endPoint: .bottom
)
)
.cornerRadius(12)
}
Button(action: onQuit) {
HStack {
Image(systemName: "xmark.circle")
Text("Exit Application")
}
.foregroundColor(.white.opacity(0.8))
.frame(maxWidth: .infinity)
.frame(height: 44)
.background(Color.white.opacity(0.1))
.cornerRadius(10)
}
The "Retry Connection" button uses a prominent blue gradient, while the "Exit Application" button has a more subtle appearance, guiding users toward the preferred action.
Integrating with Network Validation
The next step was connecting this full-screen interface to the network validation logic through the CorporateNetworkValidator
.
Blocked Presentation Logic
The validator presents the blocking screen when network validation fails:
private func showNetworkBlockedScreen() {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else { return }
let blockedView = NetworkBlockedView(
onRetry: {
CorporateNetworkValidator.validateAndBlockIfNeeded()
},
onQuit: {
exit(0)
}
)
let hostingController = UIHostingController(rootView: blockedView)
hostingController.modalPresentationStyle = .fullScreen
hostingController.modalTransitionStyle = .crossDissolve
window.rootViewController?.present(hostingController, animated: true)
}
This approach uses UIHostingController
to wrap the SwiftUI view and present it as a full-screen modal. The .fullScreen
presentation style is crucial—it ensures users cannot dismiss the view through standard gestures.
Application Flow
When network validation fails, the sequence of events is:
- Validation detects unauthorized network → Calls
showNetworkBlockedScreen()
- Creates NetworkBlockedView → Passes retry and quit closures
- Wraps in UIHostingController → Enables presentation in UIKit environment
- Presents full-screen modal → Completely blocks underlying interface
- User taps Retry → Re-runs validation check
- If validation passes → Modal dismisses automatically
- User taps Quit → Application exits cleanly
Implementation
To implement this system in your application, you need three main files working together.
File 1: NetworkBlockedView.swift
Create a new Swift file with the complete blocking interface:
import SwiftUI
struct NetworkBlockedView: View {
let onRetry: () -> Void
let onQuit: () -> Void
var body: some View {
ZStack {
// Background gradient
LinearGradient(
gradient: Gradient(colors: [
Color(red: 0.8, green: 0.2, blue: 0.2),
Color(red: 0.9, green: 0.3, blue: 0.3),
Color(red: 0.7, green: 0.1, blue: 0.1)
]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
ScrollView {
VStack(spacing: 40) {
Spacer(minLength: 60)
// Warning Icon
ZStack {
Circle()
.fill(Color.white.opacity(0.2))
.frame(width: 120, height: 120)
Image(systemName: "wifi.exclamationmark")
.font(.system(size: 50, weight: .medium))
.foregroundColor(.white)
}
// Title and Message
VStack(spacing: 24) {
Text("Corporate Connection Required")
.font(.system(size: 32, weight: .bold, design: .rounded))
.foregroundColor(.white)
.multilineTextAlignment(.center)
Text("This application requires a secure connection to the corporate network.")
.font(.system(size: 18, weight: .medium))
.foregroundColor(.white.opacity(0.8))
.multilineTextAlignment(.center)
}
// Instructions
VStack(spacing: 20) {
Text("To continue, please:")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white.opacity(0.9))
VStack(alignment: .leading, spacing: 12) {
InstructionRow(
icon: "1.circle.fill",
text: "Connect to your corporate WiFi network"
)
InstructionRow(
icon: "2.circle.fill",
text: "Or activate your corporate VPN (Twingate)"
)
InstructionRow(
icon: "3.circle.fill",
text: "Ensure your connection is active and configured"
)
}
}
// Action Buttons
VStack(spacing: 16) {
Button(action: onRetry) {
HStack {
Image(systemName: "arrow.clockwise")
Text("Retry Connection")
}
.frame(maxWidth: .infinity)
.frame(height: 54)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
}
Button(action: onQuit) {
HStack {
Image(systemName: "xmark.circle")
Text("Exit Application")
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.foregroundColor(.white.opacity(0.8))
}
}
Text("Contact IT Support if issues persist")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.6))
}
}
}
}
}
struct InstructionRow: View {
let icon: String
let text: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
Image(systemName: icon)
.foregroundColor(.white)
Text(text)
.foregroundColor(.white.opacity(0.9))
}
}
}
File 2: CorporateNetworkValidator.swift
The validator handles IP checking and presents the blocking screen:
import Foundation
import UIKit
import SwiftUI
class CorporateNetworkValidator {
// REPLACE THESE WITH YOUR ACTUAL CORPORATE IP RANGES
private let corporateIPRanges = [
"203.0.113", // Example: 203.0.113.x
"198.51.100", // Example: 198.51.100.x
"10.0.0", // Example: 10.0.0.x
"172.16" // Example: 172.16.x.x
]
static func validateAndBlockIfNeeded() {
let validator = CorporateNetworkValidator()
Task {
let isOnCorporateNetwork = await validator.validateCorporateConnection()
await MainActor.run {
if !isOnCorporateNetwork {
validator.showNetworkBlockedScreen()
}
}
}
}
func validateCorporateConnection() async -> Bool {
guard let publicIP = await getPublicIPAddress() else {
return false
}
print("Current IP: \(publicIP)")
return isIPInCorporateRange(ip: publicIP)
}
private func getPublicIPAddress() async -> String? {
do {
let url = URL(string: "https://api.ipify.org")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
} catch {
print("Failed to get IP address: \(error)")
return nil
}
}
private func isIPInCorporateRange(ip: String) -> Bool {
for range in corporateIPRanges {
if ip.hasPrefix(range) {
return true
}
}
return false
}
private func showNetworkBlockedScreen() {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else { return }
let blockedView = NetworkBlockedView(
onRetry: {
CorporateNetworkValidator.validateAndBlockIfNeeded()
},
onQuit: {
exit(0)
}
)
let hostingController = UIHostingController(rootView: blockedView)
hostingController.modalPresentationStyle = .fullScreen
hostingController.modalTransitionStyle = .crossDissolve
window.rootViewController?.present(hostingController, animated: true)
}
}
File 3: AppDelegate Integration
Add the validation check to your existing AppDelegate:
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Your existing code stays here
// Add corporate network validation
CorporateNetworkValidator.validateAndBlockIfNeeded()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Your existing code stays here
// Check when app becomes active
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
}
Adding to Existing Applications
If you're integrating this into an existing production app, follow these steps carefully to avoid disrupting current functionality.
Step 1: Add NetworkBlockedView.swift
Create a new Swift file in your project called NetworkBlockedView.swift
. Copy the complete view implementation from File 1 above. This file is completely self-contained and doesn't modify any existing code.
Step 2: Add CorporateNetworkValidator.swift
Create another new Swift file called CorporateNetworkValidator.swift
. Copy the complete validator implementation from File 2 above.
Critical: Update the corporateIPRanges
array with your actual corporate IP address prefixes before deploying.
Step 3: Integrate with Your App Lifecycle
For UIKit Apps: Add the validation calls to your existing AppDelegate.swift
:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// All your existing code remains unchanged
// Add this single line at the end
CorporateNetworkValidator.validateAndBlockIfNeeded()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// All your existing code remains unchanged
// Add this single line
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
For SwiftUI Apps:
Modify your main App structure:
@main
struct YourExistingApp: App {
var body: some Scene {
WindowGroup {
YourExistingContentView()
.onAppear {
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
}
}
}
Step 4: Configure Your IP Ranges
The most critical configuration step is setting your corporate IP ranges. To find these:
- Connect to your corporate network or VPN
- Visit
https://api.ipify.org
in a browser - Note the displayed IP address (e.g.,
203.145.67.123
) - Add the prefix to your array (e.g.,
"203.145.67"
)
You can add multiple ranges if your organization has multiple network exit points.
Conclusion
Implementing a full-screen blocking interface for corporate network validation provides both robust security and excellent user experience. The approach completely prevents unauthorized access while providing clear guidance to help users resolve connectivity issues independently.
The implementation integrates cleanly into existing applications with minimal code changes. By using SwiftUI for the blocking interface and UIHostingController to bridge into UIKit presentation, the solution works seamlessly across different app architectures