Karhin’s Blog
Apps, Design and Music
Later ↑

Правильная реализация UIActivityViewController в SwiftUI

В iOS 16 добавлен ShareLink, который полностью решает проблему. Если вы собираетесь поддерживать версии iOS до 16, то можете читать дальше.

С помощью Activity View Controller можно предоставить пользователю функции для взаимодействия с контентом в других приложениях: копирования, публикации в социальные сети, отправки через мессенджеры и многие другие функции.

К сожалению, Apple до сих пор не представили нормального способа создания UIActivityViewController в контексте SwiftUI. Как и не сделали ещё больше десятка мелких, но необходимых вещей для базового приложения.

Самым простым и логичным решением кажется оборачивание в UIViewControllerRepresentable, как в топовом ответе на StackOverflow, и представление через .sheet().

struct ActivityViewController: UIViewControllerRepresentable {
    var activityItems: [Any]
    var applicationActivities: [UIActivity]? = nil
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
        return UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
}

Всё это хорошо работает на iPhone. Однако у этой реализации есть один большой недостаток: Activity View будет занимать весь экран iPad, а в macOS меню будет торчать не там, где нужно.

Представление Activity View курильщика в iPadOS и macOS.

Основная причина некорректного поведения в том, что в UIKit приложении для представления UIActivityViewController требуется указывать sourceView и sourceRect, что явно указано в документации. Приложение на iPad или macOS просто упало бы без этих атрибутов, но в SwiftUI поведение изменили.

On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally.

Представление Activity View здорового человека в iPadOS и macOS.

Если вы используете SwiftUI из UIKit, то просто передайте координаты и размеры вашей кнопки в UIViewController. Имея координаты, можно установить необходимую позицию для UIActivityViewController. Получить координаты для View можно через .overlay() и GeometryReader. Не очень красиво, но работает.

protocol MyUIViewDelegate: AnyObject {
    func shareButtonFrameChanged(frame: CGRect)
}

.overlay(GeometryReader { proxy -> AnyView in
    delegate?.shareButtonFrameChanged(
        frame: proxy.frame(in: CoordinateSpace.global)
    )
    return AnyView(Rectangle().fill(.clear))
})

Что делать, если вы хотите писать только на SwiftUI? Я попытался воссоздать нормальное поведение через popover. В iPadOS меню начало отображаться ровно по середине кнопки, но откорректировать его положение не получилось. В macOS поведение вообще не изменилось.

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

extension UIApplication {
    
    var keyWindow: UIWindow? {
        return UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .first(where: { $0 is UIWindowScene })
            .flatMap({ $0 as? UIWindowScene })?.windows
            .first(where: \.isKeyWindow)
    }
    
    var keyWindowPresentedController: UIViewController? {
        var viewController = self.keyWindow?.rootViewController
        if let presentedController = viewController as? UITabBarController {
            viewController = presentedController.selectedViewController
        }
        while let presentedController = viewController?.presentedViewController {
            if let presentedController = presentedController as? UITabBarController {
                viewController = presentedController.selectedViewController
            } else {
                viewController = presentedController
            }
        }
        return viewController
    }
    
}

Кнопку шаринга нужно разместить в Geometry Reader, чтобы получить её координаты. .overlay() уже не подойдёт, так как он будет изменять состояние View, но и код станет проще.

GeometryReader { proxy in
    Button(action: {
        let rect = proxy.frame(in: CoordinateSpace.global)
        let activityViewController = UIActivityViewController(activityItems: [URL(string: "https://karh.in/")!], applicationActivities: nil)
        if let vc = UIApplication.shared.keyWindowPresentedController {
            activityViewController.popoverPresentationController?.sourceView = vc.view
            activityViewController.popoverPresentationController?.sourceRect = rect
            vc.present(activityViewController, animated: true, completion: nil)
        }
    }, label: {
        Image(systemName: "square.and.arrow.up")
            .font(.system(size: 32))
    }).buttonStyle(.bordered)
}.frame(width: 48, height: 48)

Плохая новость: это может перестать работать в следующих версиях 🤡. Хорошая новость в том, что теперь Activity View будет появляться в нужном месте, а если даже поведение окон поменяют, то приложение не упадёт, а просто не отобразит меню.

Представление Activity View в SwiftUI здорового человека.

Полезные ссылки по теме:

Если вдруг есть какой-то более красивый способ правильно отобразить UIActivityViewController на всех платформах, то обязательно поделитесь в комментариях. Надеюсь, что на WWDC 2022 всё таки представят нормально решение.

Запускаем Chrome OS Flex

Google запустил открытое тестирование операционной системы Chrome OS Flex. Обещают, что будет работать даже на очень старом ПК или маке. Я не поверил и решил проверить: рассказываю про впечатления, как установить и небольшую проблему, с которой столкнулся. Если кратко про впечатления, то мне очень понравилась.

Минимальные системные требования:

  • 64-разрядный процессор Intel или AMD;
  • 4 гигабайта ОЗУ;
  • 16 гигабайтов постоянной памяти.

Установка

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

Для установки нужна флешка минимум 8 гигабайтов, установленный гугл хром с расширением Chromebook Recovery Utility. Расширение работает только на маке и виндоус, на линуксе записать не получилось.

Втыкаем флешку, запускаем расширение, выбираем производителя Google Chrome OS Flex, продукт Chrome OS Flex (Developer-Unstable), ждём несколько десятков минут загрузку и запись операционной системы.

Не без приколов, конечно.

Перезагружаем компьютер, загружаемся с USB. Я запускал операционную систему на старом Lenovo Y510P и у меня возник прикол: чёрный экран после загрузочного с логотипом Google Chrome. Система издавала какие-то звуки, но на экране пустота: сделал верный вывод, что проблема с GPU. Уже думал, что пора бросить это дело, но вспомнил, что в моём старике целых три видеокарты. Одну видеокарту можно достать, что я и сделал.

Вот это технологии делали в 2013 году.

Попробовал загрузиться ещё раз и провалился в матрицу. Операционную систему можно установить на диск или загрузиться с флешки. Загрузка с флешки персистентная и состояние сохраняется между запусками. Из-за того, что для запуска нужно доставать GPU, решил пока не ставить как основную операционную систему.

Теперь это не виндоноут, а миленький хромбук.

Решил, что нужно продолжить тестирование и нашёл ноутбук динозавра с ещё более впечатляющими характеристиками – HP 450 G2. У этого компьютера как раз минимальные требования для этой операционной системы. Думаю, что вы представляете, как он работает на последней виндоус (никак).

Как тебе такое, Сатья Наделла?

Впечатления

Первые впечатления обманчивые, но мне понравилось. Камера, микрофон, Wi-Fi, Bluetooth и другая периферия заработали прямо из коробки. На обычном линуксе пришлось бы еще два часа возиться и искать нужные пакеты.

Нарядный лаунчпад, как в макос.

Самое главное – она реально быстрая. Даже на обычном жестком диске, на старом процессоре и с 4 или 8 гигабайтами оперативной памяти. После виндоус, где приложения могут запускаться по 30 секунд или минуте на такой конфигурации, как глоток свежего воздуха.

Все приложения в Chrome OS – это веб-приложения, поэтому у меня возник логичный вопрос, как кодить. Оказалось, что всё проще простого и можно запускать линукс приложения через Crostini. Я не попробовал запустить Android Studio или VS Code, потому что под рукой только стик на 16 гигабайт, но кажется, что с этим не будет проблем.

Мне кажется, что эта операционная система закроет потребности большинства пользователей. Серьезно, когда вы устанавливали приложения на компьютер, а не открывали веб-браузер?

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

А зачем это всё, когда есть убунту/дебиан/аниме? Давно хотел написать про то, почему я не использую линукс на основном компьютере, но люблю его на серверах и в эмбеддед. За компьютером хочется работать, а не заставлять его работать. К сожалению, в большинстве дистрибутивов наоборот и, скорее всего, так и будет. Гугл снова выпустил дистрибутив лучше бубунты.

Закрытый показ Pepperoni Engine

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

Весь движок разработан на языке TypeScript. Сегодня я запускаю его в бету и опубликовал весь исходный код в приватный репозиторий. Если вы не боитесь JavaScript, Docker, Postgres и Redis, то его можно попробовать прямо сейчас. Если боитесь, то постараюсь помочь разобраться; Тимур сегодня впервые установил Docker и запустил движок локально за 10 минут.

Почему мой движок клёвый?

  • Он оптимизирован для SEO. Есть поддержка Sitemap, Yandex.Turbo, OpenGraph, JSON-LD и некоторых других технологий.
  • Свой собственный язык для разметки статей, похожий на Markdown. Благодаря собственному языку и его парсеру, можно приводить статьи к любому формату, даже для нативного отображения в мобильных приложениях.
  • Система древовидных комментариев с возможностью анонимного комментирования.
  • Авторизация через Телеграм, мгновенные оповещения для пользователей и администраторов сайта через него же.
  • Черновики, статические страницы, категории, теги, счётчики просмотров, рекомендации и всё остальное, как у всех.
  • Стандартная тема поддерживает ночной и дневной режимы, адаптивная, набирает много попугаев в Google Lighthouse.
  • Если меня пнуть и убедить, сделаю нужную фичу. Ну либо вы сами её сделаете.

Для того, чтобы получить доступ к исходному коду, добавляйтесь в группу в Телеграме. После я отправлю вам приглашение в GitLab.

Конфигурация движка ещё немного гиковая (через JSON), но надеюсь, что скоро решим эту проблему. Уже не гиковая. Когда причешем весь код, скорее всего, опубликуем его публично.

Пять последних функций iOS, о которых вы не знали

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

AirPods Pro можно использовать как AirTag и удалённо блокировать

Обновлённое приложение Find My позволяет отслеживать AirPods и даже получать уведомления, когда они оказались где-то далеко от вас. Устройства Apple уже несколько лет умеют общаться друг с другом в фоне с помощью блютуза, а ещё передавать идентификаторы устройств и геолокацию на сервера Apple.

Как-то провёл эксперимент: отправил Катю гулять с моими наушниками. Смог отслеживать её геолокацию практически в реальном времени.

Совсем недавно в одну из последних iOS 14 и iOS 15 добавили фичу с удалённой блокировкой AirPods, как с iPhone. Теперь воровать наушники нет смысла, а при покупке с рук нужно проверять на предмет блокировки. На наушниках должна быть версия прошивки не ниже 4A400. Если ниже, то необходимо обновить. Это не совсем тривиальная операция:

  • поместите наушники в кейс;
  • подождите, пока зарядятся;
  • подключите к вашему iPhone;
  • скорее всего, они скоро обновятся.

App Privacy Report

Моя любимая функция как безопасника, но вам тоже понравится. С помощью App Privacy Report можно отслеживать основную активность приложений: доступ к фотографиям, камере, контактам, геолокации, сети (включая IP адреса и домены) и так далее.

Функция доступна с iOS 15: Settings → Privacy → Листаем в самый низ → App Privacy Report.

Уже есть пример из жизни, когда это помогло. Приложение Яндекса в фоне отслеживало мою геолокацию через виджет погоды с уровнем разрешения «Пока использую приложение». Через App Privacy Report я смог это отследить и отобрал у приложения разрешение.

Drag-and-Drop в iOS

Одна из моих любимых функций, которая очень давно была в iPad, но совсем недавно появилась в iOS. Спросил у своих знакомых, а они про нее не слышали. Единственный недостаток в том, что телефон придётся брать в две руки.

Тягать между приложениями можно ссылки, картинки, текст и вообще почти всё, что угодно. Попробуйте прямо сейчас перетянуть фотографию из приложения Photos в Telegram.

Drag-and-Drop скриншота домашнего экрана в мой канал.

Постукивание по задней крышке

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

Одна из новых - постукивание по задней крышке телефона. Можно привязать к этим жестам почти любое действие, которое вы можете совершить с шорткатами.

Функция доступна с iOS 14: Settings → Accessibility → Touch → Back Tap. Советую прогуляться по Accessibility: в iOS очень много прикольных функций спрятано именно там.

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

Магия Back Tap в действии.

Режимы микрофона в звонках

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

Во время звонка откройте центр уведомлений, сверху будет кнопка «Режим работы микрофона». Иногда не получается переключить режим; мне помогает перезагрузка телефона и повторный звонок. Функция доступна не только в iOS, но и в macOS и iPadOS.

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

Решаю важные вопросы с включенной изоляцией голоса.

Если вы знаете какие-то крутые функции, расскажите в комментариях.

Красивый Email за десять минут

Если думали, что собственный домен для почты – это привилегия бизнеса или программистов, то нет. Сделать собственный адрес проще простого, но нужно потратить десять минут и немного походить по ссылкам.

Регистрируем домен

Это самый длительный и тяжелый момент. Во-первых, нужно придумать и найти свободный домен. Если вы потратите здесь больше трёх минут, то я не виноват, что у вас ушло больше времени, чем в заголовке. Во-вторых, нужно еще походить по разным сайтам и найти там, где будет дешевле.

Домен – это часть адреса от точки до точки, если очень просто. Домены считают справа налево. Например, в karh.in часть in это домен первого уровня, а karh это домен второго уровня. Домены второго уровня обычно платные. Домены первого уровня получают по заявкам и за очень большие деньги.

Всю связку доменов karh.in называют хостнеймом (hostname).

Есть домены третьего уровня за которые тоже нужно платить, типа kargin.co.uk или kargin.aeroport.fr. Но это специализированные предложения. Вообще, самое крутое, когда у тебя короткий домен, типа the.tj или t.me. За такие домены иногда платят миллионы баксов.

Я покупаю уже несколько лет домены у Name.com, потому что чаще всего беру какие-то специфичные домены первого уровня, а их сейчас очень много. Если вы собрались брать национальную зону (по названию государства), типа by или ua, то лучше смотреть у локальных регистраторов, у них будет дешевле. Например, у REG.RU сейчас можно купить ru за 200 ₽ или $2.70. В общем, вбивайте в поиске по маске “.club купить домен”.

Найти хороший домен – это самое сложное.

Обращайте внимание на сумму продления: в первый год могут попросить $0.01, а за продление $49.00. Для регистраторов обычное дело.

С покупкой никаких проблем возникнуть не должно, обычно, там простая и быстрая регистрация. Не сложнее, чем на Алике или Озоне. Возможно, потребуется указать свой адрес, но не пугайтесь – обычная практика и требование разных законодательств. Не забывайте, что оплачивать такие штуки лучше через виртуальную карту.

Хостинг

Этот момент вообще любого может отпугнуть. Самому держать свою почту, конечно, намного конфиденциальнее и безопаснее (пока не найдут новую уязвимость нулевого дня, ха-ха), но, если вы не собираетесь обсуждать планы захвата мира, а злое АНБ только сидит и думает, как прочитать вашу очередную рассылку от МдаМода со скидками 1000%, то можно взять у компаний, которые делают на этом бизнес.

Есть предложение от гугла по $6 за один почтовый ящик. Мне сложно комментировать без мата, откуда эта цена: ребята продают воздух.

Есть условно-бесплатное предложение для 5 почтовых ящиков от Apple, идёт бонусом с iCloud+. Намного интереснее, чем от гугла.

В любом случае, принцип привязки купленного домена к почтовому сервису у всех одинаковый.

Я использую почту от Яндекса, потому что она бесплатная до 1000 почтовых ящиков, которые можно зарегистрировать, если вы собираетесь с них что-то писать. Если не собираетесь, то расскажу, как приблизить это количество к бесконечности.

К сожалению, пока писал этот пост и делал инструкцию, произошло кое-что интересное. Яндекс закрыл регистрацию в 360 (Connect) для физических лиц. В общем, передаю их менеджерам и управленцам привет. Будем переносить почту к Apple.

Теперь десяток моих некоммерческих проектов и поделок должны иметь российский ИНН. Спасибо, Яндекс, и до свидания.

Настройка Custom Email Domain в iCloud+

Переходим по адресу Custom Email Domain. У меня уже был добавлен адрес karh.in, но проблем с добавлением не должно возникнуть.

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

На мой основной iCloud пришла информация, которую необходимо прописать в настройках DNS. Аналогичная информация доступна прямо на этой же странице при нажатии на кнопку “View” в разделе Update your domain registrar’s settings.

DNS – это система идентификации в интернете. Вы же слышали про IP адреса? Ну такие, типа 192.168.0.1 по которым вы заходите в настройки роутера. Есть еще буквенные адреса, типа google.com. Под капотом, на самом деле, есть отдельный сервис, который соотносит вот этот google.com с его IP адресом.

В ближайшем будущем IP адреса станут очень сложными, но они смогут уникально идентифицировать вообще каждое устройство в интернете. Фраза "вычислю по айпи и набью лицо” намного ближе к реальности.

Но DNS отвечает не только за такую конвертацию, там ещё содержатся всякие разные данные про сервера. Например, адрес почтового сервера, который мы сейчас заполним.

Apple предлагает настроить сразу несколько записей: TXT, SPF, MX, DKIM. Очень сложные слова, но попробую объяснить очень просто, что это за записи.

  • С помощью TXT записи сервер Apple проверит то, что именно вы являетесь владельцем домена.
  • С помощью MX (Mail Exchanger) записи определяется адрес по которому другие почтовые сервера должны доставлять письма.
  • SPF и DKIM нужны для того, чтобы получатель почты мог определить, что именно ваш сервер отправил сообщение. Звучит сложно, но электронная почта – это децентрализованный сервис, где такие вещи необходимы для того, чтобы избежать спама и поддельных сообщений.

Думаю, что вы уже устали от непонятных слов и всякой теории, но вдруг пригодится. Ищем в настройках вашего аккаунта, где вы покупали почтовый домен, кнопку “Manage DNS Records” или “Настройки DNS”.

У меня уже есть запись от Яндекса, которую я смело удаляю. Остальное заполняется из таблички, которая пришла от Apple. Там указан тип записи и что в него нужно ввести.

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

Я провозился дольше из-за странной ошибки, связанной с проверкой SPF записи, которая никак не упоминается в документации от Apple. У Тимура получилось с первого раза по этой статье (у него был черновик). Обратите внимание на TTL, если он есть в настройках DNS; укажите 60 минут (3600 секунд) на все записи, которые просит Apple.

TTL (Time To Live) – это время, которое указывает сколько клиент, запрашивающий запись, должен хранить у себя ответ перед тем, как запросить запись снова.

Спустя минуту получаем заполненную табличку.

Возвращаемся в настройки и нажимаем “Finish Setup”. Если всё окей, то вы получите соответствующее сообщение, иначе проверьте записи DNS и прочитайте сноску выше. На других устройствах появится уведомление, что вы начали использовать новый почтовый адрес с вашим iCloud в iMessage.

Та-дам. Я переехал на почту от iCloud.

Я собирался рассказать про то, как сделал безлимитные адреса в Яндексе. У них есть возможность перенаправлять письма с несуществующих адресов на какой-либо существующий. К сожалению, у iCloud такой возможности нет. У меня был десяток адресов, которые я использовал вместо функции “Hide My Email” (yandex@karh.in, uber@karh.in и другие).

Отправил письмо в iCloud с просьбой добавить такую функцию. Мне сразу сказали, что не ответят.

UPD: В iCloud добавили возможность ловить все письма, которые приходят на домен.


Мне подсказали несколько других хостингов:

  • Zoho. 5 бесплатных ящиков, а потом по евро баксу за пользователя.
  • Mail.ru. Бесплатно и с рекламой. Подозреваю, что может стать такой же ловушкой Джокера, как и Яндекс.

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

Earlier ↓