Случалось ли вам терять ссылку, песню или другую рекомендацию, которую друг прислал вам в чате? Такое случается с каждым из нас, и, к счастью, именно эту проблему призвана решить функция Shared with You («Поделились с вами») на iOS.
Что такое «Поделились с вами»?
Функция Shared with You позволяет пользователям легко находить контент, которым с ними поделились в Сообщениях, непосредственно в соответствующих приложениях. Например, здесь мы можем увидеть все ссылки на веб-сайты, которыми поделились со мной мои собеседники, и я могу продолжить разговор, не выходя из Safari:
Эта функция была введена в iOS 16, но лишь немногие приложения используют ее в своих интересах. Как вы скоро увидите, ее очень легко реализовать, поэтому если в вашем приложении есть контент, которым можно поделиться, я настоятельно рекомендую добавить эту функцию в ваше приложение.
Начинаем работу
Прежде чем мы перейдем к коду, вам следует знать несколько вещей:
- Для того чтобы ваше приложение поддерживало функцию Shared With You, оно должно поддерживать Universal Links, поскольку именно так Apple проверяет, что ссылки, которыми вы делитесь в Messages, принадлежат вашему приложению.
- Поскольку Shared with You опирается на Messages, вам нужно будет протестировать свое приложение на физическом устройстве.
- Фреймворк извлекает контент только из сохраненных контактов, поэтому при тестировании убедитесь, что человек, отправивший вам ссылку, сохранен в ваших контактах.
Единственный шаг по настройке — добавить возможность Shared with You в наш проект Xcode:
Обзор реализации
В большинстве реализаций Shared with You вы увидите два компонента — полку и представление атрибуции (кто этим поделился).
Полка
На полке в одном удобном месте хранится весь контент, которым вы поделились в «Сообщениях». Система автоматически упорядочивает эту полку, начиная с Siri Suggestions, основанных на недавних взаимодействиях с контентом, затем идут прикрепленные сообщения и, наконец, все остальное сортируется в хронологическом порядке.
Представление атрибуции
Представление атрибуции позволяет увидеть, кто поделился с вами контентом, показывает его имя, фотографию профиля и предоставляет ссылку на исходное сообщение в беседе.
Насколько я знаю, наличие выделенной полки в вашей реализации не является обязательным, но Apple рекомендует ее использовать. Если вы предпочитаете, вы можете просто использовать представление атрибуции напрямую, чтобы указать общий контент в вашем приложении.
Давайте добавим поддержку Shared with You в приложение.
На первой вкладке отображается список записей блога этого сайта, а на второй вкладке мы создадим полку для отображения записей, которыми с нами поделились наши контакты.
Получение ссылок
Чтобы получить список ссылок, которыми поделился пользователь, мы создадим экземпляр SWHighlightCenter
, основного класса, отвечающего за получение и управление общими ссылками.
import SharedWithYou // Provides the application with a priority-ordered list of // universal links which have been shared with the current user. private let highlightCenter = SWHighlightCenter()
Далее мы воспользуемся SWHighlightCenter
, чтобы получить список highlights
. Они представляют собой список элементов, которыми с вами поделились, поэтому каждый раз, когда вы видите highlight
, думайте, что это просто ссылка, которой поделился пользователь.
Мы сохраним highlights
в свойстве @Published
, которое впоследствии будем использовать для наполнения нашей полки:
import SharedWithYou final class SharedWithYouService: NSObject, ObservableObject { // Each highlight represents a shared link @Published var highlights: [SWHighlight] = [] // Provides the application with a priority-ordered list of universal links // which have been shared with the current user. private let highlightCenter = SWHighlightCenter() override init() { super.init() highlights = highlightCenter.highlights } }
Затем мы реализуем функцию HighlightCenterDelegate
, чтобы получать уведомления о каждом изменении в highlights
:
import SharedWithYou final class SharedWithYouService: NSObject, ObservableObject, SWHighlightCenterDelegate { // Each highlight represents a shared link @Published var highlights: [SWHighlight] = [] // Provides the application with a priority-ordered list of universal links // which have been shared with the current user. private let highlightsCenter = SWHighlightCenter() override init() { super.init() highlights = highlightsCenter.highlights highlightsCenter.delegate = self } func highlightCenterHighlightsDidChange(_ highlightCenter: SWHighlightCenter) { highlights = highlightsCenter.highlights } }
Не забудьте реализовать этот делегат, иначе вы не получите никакого контента.
Вот и все! Это весь код, который нам нужен для получения списка контента, которым поделились с пользователем. Теперь у нас есть обновляемый в реальном времени список ссылок, который мы можем использовать для наполнения нашей полки.
Осталось только отобразить представление атрибуции.
Можете пропустить следующий раздел и продолжить знакомство с деталями реализации ниже.
Более детальный взгляд на SWHighlight
SWHighlight
содержит такие сведения, как кто поделился содержимым и ссылку на исходное сообщение, но единственные публичные свойства, к которым мы имеем доступ — это поля identifier
и URL
:
SW_EXTERN @interface SWHighlight : NSObject <NSSecureCoding, NSCopying> /*! @abstract The unique identifier for this highlight */ @property (copy, readonly, nonatomic) id <NSSecureCoding, NSCopying> identifier; /*! @abstract The surfaced content URL */ @property (copy, readonly, nonatomic) NSURL *URL; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end
К счастью, все, что нам действительно нужно, — это URL
. Мы можем использовать информацию, содержащуюся в URL, чтобы определить, какие данные нам нужно получить из бэкенда.
Например, в случае с подкастом URL-адрес, скорее всего, содержит идентификатор подкаста, который мы можем отправить в конечную точку бэкенда, чтобы получить остальные данные, необходимые для отображения подкаста на нашей полке, такие как миниатюра, автор, продолжительность и т.д.
Отображение ссылок
Фреймворк Shared with You включает класс SWAttributionView
для отображения представлений атрибуции, но он не имеет поддержки SwiftUI из коробки. Мы можем легко добавить поддержку, создав собственный UIViewRepresentable
, передав в highlight
свяданное представление атрибуции.
Для начала мы создадим экземпляр SWAttributionView
и начнем его настраивать.
DisplayContext
информирует систему о том, в каком окружении мы показываем представление атрибуции — мы хотим использовать .summary
, если мы представляем представление в списке верхнего уровня, и .detail
, если мы показываем представление на какой-либо странице с подробностями. Знание контекста, в котором пользователь сталкивается с представлением атрибуции, помогает системе ранжировать это выделение на полке.
struct SWAttributionViewRepresentable: UIViewRepresentable { let highlight: SWHighlight func makeUIView(context: Context) -> UIView { let attributionView = SWAttributionView() attributionView.horizontalAlignment = .leading // Change `.summary` to `.detail` if presenting in // a detail view. attributionView.displayContext = .summary attributionView.highlight = highlight attributionView.backgroundStyle = .default attributionView.menuTitleForHideAction = "Remove Article" return attributionView } func updateUIView(_ uiView: UIView, context: Context) {} }
Этот вид действительно заблокирован, и единственное, что Apple позволяет нам настраивать здесь, это некоторые базовые свойства макета — ни цветов, ни шрифтов, ни даже высоты.
Мы рассмотрим некоторые возможности настройки в ближайшее время, а пока давайте закончим реализацию нашей полки.
Создание полки
Мы воспользуемся SharedWithYouService
, который мы создали ранее, и SWHighlightCenter
, чтобы получить список highlights
(помните, что highlight — это просто способ, которым фреймворк представляет ссылку, которой поделились).
Мы интегрируем их все и создадим представление атрибуции и BlogPostRow
для каждого, что даст нам следующее:
struct SharedWithYouShelf: View { @StateObject var sharedWithYouService = SharedWithYouService() var body: some View { NavigationView { List(sharedWithYouService.highlights, id: \.url.absoluteString) { highlight in VStack { SWAttributionViewRepresentable(highlight: highlight) BlogPostRow(blogPost: getBlogPostFrom(highlight)) } } } } }
В документации Apple говорится, что ваша полка должна предлагать предварительный просмотр содержимого, включая миниатюру, заголовок, подзаголовок и представление атрибуции, которое, как вы видите, мы реализовали здесь для каждого highlight
.
Apple хочет, чтобы отображение этих представлений атрибуции было безопасным и не раскрывало никакой информации о получателях или разговорах, поэтому Apple создает эти представления от вашего имени «вне процесса». Это означает, что представление создается отдельным процессом вне основного потока, поэтому вы можете добавить эту функцию в свое приложение, не беспокоясь о том, что она действительно повлияет на производительность вашего приложения.
Вот и все, что нам нужно для создания полки и показа общего контента в наших приложениях.
Настройка меню
В нашей текущей реализации при длительном нажатии на SWAttributionView
мы увидим дополнительное меню с некоторыми действиями по умолчанию:
- Reply приведет к появлению соответствующего сообщения в беседе, что позволит нам ответить, не выходя из приложения.
- Remove Article удалит эту ссылку из Shared with You.
Несмотря на то, что функция Shared with You в целом очень закрыта, Apple предоставляет некоторые возможности настройки, которые позволяют нам добавить еще несколько опций в это меню.
Чтобы сделать это, нам нужно обновить нашу реализацию UIViewRepresentable
, описанную ранее.
Сначала мы добавим ряд rfcnjvys[ UIActions
, которые мы хотим добавить в это меню. Они будут зависеть от конкретного случая использования, но в случае со списком записей блога мы, возможно, захотим отобразить действия для сохранения в списке чтения, перевода статьи и добавления ее в закладки.
func makeUIView(context: Context) -> UIView { let attributionView = SWAttributionView() ... // Action to save the article to a reading list let saveToReadingListAction = UIAction( title: "Save to Reading List", image: UIImage(systemName: "book") ) { _ in ... } // Action to translate the article let translateAction = UIAction( title: "Translate", image: UIImage(systemName: "globe") ) { _ in ... } // Action to bookmark the article let bookmarkAction = UIAction( title: "Bookmark", image: UIImage(systemName: "bookmark") ) { _ in ... }
Затем мы можем просто определить наше новое меню, указать заголовок и дочерние элементы для показа и назначить его свойству supplementalMenu
представлению атрибуции.
func makeUIView(context: Context) -> UIView { let attributionView = SWAttributionView() ... // Action to save the article to a reading list let saveToReadingListAction = UIAction( title: "Save to Reading List", image: UIImage(systemName: "book") ) { _ in ... } // Action to translate the article let translateAction = UIAction( title: "Translate", image: UIImage(systemName: "globe") ) { _ in ... } // Action to bookmark the article let bookmarkAction = UIAction( title: "Bookmark", image: UIImage(systemName: "bookmark") ) { _ in ... } attributionView.supplementalMenu = UIMenu( title: "Extras", children: [ saveToReadingListAction, translateAction, bookmarkAction ] ) return attributionView }
Теперь у нас есть эти кастомные опции, появляющиеся каждый раз, когда мы взаимодействуем с SWAttributionView
.
Тестирование
Напоследок я хочу обратить внимание на некоторые моменты, которые облегчат вам тестирование.
- В приложении «Настройки» перейдите в раздел «Сообщения» и убедитесь, что функция Shared with You включена на всех устройствах. Это должно быть так по умолчанию, но не лишним будет перепроверить.
- Apple позволяет пользователям отключать автоматический шаринг как глобально, так и для отдельных приложений, поэтому убедитесь, что функция «Поделились с вами» включена и для вашего приложения.
- Сохранение контента в Messages — отличный способ проверить, как работает эта функция, поскольку она автоматически предоставляет разрешение Shared with You. Если вы закрепили сообщение от известного контакта и не видите результатов в
HighlightCenter
, проблема, скорее всего, кроется в вашей реализации.
Если вы пропустили, вот запись моего выступления на SwiftCraft в начале этого года: