Глава 1.5: iOS WKWebView: Особенности и ограничения

UIWebView vs WKWebView, ограничения памяти, SFSafariViewController и специфичные баги iOS

Глава 1.5 • Часть 1 из 8

iOS WKWebView: Особенности и ограничения

iOS WebView — это более предсказуемая, но более ограничительная среда по сравнению с Android. Здесь мы разберём все нюансы работы с WKWebView.

1 UIWebView vs WKWebView: Эволюция

Исторический контекст: Apple использовала два разных WebView-компонента за свою историю. Понимание различий между ними критически важно, так как многие баги и ограничения связаны с переходом между ними.

📱
UIWebView
iOS 2.0 — iOS 12
  • Первый WebView-компонент Apple
  • Работал в том же процессе, что и приложение
  • Медленный JavaScript (SquirrelFish)
  • Утечки памяти при длительной работе
  • Ограниченная безопасность
  • Запрещён в App Store с 2020 года
🚀
WKWebView
iOS 8 — настоящее время
  • Современный WebView-компонент
  • Отдельный процесс (Nitro JavaScript engine)
  • Быстрый JavaScript (до 3x быстрее)
  • Улучшенная безопасность
  • Лучшая производительность памяти
  • Единственный разрешённый в App Store

Ключевые различия:

Критерий UIWebView WKWebView
Архитектура Один процесс с приложением Отдельный процесс (out-of-process)
JavaScript Engine SquirrelFish (медленный) Nitro (быстрый, JIT-компиляция)
Производительность Низкая Высокая (до 3x быстрее)
Память Утечки при длительной работе Лучшее управление памятью
Безопасность Ограниченная Строгая (Content Security Policy)
Доступ к файлам Прямой доступ Только через loadFileURL
Cookies Общие с Safari Изолированные (с iOS 11+)
Статус в App Store ❌ Запрещён (с 2020) ✅ Разрешён
⚠️ Важно для QA

Если вы нашли баг в приложении, которое использует UIWebView — не тратьте время на его локализацию. Приложение должно быть обновлено до WKWebView. Это критическое требование App Store с декабря 2020 года.

Проверить, какой WebView используется:

  • В коде приложения: поиск UIWebView vs WKWebView
  • В User-Agent: UIWebView не содержит AppleWebKit
  • Через Instruments: UIWebView работает в том же процессе
Пример: Инициализация WKWebView (Swift)
import WebKit

class WebViewController: UIViewController {
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Создаём конфигурацию
        let config = WKWebViewConfiguration()
        config.allowsInlineMediaPlayback = true
        config.mediaTypesRequiringUserActionForPlayback = []
        
        // Настройки предпочтений
        config.preferences.javaScriptEnabled = true
        config.preferences.javaScriptCanOpenWindowsAutomatically = true
        
        // Создаём WebView
        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        webView.navigationDelegate = self
        webView.uiDelegate = self
        
        view.addSubview(webView)
        
        // Загружаем URL
        if let url = URL(string: "https://example.com") {
            webView.load(URLRequest(url: url))
        }
    }
}

// Делегаты для обработки событий
extension WebViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("Страница загружена")
    }
    
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("Ошибка загрузки: \(error.localizedDescription)")
    }
}

extension WebViewController: WKUIDelegate {
    // Обработка JavaScript alert/confirm/prompt
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
            completionHandler()
        })
        present(alert, animated: true)
    }
}

2 Архитектура WKWebView: Отдельный процесс

Ключевая особенность WKWebView — это работа в отдельном процессе (out-of-process). Это фундаментальное отличие от UIWebView и Android WebView.

📱
Процесс приложения
Swift/Kotlin код, UI, логика
IPC (Inter-Process Communication)
🌐
Процесс WKWebView
WebKit, JavaScript, рендеринг

Что это значит на практике:

  • Изоляция: Краш WKWebView не всегда убивает всё приложение (но может).
  • Безопасность: JavaScript не имеет прямого доступа к памяти приложения.
  • Производительность: JavaScript работает быстрее благодаря JIT-компиляции в отдельном процессе.
  • Ограничения памяти: WKWebView имеет свой лимит памяти, независимый от приложения.
  • Сложность отладки: Нужно подключаться к двум процессам одновременно.

Как это влияет на тестирование:

  • Краши: Если WKWebView крашится, вы увидите два краш-репорта — один для приложения, один для WebKit.
  • Утечки памяти: Утечка в JavaScript может не повлиять на приложение, но убить WKWebView.
  • Производительность: Измеряйте производительность отдельно для приложения и WKWebView.
  • Восстановление: После краша WKWebView приложение должно корректно восстановить состояние.
Обработка краша WKWebView (Swift)
extension WebViewController: WKNavigationDelegate {
    // Обработка краша веб-контента
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
        print("WKWebView крашнулся!")
        
        // Перезагружаем страницу
        webView.reload()
        
        // Или показываем ошибку пользователю
        showErrorAlert(message: "Произошла ошибка. Попробуйте обновить страницу.")
    }
    
    // Обработка краша процесса
    func webView(_ webView: WKWebView, didTerminateWithError error: Error) {
        print("Ошибка процесса WKWebView: \(error.localizedDescription)")
        // Логируем ошибку и уведомляем аналитику
    }
}
🎯 Почему это важно

Если ваше приложение «падает» при открытии определённой страницы — это может быть краш WKWebView, а не приложения. В Xcode вы увидите два отдельных процесса. Всегда проверяйте, какой именно процесс крашнулся.

3 Ограничения WKWebView

WKWebView имеет ряд строгих ограничений, которых нет в Android WebView. Знание этих ограничений поможет вам понять, почему некоторые фичи «не работают».

Ограничение 1: Лимит памяти

Симптом
WKWebView крашится без ошибки при загрузке больших страниц или изображений.
Особенно заметно на устройствах с малым объёмом RAM (iPhone SE, старые iPad).

Лимиты памяти (примерные):
- iPhone с 2 ГБ RAM: ~1.2 ГБ для WKWebView
- iPhone с 3 ГБ RAM: ~1.8 ГБ для WKWebView
- iPhone с 4+ ГБ RAM: ~2.5 ГБ для WKWebView
- iPad: зависит от модели
Решение
// 1. Оптимизируйте изображения
// Используйте WebP/AVIF вместо PNG/JPEG
// Сжимайте изображения до разумных размеров

// 2. Ленивая загрузка (Lazy Loading)
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">

// 3. Виртуализация списков
// Используйте библиотеки типа react-window или vue-virtual-scroller

// 4. Мониторинг памяти в нативном коде
func checkMemoryUsage() {
    var info = mach_task_basic_info()
    var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
    
    let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
        $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
            task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
        }
    }
    
    if kerr == KERN_SUCCESS {
        let memoryMB = Double(info.resident_size) / 1024.0 / 1024.0
        print("Использование памяти: \(memoryMB) MB")
        
        if memoryMB > 1000 { // Предупреждение при 1 ГБ
            print("⚠️ Высокое потребление памяти!")
        }
    }
}

Ограничение 2: Доступ к локальным файлам

Симптом
Нельзя загрузить локальный HTML-файл напрямую через file:// URL.
WKWebView блокирует такие запросы из соображений безопасности.
Решение
// ❌ Плохо: не работает в WKWebView
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "index", ofType: "html")!)
webView.load(URLRequest(url: url))

// ✅ Хорошо: используем loadFileURL
let url = Bundle.main.url(forResource: "index", withExtension: "html")!
let directoryURL = url.deletingLastPathComponent()
webView.loadFileURL(url, allowingReadAccessTo: directoryURL)

// ✅ Альтернатива: загрузка через data: URL
let htmlString = "<html><body>Hello</body></html>"
webView.loadHTMLString(htmlString, baseURL: nil)

Ограничение 3: Cookies и ITP (Intelligent Tracking Prevention)

Симптом
Cookies не сохраняются между сессиями.
Cookies из Safari не доступны в WKWebView (с iOS 11+).
Трacking cookies блокируются автоматически.
Решение
// Настройка хранилища cookies
let config = WKWebViewConfiguration()
config.websiteDataStore = WKWebsiteDataStore.default()

// Ручное управление cookies
let httpCookieStore = config.websiteDataStore.httpCookieStore

// Установка cookie
let cookie = HTTPCookie(properties: [
    .name: "session_id",
    .value: "abc123",
    .domain: "example.com",
    .path: "/",
    .expires: Date().addingTimeInterval(86400) // 1 день
])!

httpCookieStore.setCookie(cookie) {
    print("Cookie установлен")
}

// Получение всех cookies
httpCookieStore.getAllCookies { cookies in
    for cookie in cookies {
        print("\(cookie.name): \(cookie.value)")
    }
}

// Очистка cookies
let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes()
WKWebsiteDataStore.default().removeData(ofTypes: dataTypes, modifiedSince: Date.distantPast) {
    print("Все данные очищены")
}

Ограничение 4: Автовоспроизведение видео и аудио

Симптом
Видео и аудио не воспроизводятся автоматически без взаимодействия пользователя.
Это ограничение iOS для экономии батареи и трафика.
Решение
// В нативном коде (Swift)
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = [] // Разрешаем автовоспроизведение

// В HTML
<video autoplay muted playsinline>
  <source src="video.mp4" type="video/mp4">
</video>

// Важно: видео должно быть muted для автовоспроизведения!
// Или пользователь должен взаимодействовать со страницей (клик, тап)

Ограничение 5: JavaScript alert/confirm/prompt

Симптом
JavaScript alert(), confirm(), prompt() не работают по умолчанию.
Нужно реализовать их нативно через WKUIDelegate.
Решение
extension WebViewController: WKUIDelegate {
    // JavaScript alert
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
            completionHandler()
        })
        present(alert, animated: true)
    }
    
    // JavaScript confirm
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
            completionHandler(false)
        })
        alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
            completionHandler(true)
        })
        present(alert, animated: true)
    }
    
    // JavaScript prompt
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
        let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
        alert.addTextField { textField in
            textField.text = defaultText
        }
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
            completionHandler(nil)
        })
        alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
            completionHandler(alert.textFields?.first?.text)
        })
        present(alert, animated: true)
    }
}
⚠️ Частая ошибка QA

Если JavaScript alert не появляется — это не баг, это ограничение WKWebView. Разработчики должны реализовать нативные алерты через WKUIDelegate. Если они этого не сделали — заведите баг на разработчиков.

4 SFSafariViewController: Альтернатива WKWebView

SFSafariViewController — это альтернатива WKWebView от Apple. Это «облегчённая» версия Safari, которая интегрируется в ваше приложение, но использует полноценный движок Safari.

Когда использовать SFSafariViewController:

  • Открытие внешних ссылок: Когда нужно открыть сайт, не связанный с вашим приложением.
  • Авторизация через OAuth: Apple рекомендует использовать SFSafariViewController для OAuth.
  • Контент с cookies Safari: SFSafariViewController имеет доступ к cookies пользователя из Safari.
  • Простой просмотр: Когда не нужна кастомизация и JS Bridge.

Преимущества SFSafariViewController:

  • Общие cookies: Пользователь уже авторизован в сервисах Apple.
  • Знакомый UX: Пользователь видит привычный интерфейс Safari.
  • Безопасность: Автоматические обновления через iOS.
  • Все фичи Safari: Reader Mode, AutoFill, iCloud Keychain.
  • Простота: Не нужно настраивать WKWebView.

Недостатки SFSafariViewController:

  • Нет JS Bridge: Нельзя вызывать нативные методы из JavaScript.
  • Ограниченный контроль: Нельзя кастомизировать UI (только цвет тулбара).
  • Нет доступа к нативным API: Камера, геолокация — только через веб-API.
  • Только iOS 9+: Не работает на старых устройствах.
Пример: Использование SFSafariViewController (Swift)
import SafariServices

class ViewController: UIViewController {
    func openURL() {
        guard let url = URL(string: "https://example.com") else { return }
        
        let safariVC = SFSafariViewController(url: url)
        
        // Настройка цвета тулбара
        safariVC.preferredBarTintColor = .systemBlue
        safariVC.preferredControlTintColor = .white
        
        // Показывать кнопку "Готово"
        safariVC.dismissButtonStyle = .done
        
        present(safariVC, animated: true)
    }
}

// Делегат для обработки событий
extension ViewController: SFSafariViewControllerDelegate {
    func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
        print("Safari закрыт")
    }
    
    func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
        if didLoadSuccessfully {
            print("Страница загружена")
        } else {
            print("Ошибка загрузки")
        }
    }
}
WKWebView vs SFSafariViewController
Критерий WKWebView SFSafariViewController
Контроль над UI ✅ Полный ⚠️ Ограниченный (только цвет)
JS Bridge ✅ Поддерживается ❌ Не поддерживается
Cookies Safari ❌ Изолированы (с iOS 11+) ✅ Общие с Safari
Reader Mode ❌ Нет ✅ Есть
AutoFill ⚠️ Ограниченный ✅ Полный
iCloud Keychain ❌ Нет ✅ Есть
Кастомизация JavaScript ✅ Полная ❌ Нет
Доступ к нативным API ✅ Через JS Bridge ❌ Только веб-API
💡 Правило выбора

Используйте WKWebView, если:

  • Нужен JS Bridge для связи с нативным кодом
  • Нужна полная кастомизация UI
  • Нужен доступ к нативным API (камера, геолокация)
  • Это часть вашего приложения (не внешняя ссылка)

Используйте SFSafariViewController, если:

  • Нужно открыть внешнюю ссылку
  • Нужна авторизация через OAuth
  • Нужны cookies из Safari
  • Не нужна кастомизация

5 Специфичные баги iOS WKWebView

iOS WKWebView имеет ряд специфических багов, которые не встречаются в Android WebView. Знание этих багов поможет вам быстрее локализовать проблемы.

Баг 1: Проблема с 100vh

Симптом
На iOS 100vh включает адресную строку Safari, что приводит к обрезке контента.
Особенно заметно при использовании position: fixed или height: 100vh.
Решение
/* ❌ Плохо: на iOS контент обрежется */
.fullscreen {
  height: 100vh;
}

/* ✅ Хорошо: используем dvh (dynamic viewport height) */
.fullscreen {
  height: 100dvh; /* iOS 15.4+ */
}

/* ✅ Альтернатива: JS-решение для старых iOS */
.fullscreen {
  height: calc(var(--vh, 1vh) * 100);
}

<script>
  // Устанавливаем CSS-переменную
  function setVH() {
    const vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);
  }
  
  setVH();
  window.addEventListener('resize', setVH);
  window.addEventListener('orientationchange', setVH);
</script>

Баг 2: position: fixed и клавиатура

Симптом
При открытии клавиатуры фиксированные элементы (footer, кнопки) могут "уезжать" или скрываться.
Особенно заметно на iPhone с физической кнопкой Home.
Решение
/* Используем flexbox вместо fixed */
.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

.content {
  flex: 1;
  overflow-y: auto;
}

.footer {
  flex-shrink: 0; /* Не сжимается */
}

/* Или используем sticky position */
.footer {
  position: -webkit-sticky;
  position: sticky;
  bottom: 0;
}

Баг 3: Проблема с input type="file"

Симптом
На iOS input type="file" открывает стандартный file picker, но:
- Нельзя выбрать несколько файлов (без accept="multiple")
- Ограниченный доступ к файловой системе
- Нет доступа к Camera Roll без разрешения
Решение
<!-- Разрешаем выбор нескольких файлов -->
<input type="file" accept="image/*" multiple>

<!-- Разрешаем доступ к камере -->
<input type="file" accept="image/*" capture="camera">

<!-- В нативном коде (Swift) нужно обработать UIDelegate -->
func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {
    let picker = UIImagePickerController()
    picker.delegate = self
    picker.sourceType = .photoLibrary
    picker.allowsEditing = false
    
    // Сохраняем completionHandler для später использования
    self.filePickerCompletionHandler = completionHandler
    
    present(picker, animated: true)
}

// В UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let url = info[.imageURL] as? URL {
        filePickerCompletionHandler?([url])
    }
    picker.dismiss(animated: true)
}

Баг 4: Проблема с overflow: scroll и инерцией

Симптом
На iOS скролл внутри контейнера может быть "дёрганым" или "застревать".
Особенно заметно при использовании overflow: auto без -webkit-overflow-scrolling.
Решение
/* ✅ Хорошо: плавная прокрутка с инерцией */
.scrollable {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch; /* Включает инерцию */
}

/* Для iOS 13+ можно использовать scroll-behavior */
.scrollable {
  scroll-behavior: smooth;
}

/* Избегайте вложенных скроллов */
/* ❌ Плохо: скролл внутри скролла */
.outer {
  overflow-y: auto;
}
.inner {
  overflow-y: auto;
}

/* ✅ Хорошо: один уровень скролла */
.outer {
  overflow-y: auto;
}
.inner {
  overflow-y: visible;
}

Баг 5: Проблема с autoplay для видео

Симптом
Видео не воспроизводится автоматически без взаимодействия пользователя.
Это ограничение iOS для экономии батареи и трафика.
Решение
<!-- Видео должно быть muted для автовоспроизведения -->
<video autoplay muted playsinline>
  <source src="video.mp4" type="video/mp4">
</video>

<!-- В нативном коде (Swift) -->
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = [] // Разрешаем автовоспроизведение

// Или используйте JavaScript для запуска после взаимодействия
<button onclick="playVideo()">Play</button>
<video id="myVideo">
  <source src="video.mp4" type="video/mp4">
</video>

<script>
  function playVideo() {
    const video = document.getElementById('myVideo');
    video.play();
  }
</script>

Баг 6: Проблема с transform и fixed элементами

Симптом
Элементы с position: fixed и transform: translateZ(0) могут "прыгать" при скролле.
Особенно заметно на старых версиях iOS (10-12).
Решение
/* ❌ Плохо: может прыгать */
.fixed-element {
  position: fixed;
  transform: translateZ(0);
}

/* ✅ Хорошо: используем sticky */
.sticky-element {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
}

/* Или избегайте transform на fixed элементах */
.fixed-element {
  position: fixed;
  /* Не используйте transform */
}
⚠️ Золотое правило

Если вы нашли баг, который воспроизводится только на iOS, но не на Android:

  1. Проверьте версию iOS (может, это баг конкретной версии).
  2. Проверьте модель устройства (iPhone vs iPad, старый vs новый).
  3. Проверьте, используется ли WKWebView или SFSafariViewController.
  4. Поищите баг на WebKit Bug Tracker — возможно, он уже известен.

6 Практика тестирования iOS WebView

Тестирование iOS WKWebView имеет свою специфику. Вот полный чек-лист для QA.

Матрица тестирования iOS WebView:

Версия iOS Устройство Приоритет Особенности
iOS 17 iPhone 15 Pro 🔴 Критический Последняя версия, Dynamic Island
iOS 16 iPhone 14 🔴 Критический Lock Screen widgets
iOS 15 iPhone 13 🟡 Высокий Focus modes
iOS 14 iPhone 12 🟡 Высокий App Library
iOS 13 iPhone 11 🟢 Средний Dark Mode
iOS 12 iPhone 8 🟢 Средний Последняя версия для старых устройств
iPadOS 17 iPad Pro 🟡 Высокий Stage Manager

Чек-лист для тестирования iOS WKWebView:

🎨 UI/UX
✅ Safe Area (notch, Dynamic Island, home indicator)
✅ Ориентация экрана (портрет/ландшафт)
✅ Тёмная тема (prefers-color-scheme)
✅ Системные шрифты (Dynamic Type)
✅ 100vh vs 100dvh
✅ Плавная прокрутка (-webkit-overflow-scrolling)
⚙️ Функциональность
✅ JavaScript alert/confirm/prompt
✅ File upload (input type="file")
✅ File download
✅ Camera access (getUserMedia)
✅ Geolocation
✅ Video/Audio autoplay
✅ Deep Links / Universal Links
🔒 Безопасность
✅ Cookies и ITP (Intelligent Tracking Prevention)
✅ LocalStorage / SessionStorage
✅ IndexedDB
✅ HTTPS только (Mixed Content)
✅ Content Security Policy
🚀 Производительность
✅ Время загрузки (Time to Interactive)
✅ Утечки памяти (Instruments)
✅ Потребление батареи
✅ Краш WKWebView (webViewWebContentProcessDidTerminate)
✅ Оптимизация изображений
🔄 Жизненный цикл
✅ Сворачивание/разворачивание приложения
✅ Поворот экрана
✅ Получение звонка/уведомления
✅ Блокировка экрана
✅ Низкий заряд батареи
💡 Инструменты для тестирования iOS WebView
  • Safari Web Inspector: Для отладки DOM, CSS, JavaScript, Network
  • Xcode Instruments: Для профилирования памяти, CPU, энергии
  • Charles / Proxyman: Для перехвата и модификации трафика
  • Xcode Simulator: Для тестирования на разных версиях iOS
  • TestFlight: Для бета-тестирования на реальных устройствах
🎯 Как подключиться к WKWebView для отладки
  1. На iPhone: Настройки → Safari → Дополнительно → Включить «Web Inspector»
  2. На Mac: Safari → Настройки → Дополнительно → Показать меню «Разработка»
  3. Подключите iPhone к Mac через USB
  4. Откройте Safari на Mac → Разработка → [Ваш iPhone] → [Ваше приложение]
  5. Теперь вы можете отлаживать WebView как обычный сайт
⚠️ Частые ошибки QA при тестировании iOS
  • Тестирование только на Simulator: Simulator не воспроизводит многие баги (память, производительность, камера).
  • Игнорирование старых iOS: Многие пользователи всё ещё на iOS 14-15.
  • Непроверка iPad: iPad имеет другой User-Agent и может отображать десктопную версию.
  • Непроверка Low Power Mode: В этом режиме отключаются многие фичи (автовоспроизведение, фоновая загрузка).