Реалізація Clipboard-інтеграції в мобільному додатку
Clipboard—один з тих API, який виглядає тривіально, поки не сталкиваєтесь з його поведінкою на різних версіях iOS та Android. Починаючи з iOS 14 кожен доступ до UIPasteboard.general без согласия користувача показує системний баннер «[App] вставила з буфера обміну». На Android 12+ аналогічний тост. Це не баг—це намисна поведінка платформ.
iOS: UIPasteboard та обмеження
UIPasteboard.general.string—синхронне читання. Але з iOS 14 додаток отримує попередження при кожному читанні, якщо воно не інічійоване явним дією користувача (натисканням кнопки «Вставити»).
Правильний підхід—використовувати UIPasteControl (iOS 16+): системна кнопка, яка читає буфер обміну без попередження, тому що користувач явно натиснув на неї:
let pasteControl = UIPasteControl(configuration: UIPasteControl.Configuration())
pasteControl.target = self
// реалізуємо UIPasteConfigurationSupporting
override func paste(itemProviders: [NSItemProvider]) {
for provider in itemProviders {
if provider.canLoadObject(ofClass: NSString.self) {
provider.loadObject(ofClass: NSString.self) { string, _ in
DispatchQueue.main.async {
self.handlePastedText(string as? String)
}
}
}
}
}
Для iOS 14–15, де UIPasteControl недоступен, єдиний спосіб не показувати баннер—читати буфер тільки в applicationDidBecomeActive або при явному tap-дії. Читання в viewDidLoad або в фоні—гарантований баннер.
Запис у буфер обміну—без обмежень: UIPasteboard.general.string = "text". Для складного контенту (зображення + текст), використовуйте setItems([["public.plain-text": text, "public.png": imageData]]) з явними UTI-типами.
Android: ClipboardManager
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// Запис
val clip = ClipData.newPlainText("label", "text to copy")
clipboard.setPrimaryClip(clip)
// Читання
val text = clipboard.primaryClip?.getItemAt(0)?.coerceToText(context)
На Android 13+ (API 33) ClipboardManager.getPrimaryClip() повертає дані тільки для додатку на передньому плані або додатку, який записав дані. Фонове читання—SecurityException. Ця зміна сломала кілька менеджерів паролів при оновленні.
Android 13 також додав візуальне підтвердження при копіюванні—системний тост з превью скопійованого тексту. Додаток може його відключити для конкретної операції: ClipData.newPlainText("label", text).apply { description.extras = PersistableBundle().apply { putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true) } }—для чутливих даних (паролі) тост не показується.
Flutter та React Native
У Flutter—flutter/services package, Clipboard.getData(Clipboard.kTextPlain) та Clipboard.setData(). Повертає Future—не викликайте в initState без перевірки mounted, інакше setState після dispose дає assertion error.
У React Native—@react-native-clipboard/clipboard. На iOS під капотом той же UIPasteboard, на Android—ClipboardManager. Проблема: пакет не абстрагує iOS 16 UIPasteControl—при необхідності потрібен нативний модуль.
Реалізація clipboard-інтеграції з урахуванням iOS 14+ та Android 13+: 1 день. Вартість розраховується індивідуально.







