Quickly access VPN on iOS using App Intents

Hello! My name is Anton Dolganov, I am an iOS developer at Kontur. I'm working on infrastructure modules and maintaining our application at the same time Contour.Connectwhich is used to connect to the internal VPN and gives access to corporate services.

We recently released an update to Connect, adding support for Shortcuts. Now you can turn VPN on and off without even going into the application. In this article I'll explain how I implemented this and why it's useful.

What are quick commands and why are they needed?

Quick commands is an iOS feature that allows you to automate routine actions in different applications. For example, you can create a command that, with one click, will show the weather forecast, get directions to the nearest beach and start your favorite playlist. You can configure these commands to run automatically based on time of day, location, or other conditions.

Commands can be launched not only through the Commands app, but also from the Home screen, widgets, Siri, or even on the Apple Watch. And this is not only convenient – you can share your commands with other users, making their life easier. For example, in this article 35 commands for working on the iPhone are described: from conveniently setting alarms at 5-minute intervals to quickly searching for a link to a song on another streaming platform.

The main benefit of quick commands is time saving and convenience. In the case of our application, now users do not need to go into the application every time and press several buttons to connect to the VPN. You can do this literally with one command or even set up an automatic connection. This solution is especially useful for those who frequently use VPN and want to automate the process.

To add support for quick commands to our Connect, we turned to a new framework App Intents.

What are App Intents?

App Intents is a framework introduced in iOS 16 that allows developers to create commands for their apps through Siri, Spotlight, and the Commands app. It replaces the legacy SiriKit Intents and offers a simpler, more powerful alternative.

Here are the main advantages of App Intents:

  1. Easy integration. Implementing the command requires minimal configuration.

  2. Increased productivity. Simplified framework structure and support for asynchronous tasks.

  3. Flexibility of the user interface. Ability to customize dialogs and user interaction.

  4. Spotlight integration. Commands can appear in Spotlight search results, making them more accessible to the user.

Example implementation of a command to manage VPN

Now let's look at the step-by-step process of how the VPN management team was created. As a first step, let's create a simple command that, as usual, will display a dialog with the text “Hello, world!” This will allow us to understand the basic principles of App Intents.

Step 1: Create a simple command

First of all, we create a structure that inherits from AppIntent. For our command, it is enough to specify the title and implement the method perform()which will display the message “Hello, world!” in the form of a dialog box.

Example code:

import AppIntents

struct VPNIntent: AppIntent {
  static let title: LocalizedStringResource = "Hello, world!"

  func perform() async throws -> some ProvidesDialog {
    return .result(dialog: .init("Hello, world!"))
  }
}

⚠️ If your project supports iOS versions lower than 16, then you will need to add the @available annotation, since this functionality will only run on iOS 16 and higher.

@available(iOS 16, *)
struct VPNIntent: AppIntent {
// Остальной код команды
}

You must compile the project for this intent to be available in the Teams app.

Step 2: Set up a team in the Teams app

Now let's see how to find and run the created command:

  1. Open the “Teams” application on the device.

  1. Click on the “+” button in the upper right corner to create a new team.

  1. We scroll through the curtain with various actions and find our application.

  1. Select the team that we created earlier.

  1. We confirm the creation of the team. It will automatically appear in the Teams app.

  1. The command is now ready to use.

  1. To launch it, just click on the created plate, and you will see the result – a dialogue with the text “Hello, world!”

We'll look at other ways to run a command a little later.

Step 3. Add the ability to turn VPN on and off

We will not go into the details of the implementation of enabling and disabling VPN, but will focus on the current command. In method perform() Let's call a function that will manage the VPN state.

At first glance, you could create two separate commands: one to enable the VPN and another to disable it. However, this approach is inconvenient: the developer will have to duplicate logic, and users will have to switch between two commands, which reduces usability.

The best solution is to create one command that will both enable and disable the VPN depending on its current status.

The principle of operation will be as follows: when you run the command for the first time, it connects to the VPN, and when you run it again, it disconnects. This way the command automatically adapts to the current VPN state.

To do this, change the perform method:

func perform() async throws -> some ProvidesDialog {
    VPNManager.toggleVpnConnection()
    return .result(dialog: .init("Команда выполнена"))
}

Now, when you run a command, the VPN will turn on or off, and the user will also see a dialog with the message “Command completed.”

However, there is one caveat here: the process of connecting to a VPN can take some time, and in the current implementation, a dialog with the result is called without waiting for the process to complete. As a result, the “Command Completed” dialog will appear before the VPN is actually completed.

This is easy to fix with asynchronous programming using async/await, which is supported in the perform method:

func perform() async throws -> some ProvidesDialog {
    await VPNManager.toggleVpnConnection()
    return .result(dialog: .init("Команда выполнена"))
}

⚠️ For this to work correctly, you need to change the method toggleVpnConnection()adding support for async/await.

The “Command Completed” dialog will now only appear after the connection is complete or the VPN is disconnected, providing more accurate feedback to the user.

Step 4. Add a team status display

At the moment, the command displays the result in the form of a dialog with the text “Command completed,” but this solution is not informative enough. It would be better to display different information depending on whether the VPN is enabled or disabled. It is also useful to provide an error message, for example, if there is no Internet connection.

The AppIntents framework allows you to create custom Views for more flexible display of command results. Let's realize this opportunity.

First, let's prepare an enumeration that will describe the possible VPN states: active, inactive, and error state. We will also set the corresponding message, icon and color for each state.

import SwiftUI

enum VPNIntentState {
  case inactive
  case active
  case error(VPNConfigurationError)
  var message: String {
    switch self {
    case .inactive:
      "Не подключено"
    case .active:
       "Подключено"
    case .error(let error):
      error.message
    }
  }
  var icon: String {
    switch self {
    case .inactive:
      "xmark.shield"
    case .active:
      "checkmark.shield"
    case .error:
      "exclamationmark.triangle"
    }
  }
  var color: Color {
    switch self {
    case .inactive:
      .gray
    case .active:
      .green
    case .error:
      .red
    }
  }
}

Now let's create a custom View using SwiftUI to display the VPN status:

import SwiftUI

struct VPNStatusView: View {
  let state: VPNIntentState

  var body: some View {
    VStack(spacing: 16) {
      Image(systemName: state.icon)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: 68, height: 68)
        .foregroundColor(state.color)
      Text(state.message)
        .foregroundColor(.primary)
        .multilineTextAlignment(.center)
    }
    .padding()
  }
}

All that remains is to refine the method perform()so that he returns ShowsSnippetView. Let's also change the method toggleVpnConnectionwhich will now return the current VPN state:

func perform() async throws -> some ShowsSnippetView {
    let result = await VPNManager.toggleVpnConnection()
    return .result(view: VPNStatusView(state: result))
}

As a result, after executing the command, more detailed information about the current VPN status will be displayed on the screen.

VPN connected successfully

VPN successfully disabled

VPN connection error

Messages about connection, disconnection or error will significantly increase the information content and usability of the command.

Step 5: Restrict access to the command for users

Our task is to make sure that only authorized users of the application can use the command. Unfortunately, there is currently no built-in mechanism to hide or show a command depending on various conditions. Therefore, the check will have to be implemented within the command itself and an error message will be displayed.

We'll add a condition that checks if the current user is eligible to use the VPN. If access is denied, the command will return an error status.

Example implementation:

func perform() async throws -> some ShowsSnippetView {
    guard VPNManager.isVPNAllowed
    else {
      return .result(
        view: VPNStatusView(
          state: VPNIntentState.error(.configurationIsNotAvailable)
        )
      )
    }
    let result = await appDelegate.toggleVpnConnection()
    return .result(view: VPNStatusView(state: result))
}

If the user is not allowed to use VPN, the command will return an error message. Otherwise, the VPN will connect or disconnect.

Step 6. Improving the command description

At the final stage, we will prepare a description of the command for users. This description will be displayed in the application interface.

We will prepare the title and description of our team. These strings can be placed in the Localizable.strings file for convenient application localization.

"vpn_intent_title" = "Коннект VPN";
"vpn_intent_description" = "Команда предназначена для удобного включения и выключения VPN на вашем устройстве.";

Now let's update the command description in the code:

struct VPNIntent: AppIntent {
  static let title: LocalizedStringResource = "vpn_intent_title"
  static var description: IntentDescription? { "vpn_intent_description" }
  // Остальной код команды
}

This way, the command will have a clear and informative description that users will see when setting up and using the command. This will help make the interface more understandable and convenient.

Ways to run a command

Further, under the catas there are hidden instructions for launching the command in various ways. We wrote them to present a new feature to our users. If you have other interesting automation scenarios, please share them in the comments.

Control Center

iOS 18 brings a significant update to the Control Center. With the new features, users can now add new controls, including a VPN control command.

To do this:

  1. Go to edit mode and click on “Add Control”.

  1. Find a shortcut in the list.

  1. Select the team we created earlier.

  1. Ready. Now you can run the command directly from the Control Panel.

Logo on the desktop

After creating a command, you can also add it to your device's home screen for quick access.

To do this:

1. Click on the three dots in the corner of the command.

2. Then expand the menu and select “Go to Home Screen”.

3. If you wish, you can customize the logo as you want: you can select an icon, set the background color and name for the team. Then click “Add”.

4. Now you can turn VPN on or off in just one click right from your desktop.

Widget on the lock screen

Sometimes the desktop can be difficult to navigate due to the multitude of applications. To make this process easier, you can place a command on the lock screen using widgets.

To do this:

1. Go to lock screen editing mode by holding it down. Click “Customize”.

2. Select Add Widgets and find the Teams app.

3. Select the VPN Connect command that you created earlier.

4. Click “Done” to save your changes. Now you can run the command on the lock screen.

Action Button

If you are the proud owner of an iPhone 15 Pro, 15 Pro Max, or another current model, then you have the opportunity to assign a command to the action button.

To do this:

1. Go to Settings on your device and select Action Button.

2. Select the Shortcut option and assign an action to manage the VPN.

Now, when you press the “Action Button”, you can control the VPN while on any screen.

Apple Watch

Apple Watch users also have the option to control the VPN from their wrist.

To do this:

1. Go to the team settings and click on the ⓘ sign.

2. Turn on Display on Apple Watch and tap Done.

3. For the command to appear on your Apple Watch, you need to make sure you have the Teams and Watch apps synced to iCloud.

4. You can now find the Teams app on your Apple Watch.

5. And manage VPN directly from the watch.

Automation

Automation allows you to configure the VPN to automatically turn on, for example, during business hours or when you open a browser. Let's look at how to automate the process so that the VPN turns on when you start the browser and turns off when you close it.

To do this, follow these steps:

1. Open the Teams app and go to the Automation tab.

2. Click New Automation.

3. Select “Application” from the list.

4. Specify the desired application, for example, your main browser. Next, set the automation options: check “open” to start the VPN when you open the application, and “closed” to turn it off when you close it. And for instant action, check the box next to “Run immediately”, then click “Next”.

5. Select a command that will be executed based on the specified conditions.

6. Done! Now the process of turning VPN on and off is automated.

In the end

The iOS shortcuts feature opens up a ton of automation possibilities. And with the help of App Intents, we made the process of connecting to a VPN in Kontur.Connect faster and easier. This is a small but important detail that can significantly improve the user experience.

So if you're an app developer and haven't used App Intents yet, give it a try! Here is a list of resources that helped me a lot when adding new functionality to the application:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *