Быстрый доступ к VPN в iOS с помощью App Intents

ae2cc53b3b40b204b9efa159be62fb85.png

Привет! Меня зовут Антон Долганов, я iOS-разработчик в компании Контур. Я работаю над инфраструктурными модулями и параллельно поддерживаю наше приложение Контур.Коннект, которое используется для подключения к внутреннему VPN и даёт доступ к корпоративным сервисам.

Недавно мы выпустили обновление Коннекта, добавив поддержку Быстрых команд (Shortcuts). Теперь можно включать и отключать VPN, даже не заходя в приложение. В этой статье я расскажу, как я это реализовал и почему это полезно.

Что такое быстрые команды и зачем они нужны?

Быстрые команды — это функция iOS, которая позволяет автоматизировать рутинные действия в разных приложениях. Например, вы можете создать команду, которая за одно нажатие покажет прогноз погоды, проложит маршрут до ближайшего пляжа и запустит ваш любимый плейлист. Можно настроить автоматическое выполнение таких команд по времени суток, местоположению или другим условиям.

Команды можно запускать не только через приложение «Команды», но и с главного экрана, через виджеты, Siri или даже на Apple Watch. И это не только удобно — своими командами можно делиться с другими пользователями, упрощая им жизнь. Например, в этой статье описаны 35 команд для работы на iPhone: от удобной настройки будильников с 5-ти минутным интервалом до быстрого поиска ссылки на песню на другой стриминговой платформе.

Главная польза быстрых команд — экономия времени и удобство. В случае с нашим приложением, теперь пользователям не нужно каждый раз заходить в приложение и нажимать несколько кнопок, чтобы подключиться к VPN. Можно сделать это буквально одной командой или даже настроить автоматическое подключение. Такое решение особенно полезно для тех, кто часто использует VPN и хочет автоматизировать процесс.

Чтобы добавить в наш Коннект поддержку быстрых команд, мы обратились к новому фреймворку App Intents.

Что такое App Intents?

App Intents — это представленный в iOS 16 фреймворк, который позволяет разработчикам создавать команды для работы с их приложениями через Siri, Spotlight и приложение «Команды». Он заменяет устаревший SiriKit Intents и предлагает более простую и производительную альтернативу.

Вот основные плюсы App Intents:

  1. Простота интеграции. Внедрение команды требует минимальной настройки.

  2. Повышенная производительность. Упрощенная структура фреймворка и поддержка асинхронных задач.

  3. Гибкость пользовательского интерфейса. Возможность кастомизации диалогов и взаимодействия с пользователем.

  4. Интеграция с Spotlight. Команды могут отображаться в результатах поиска Spotlight, что делает их более доступными для пользователя.

Пример реализации команды для управления VPN

Теперь рассмотрим поэтапный процесс, как была создана команда для управления VPN. В качестве первого шага создадим простую команду, которая по традиции выведет диалог с текстом «Hello, world!». Это позволит нам разобраться с основными принципами работы App Intents.

Шаг 1: Создание простой команды

Первым делом создаем структуру, которая наследуется от AppIntent. Для нашей команды достаточно указать заголовок (title) и реализовать метод perform(), который будет выводить сообщение «Hello, world!» в виде диалогового окна.

Пример кода:

import AppIntents

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

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

⚠️ Если ваш проект поддерживает версии iOS ниже 16, то необходимо будет добавить аннотацию @available, так как данный функционал будет выполняться только на iOS 16 и выше.

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

Необходимо скомпилировать проект, чтобы этот интент стал доступен в приложении «Команды».

Шаг 2: Настройка команды в приложении «Команды»

Теперь давайте посмотрим, как найти и запустить созданную команду:

  1. Открываем приложение «Команды» на устройстве.

a1d11265749c27f983927cc5cb4edc2d.png

  1. Нажимаем на кнопку »+» в правом верхнем углу для создания новой команды.

6024fa231083a4c90fcbadb5019c4f3c.png

  1. Пролистываем шторку с различными действиями и находим наше приложение.

1569648829d0c3748d270dcdec140585.png

  1. Выбираем команду, которую мы создали ранее.

ec0a9e7c4b3452a91e0f7ffeeea5a320.png

  1. Подтверждаем создание команды. Она автоматически появится в приложении «Команды».

dd467424b3f6f6fa29b2963ebbab5ea4.png

  1. Теперь команда готова к использованию. 

82afef3f66ff69fe8894fafa19f328a2.png

  1. Для её запуска достаточно нажать на созданную плашку, и вы увидите результат — диалог с текстом «Hello, world!»

863c1063ebbe47940627bfe0890cfc74.png

Чуть позже мы рассмотрим другие способы запуска команды.

Шаг 3. Добавляем возможность включать и выключать VPN

Мы не будем вдаваться в детали реализации включения и отключения VPN, а сосредоточимся на текущей команде. В методе perform() вызовем функцию, которая будет управлять состоянием VPN.

На первый взгляд, можно было бы создать две отдельные команды: одну для включения VPN и другую для отключения. Однако такой подход неудобен: разработчику придется дублировать логику, а пользователям — переключаться между двумя командами, что снижает удобство использования.

Лучшее решение — создать одну команду, которая будет выполнять как включение, так и отключение VPN в зависимости от его текущего статуса.

Принцип работы будет следующим: при первом запуске команды происходит подключение к VPN, а при повторном — отключение. Таким образом, команда автоматически адаптируется к текущему состоянию VPN.

Для этого изменим метод perform:

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

Теперь при запуске команды, VPN будет включаться или выключаться, а также пользователю отобразится диалог с сообщением «Команда выполнена».

Однако здесь есть один нюанс: процесс подключения к VPN может занимать некоторое время, а в текущей реализации диалог с результатом вызывается без ожидания завершения процесса. В результате диалог «Команда выполнена» появится до фактического завершения работы с VPN.

Это легко исправить с помощью асинхронного программирования с использованием async/await, которое поддерживается в методе perform:

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

⚠️ Чтобы это работало корректно, необходимо изменить метод toggleVpnConnection(), добавив поддержку async/await. 

Теперь диалог «Команда выполнена» будет отображаться только после завершения подключения или отключения VPN, что обеспечит более точную обратную связь для пользователя.

Шаг 4. Добавляем отображение статуса команды

На данный момент команда выводит результат в виде диалога с текстом «Команда выполнена», но это решение недостаточно информативно. Было бы лучше отображать различную информацию в зависимости от того, включен VPN или отключён. Также полезно предусмотреть вывод сообщения об ошибке, например, в случае отсутствия подключения к интернету.

Фреймворк AppIntents позволяет создавать кастомные представления (View) для более гибкого отображения результата команды. Давайте реализуем такую возможность.

Для начала подготовим перечисление, которое будет описывать возможные состояния VPN: активное, неактивное и состояние с ошибкой. Также для каждого состояния зададим соответствующее сообщение, иконку и цвет.

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
    }
  }
}

Теперь создадим кастомное представление (View) с помощью SwiftUI для отображения статуса VPN:

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()
  }
}

Осталось только доработать метод perform(), чтобы он возвращал ShowsSnippetView. Также изменим метод toggleVpnConnection, который теперь будет возвращать текущее состояние VPN:

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

В результате, после выполнения команды, на экране будет отображаться более подробная информация о текущем статусе VPN. 

VPN успешно подключён

6f3f5802992e3d04c72ed925c68b73f6.png

VPN успешно отключён

924b488d0871920c0759b0bc5b58499a.png

Ошибка при подключении VPN

3af1438a04dc3e89469e29010decb268.png

Сообщения о подключении, отключении или ошибке значительно повысят информативность и удобство использования команды.

Шаг 5. Ограничение доступа к команде для пользователей

Наша задача — сделать так, чтобы командой могли воспользоваться только авторизованные пользователи приложения. К сожалению, на данный момент нет встроенного механизма, позволяющего скрывать или отображать команду в зависимости от различных условий. Поэтому проверку придется реализовать внутри самой команды и выводить сообщение об ошибке.

Мы добавим условие, которое проверяет, имеет ли текущий пользователь право на использование VPN. Если доступ запрещен, команда вернет статус ошибки.

Пример реализации:

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))
}

Если пользователю не разрешено использовать VPN, команда вернет сообщение об ошибке. В противном случае будет выполнено подключение или отключение VPN.

Шаг 6. Улучшаем описание команды

На завершающем этапе подготовим описание команды для пользователей. Это описание будет отображаться в интерфейсе приложения.

Мы подготовим title и description нашей команды. Эти строки можно вынести в файл Localizable.strings для удобной локализации приложения.

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

Теперь обновим описание команды в коде:

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

Таким образом, команда будет иметь четкое и информативное описание, которое пользователи увидят при настройке и использовании команды. Это поможет сделать интерфейс более понятным и удобным.

77a27b8212029fa5716d0cc9a677b7ae.png

Способы запуска команды

Далее под катами скрыты инструкции запуска команды различными способами. Мы их писали для презентации новой фичи нашим пользователям. Если у вас есть другие интересные сценарии для автоматизации, поделитесь, пожалуйста, в комментариях.

Control Center

В iOS 18 появилось значительное обновление панели управления (Control Center). С новыми возможностями пользователи теперь могут добавлять новые элементы управления, включая команду управления VPN.

Для этого:  

  1. Перейдите в режим редактирования и нажмите на «Добавить элемент управления».

c158a401010f6f54ede71b4aede5c4dc.png
  1. Найдите в списке быструю команду.

99bf0c97004d8003338dd44207a24878.png
  1. Выберите команду, которую мы создали ранее.

99888b9a86c2be267462a3554704b40e.png
  1. Готово. Теперь можно запускать команду прямо из панели управления.

482c3e3a3a39d16b27b83f51460dcf01.png

Лого на рабочем столе

После создания команды, вы также можете добавить её на главный экран вашего устройства для быстрого доступа. 

Для этого:  

1. Нажмите на три точки в углу команды. 

971c3b1af42ad1c23ccf2b5d9e4cc08d.png

2. Затем раскройте меню и выберите «На экран домой».

050945eb2afdf65be4e741fc59c0dab6.png

3. По желанию вы можете кастомизировать лого, как вам захочется: можно выбрать иконку, задать цвет фона и имя для команды. После чего нажмите «Добавить».

6e47fafa397c2a20f3bea518530492a8.png

4. Теперь вы можете включать или выключать VPN всего в один клик прямо с рабочего стола.

ceefd66143b8ab964d664c139839eb20.png

Виджет на экране блокировки

Порой на рабочем столе бывает трудно сориентироваться из-за множества приложений. Чтобы упростить этот процесс, можно разместить команду на экране блокировки с помощью виджетов.

Для этого:   

1. Перейдите в режим редактирования экрана блокировки, зажав его. Нажмите «Настроить». 

d6507da67d59feec156d30148f63f2a1.png

2. Выберите «Добавить виджеты» и найдите приложение «Команды».    

acd06702e73914110a6e227e9cfc1d28.png

3. Выберите команду «Коннект VPN», которую вы создали ранее.   

57f5c43ad3ede7412c438d9b803a1155.png

4. Нажмите «Готово», чтобы сохранить изменения. Теперь вы сможете запускать команду на экране блокировки.  

bc67231c43dad51860d63d8aa1f67517.png

Action Button

Если вы счастливый обладатель iPhone 15 Pro, 15 Pro Max или другой последней актуальной модели, то у вас есть возможность назначить команду на кнопку действия. 

Для этого:  

1. Перейдите в «Настройки» вашего устройства и выберите пункт «Кнопка действия». 

1b49d140cf2defb79984ab57c1cd8b87.png

2. Выберите опцию «Быстрая команда» и назначьте действие для управления VPN.  

25c3ea029f092581599c1ace09f65e7b.png

Теперь при нажатии «Action Button» , вы сможете управлять VPN, находясь при этом на любом экране.

Apple Watch

Для пользователей Apple Watch также есть возможность управления VPN с запястья. 

Для этого:  

1. Перейдите в настройки команды и нажмите на знак ⓘ.

967c71e1776922d1261f474aca9327cf.png

2. Включите настройку «Отображение на Apple Watch» и нажмите  «Готово».

d331b423eab0e6a0097617544fe27555.png

3. Чтобы команда появилась на ваших Apple Watch, необходимо убедиться, что у вас включена синхронизация приложений «Команды» и «Watch» в iCloud.

3c7f34b574719a3363072f6b340820af.png

4. Теперь можете найти на ваших Apple Watch приложение «Команды».

f077b70145a00a4a6bf14380a614ec25.png

5. И управлять VPN прямо с часов.

43ec7d6df5e4ade5cf26584f33d56f23.png

Автоматизация

Автоматизация позволяет настроить автоматическое включение VPN, например, в рабочие часы или при открытии браузера. Рассмотрим, как автоматизировать процесс так, чтобы VPN включался при запуске браузера и выключался при его закрытии.

Для этого выполните следующие шаги:

1. Откройте приложение «Команды» и перейдите на вкладку «Автоматизация».

6f366e36adb273d994a63e26e841e5c9.png

2. Нажмите «Новая автоматизация».

715daa3f94ea5b13e515dd5e852b0532.png

3. В списке выберите «Приложение».

e267e5ed4a29fbff01af1ecd576ba4a9.png

4. Укажите нужное приложение, например, ваш основной браузер. Далее установите параметры автоматизации: отметьте «открыто» для запуска VPN при открытии приложения и «закрыто» для его выключения при закрытии. А для мгновенного действия поставьте галочку напротив «Немедленный запуск», затем нажмите «Далее».

8bf6ee9ddce672ba633fcf265b098a84.png

5. Выберите команду, которая будет выполняться по указанным условиям.

eeb2ac553c265e9edf91a30717ae22e9.png

6. Готово! Теперь процесс включения и выключения VPN автоматизирован.

f3973d0d121b37aa7188d0abf0080c78.png

В итоге

Функция быстрых команд в iOS открывает массу возможностей для автоматизации. И с помощью App Intents мы сделали процесс подключения к VPN в Контур.Коннект быстрее и проще. Это маленькая, но важная деталь, которая может заметно улучшить пользовательский опыт.

Так что если вы разрабатываете приложения и ещё не использовали App Intents, обязательно попробуйте! Вот список ресурсов, которые мне очень помогли при добавлении нового функционала в приложение:

© Habrahabr.ru