Site icon AppTractor

Реализация Shared With You в SwiftUI

Случалось ли вам терять ссылку, песню или другую рекомендацию, которую друг прислал вам в чате? Такое случается с каждым из нас, и, к счастью, именно эту проблему призвана решить функция Shared with You («Поделились с вами») на iOS.

Что такое «Поделились с вами»?

Функция Shared with You позволяет пользователям легко находить контент, которым с ними поделились в Сообщениях, непосредственно в соответствующих приложениях. Например, здесь мы можем увидеть все ссылки на веб-сайты, которыми поделились со мной мои собеседники, и я могу продолжить разговор, не выходя из Safari:

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

Начинаем работу

Прежде чем мы перейдем к коду, вам следует знать несколько вещей:

Единственный шаг по настройке — добавить возможность 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 мы увидим дополнительное меню с некоторыми действиями по умолчанию:

Несмотря на то, что функция 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.

Тестирование

Напоследок я хочу обратить внимание на некоторые моменты, которые облегчат вам тестирование.

Если вы пропустили, вот запись моего выступления на SwiftCraft в начале этого года:

Источник

Exit mobile version