Перейти к содержимому

Page link что это в айфоне

  • автор:

Универсальные ссылки: дворец из подводных камней

При том, как много мобильные приложения дали человечеству, они в то же время «сломали» интернет. Вместо понятных ссылок на сайты, которые можно скопировать и поделиться, стало нужно объяснять «поставь такое-то приложение и зайди там туда-то».

К счастью, разработчики мобильных платформ осознали эту проблему и предложили концепцию «универсальных ссылок», которые одним кликом открывают нужное на любой платформе. Но то, что для пользователя «одним кликом», для программиста — «потом и кровью». На пути к успеху стоит целый ворох неожиданных нюансов, и Константин Якушев познакомился с ними на личном опыте при внедрении универсальных ссылок в Badoo. А затем на нашей конференции Mobius рассказал, как сделать всё правильно и обойти проблемы. Зрителям доклад понравился, и мы решили, что негоже полезному материалу оставаться только видеозаписью, поэтому под катом — его текстовая версия.

В докладе ситуация рассмотрена со стороны iOS, но универсальные ссылки на то и универсальные, чтобы объединять разные платформы, так что извлечь пользу могут не только iOS-разработчики.

Вступление

Меня зовут Костя Якушев и я провёл несколько месяцев, отлаживая универсальные ссылки в Badoo для всего сразу: iOS, Android, веба. Я координировал действия всех этих прекрасных ребят, потому что я мамин архитектор.

И сегодня я расскажу вам следующее:

  • Что такое универсальные ссылки
  • Зачем они вам нужны
  • Как их сделать
  • Самое главное: какие будут с этим проблемы
  • И как эти проблемы решать
  • Каков общий фантастический алгоритм невероятного успеха, который позволит вам сделать это не за несколько месяцев, как мы, а за один вечер (ну, может быть, два, потом баги неделю)…

Что вообще такое универсальные ссылки? История достаточно простая. Вы пользуетесь каким-нибудь приложением, например, Badoo. Если вы открыли там чей-то пользовательский профиль и вам захотелось отправить другу ссылку на этот профиль, вы нажимаете эту кнопку со стрелочкой:

Присылаете другу ссылку, она в переписке красиво отображается, друг на неё нажимает, и у него открывается страница той же самой девушки:

Когда ссылка открывает какой-то контент внутри приложения, это традиционно называется диплинкингом. Но это идеальный сценарий, когда у друга установлено приложение, и оно под iOS.

А на практике у друга может не быть приложения, и тогда мы хотим его отправить в App Store.

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

Либо друг вообще открыл ссылку на десктопе, и тогда ему нужно отправить десктопный сайт.

Но ужаснее всего, что у друга может оказаться Android! И это тоже должно сработать.

Когда ссылка обладает всеми этими свойствами или хотя бы некоторыми из них, то она называется универсальной ссылкой. И про них я и расскажу, как это делать.

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

Зачем всё это нужно? Применений миллион.

Например, вы хотите отправить письмо пользователю «смотри, у тебя новый лайк». Было бы классно, если бы при нажатии на «Take a look» открылся именно новый лайк.

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

Например, вы размещаете рекламу в Facebook. Facebook очень щепетильно относится к тому, что если на рекламе изображено что-то конкретное, то ссылка тоже должна вести на это конкретное. То есть, если вы видите в рекламе профиль девушки, то они настаивают, чтобы эту же девушку можно было увидеть в приложении.

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

А кнопка «share», про которую я уже говорил — это самое важное, самое классное, самое прикольное, но и самое сложное.

Идея здесь в том, что пользователи хотят делиться чем-то, что есть в вашем приложении. Это даже важнее для других приложений, не-дейтинговых. Допустим, если у вас трэвел-приложение, пользователи точно захотят делиться билетами или гостиницами. Они бесплатно отправляют ссылки своим друзьям, тем самым делая рекламу вашему приложению. В идеале это приводит к новым установкам, к органическому росту, и при этом почти бесплатно (по цене, собственно, имплементации этой технологии).

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

Сначала мы пошли смотреть на branch.io. Всё, что они делают, связано исключительно с deep links: на сайте по центру написано deep linking, но два пункта по бокам, в общем-то, тоже означают диплинкинг.

У них куча крутых клиентов, наш любимый конкурент Tinder ими пользуется. И в некоторых случаях они бесплатные. Но в нашем случае они оказались совсем не бесплатными.

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

AppsFlyer — это маркетинговый сервис, который в первую очередь предназначен для трекинга установок, чтобы определять, откуда пользователи пришли, измерять эффективность рекламных кампаний и так далее. Когда мы занялись универсальными ссылками, в Badoo уже пользовались этим сервисом для других задач, и наши маркетологи его просто обожают. Если прислонить нашего маркетолога к стене и спросить, крутой ли AppsFlyer, он скажет: «Очень крутой, все остальные сервисы гораздо хуже. И не прислоняй меня больше к стене, пожалуйста».

У AppsFlyer тоже много крутых клиентов, им тоже пользуется Tinder — для всего, кроме диплинков, но тогда это нас не смутило. Мы заходим на сайт, видим надпись «The most world’s powerful deeplinking platform» — ну, надо брать. Кроме того, на тот момент это было для нас, в общем, бесплатно, потому что входило в продукт, за который мы и так платим.

Мы зашли к ним на сайт. Тогда там была вот такая схема, а сейчас её на сайте уже нет. В принципе, это то же, что я показывал раньше. Вы переходите по ссылке, если приложение установлено — запускается, если нет — идёте в App Store. Но!

Как выяснилось, та часть, где приложение не установлено, мы идём в AppStore и после установки открываем контент, работает. И это логично: эти ребята занимаются трекингом инсталлов, они на этом собаку съели.

Но вот часть «приложение уже установлено», когда мы её реализовали, почему-то вместо того, чтобы открывать контент, тоже показывала вопрос «Open this page in App Store?».

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

С самого начала

Вот архетипичный диплинк:

Здесь есть кастомная схема badoo://, какая-то штука, которую мы хотим открыть (допустим, юзера), и есть ID этого юзера.

Понятно, что в сыром виде эту ссылку использовать нельзя по очень простой причине. Если приложение установлено, она работает как ссылка, но если не установлено, приводит к ошибке «Safari cannot open the page». Это логично, Safari понятия не имеет, как с этим работать.

До недавнего времени это обходилось достаточно легко. Мы делаем обычную HTML-штуку, в которой у нас есть несколько вещей:

Есть iframe, который получает параметры из URL и имеет тот самый deep link. И есть JavaScript, который редиректит в App Store. Задумка в том, что если у вас установлено приложение — сработает iframe, если не установлено — сработает редирект, и все счастливы, ошибка не вылезет.

Кроме того, к HTML-странице можно довесить Open Graph-теги, которые будут позволять ссылке выглядеть вот так красиво, как я показал: у неё есть картинка, превью и все такое.

Так вот, начиная примерно с iOS 9, эта прекрасная схема перестала работать.

Что значит «перестала работать»? Это значит, что она стала работать так, как будто вы просто шарите ссылку badoo://. Если приложение установлено — работает, если не установлено — показывает ошибку (в общем-то, поверх редиректа). То есть, если пользователь переходит по ссылке и у него не установлено приложение, возникает ошибка. И в Apple сломали это совершенно осознанно.

Зачем? Потому что они придумали нечто «принципиально новое и фантастическое»: универсальные ссылки.

В их задумке вы просто регистрируете всю ссылку, весь домен как universal link. И когда я говорю «принципиально новый и фантастический» способ, я имею в виду «почти принципиально новый», в Android он уже много лет был.

Зачем они это делали?

  • Во-первых протокол badoo:// может зарегистрировать кто угодно, нет никакой верификации. А домен подтверждается DNS.
  • Во-вторых, приватность: приложение больше не получает доступ к cookie. Это то, что Apple хочет с каждым годом всё больше — отделить знания о пользователе в Safari от знания о пользователе в приложении.

Наконец, (в идеале) одинаково работает на всех платформах.

К сожалению, когда мы избавились от хаков с айфреймами, мы получили много других хаков, но об этом попозже. Сейчас про AppsFlyer. Помните, всё началось с того, что они не поддерживают универсальные ссылки.

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

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

Но мы же умеем решать эти проблемы, да? Обычно мы делаем редирект, на вебе же работают редиректы, правда ведь?

И мы стали тестировать. Окей, onelink.me работает, мы все правильно интегрировали, SDK запускается, открывает Badoo, открывает контент. Наша ссылка, которая является тупым редиректом… открывает Safari. Ничего себе, подумали мы.

И тогда мы стали вести список, который становился всё длиннее с каждым днём нашей реализации. Он называется «Когда универсальные ссылки не открывают приложение, хотя ожидаешь, что откроют».

И вот первый пункт этого списка:

Редиректы на универсальные ссылки не работают. Это сильно сбило нас с толку, тогда мы решили бросить AppsFlyer (и по этой причине, и по некоторым другим), сделав всё самостоятельно. Тем более, что у нас уже была готовая схема андроида:

Она состояла из двух частей. У нас был page id (идентификатор той страницы, которая должна открыться) и id контента (например, user id). И сделать это в принципе не сложно, у Apple на сайте есть инструкция, она занимает буквально три страницы и представляет собой три пункта.

Во-первых нужно добавить apple-app-site-association — json-файл, в котором у вас будет указан ваш appID и пути, которые должны открываться приложением в универсальные ссылки.

Второе. Вам нужно добавить в entitlements домены:

И третье: вам нужно написать немного кода, который, в общем-то проверит, что ActivityType это BrowsingWeb, а URL есть.

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

Вот в чём здесь история. У нас есть наша первая схема, о которой я сказал, в которой указан page id. Есть вторая схема, ей пользуются как раз branch.io и AppsFlyer: обычно делают так, что у вас нет идентификатора, а есть просто какой-то ref ссылки, который приложение при запуске отправляет на сервер, получает в ответ, куда ему залендить, и лендит туда.

Проблема второго варианта в том, что пока приложение получает контент, мы хотели бы показывать какой-то каркас содержимого, а вторая схема нам этого не позволяет (в частности, с AppsFlyer этого не сделать никак).

В конечном счёте мы решили, что просто будем использовать всё сразу:

В принципе, это нормальное решение: мы можем использовать ref и для статистики, и чтобы варьировать поведение сервера в зависимости от того, что это была за ссылка (например, у нас есть ссылки, которые дают кредиты). Page id и user id можем использовать для того, чтобы быстро запуститься и сразу же сделать get user.

Но возникла другая проблема. К нам пришли менеджеры и сказали: «Ребята, у вас очень длинная ссылка, как люди будут её шарить».

Ну опять же, мы знаем решение… делать редирект… гм.

В принципе, в короткой ссылке мы сохраним все свойства, там будет и идентификатор страницы, и идентификатор контента, это можно сделать. Но как мы сделаем редирект?

Оказывается, на самом деле редирект сделать можно, но для этого нужно сделать маленький хак. Он достаточно простой. Ваш минификатор (это должен быть ваш личный минификатор) тоже объявляется диплинком в приложении, для него вы тоже делает apple-app-site-association, тоже пишете код, и, соответственно, оба домена являются диплинком.

Более того, это можно достаточно легко реализовать. Просто-напросто в коде, который открывает ваше приложение, он делает http-запрос на минификатор, смотрит куда идёт редирект, и использует длинную ссылку уже по стандартной логике.

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

В чём идея. Пользователь открывает http-ссылку AppsFlyer, они делают fingerprinting пользователя (то есть запоминает IP-адрес, модель iPhone, отсечение времени и так далее, какие-то свойства этого телефона), и редиректят в App Store. Пользователь устанавливает, и после этого в самом приложении AppsFlyer SDK сопоставляет устройства, недавно ходившие по ссылкам, с текущим устройством и делает вывод о том, какую ссылку надо открыть.

Соответственно, общая схема получилась такая. Минификатор редиректит в нашу ссылку, но если ни та, ни другая ссылка не подцепились приложением, значит, приложения нет, мы перенаправляем в AppsFlyer, он редиректит в App Store с трекингом и уже делает то, что нужно.

Пока мы занимались всей этой фигнёй, к нам пришел QA и сказал: «Ребята, я отправляю ссылку в Telegram, в Skype, в HipChat, ничего не работает». Мы: «Как, подождите, у нас все работает».

Оказалось, дело в SafariViewController.

История c SafariViewController совершенно трагическая. Дело вот в чём. По задумке Apple, если пользователь открывает Safari, вводит в адрес универсальную ссылку и нажимает Enter, то она не открывает приложение. Это логично: если ты пользователь, то не ожидаешь, что при нажатии Enter в браузере попадёшь в приложение.

Нелогична вторая часть: когда приложение открывает SafariViewController, происходит ровно то же самое, как если бы пользователь ввёл ссылку в адресную строку и нажал Enter. Нет никакого способа вместо открытия SafariViewController открыть универсальную ссылку.

Так мы увеличиваем этот список:

Если пользователь ввел ссылку в Safari сам или открыл SafariViewController — ничего не работает. Мы некоторое время думали, а потом подсмотрели придумали решение.

И оно достаточно логичное. Мы будем открывать с html-превью, а там по нажатию на кнопку переходить на ту же самую ссылку:

А поскольку в SafariViewController универсальные ссылки работают, не работает только открытие вместо SafariViewController универсальной ссылки, то это должно сработать, правда ведь?

Это решение тоже не работает, и вот почему:

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

Ну как что — ещё один хак, конечно.

Всё очень просто: мы делаем два домена, и оба регистрируем как универсальную ссылку. Вот как это выглядит.

Пользователь открывает m.badoo.com, а на кнопке у него будет mlink.badoo.com. Можно даже скопировать эту ссылку и прислать, она работает в обе стороны, у нас эти два домена работают как эквивалентные. Соответственно, если пользователь откроет mlink.badoo.com, у него будет на кнопке m.badoo.com. Победа.

Общая схема стала еще веселее.

Теперь у нас есть минификатор, который здесь не показан. Домен m.badoo.com по кнопке директит в mlink, mlink редиректит в AppsFlyer, и там уже происходит редирект в App Store с трекингом. Стало работать несколько лучше. По крайней мере, в Safari, в Telegram люди более-менее смогли как-то открыть.

Потом к нам приходит опять наш любимый QA, тоже уже грустный-грустный, и говорит: «Знаете, у нас стали происходить очень странные дела».

На некоторых устройствах, только на некоторых, почему-то универсальные ссылки не работали. Вообще.

Мы выяснили. При переходе по универсальной ссылке и запуске приложения в правом верхнем углу появлялась вот эта кнопочка:

И она делает две вещи. Во-первых, она открывает Safari. Во-вторых, она навсегда ломает диплинки для вашего приложения!

И потом единственный способ разломать их обратно — это сделать long tap на ссылке, нажать «open in Badoo», и тогда всё заработает обратно. Но ни один пользователь на моей памяти сам до этого ещё не додумался. Общая идея: не трогайте эту кнопку.

К счастью, в своей бесконечной мудрости, компания Apple этой осенью сделала нам фантастический подарок и удалила эту кнопку из iOS 11.

Но если вы поддерживаете iOS 9 и 10, помните об этой кнопке. А если вы вдруг поддерживаете ещё и iOS 9, помните, что apple-app-site-association не должен быть закрыт robots.txt, иначе он не будет работать, и это тоже проблема, с которой мы столкнулись.

Версионирование

В общем, мы набили бесконечное количество шишек. И тут случилось нечто, что набило нам вторую бесконечность шишек.

Компания Badoo решила сделать новую крутую функцию «Двойники» («Lookalikes»).

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

Настолько классная, что вот про эту схему наши менеджеры сказали, что нужно её поменять.

Почему? Мы очень хотим, чтобы когда наши пользователи делятся друг с другом двойниками, получатели должны увидеть это во что бы то ни стало, ничто не должно стоять на их пути. Поэтому, если приложение не установлено, мы больше не уводим в App Store. Мы открываем в мобильном вебе.

«Ооокей», сказали мы. В принципе, всё не так сложно, да? Мы говорим: «Хорошо, если в адресе будет другой page id, по нему мы будем редиректить в мобильный веб»

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

А это значит, что даже если приложение установлено, всё равно не факт, что мы должны его открывать. На самом деле схема должна выглядеть вот так:

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

Потому что на некоторых платформах, и на iOS в частности, тогда ещё не было даже возможности это открыть. И так мы подходим к теме версионирования.

Опять же, в идеале всё очень просто. У нас есть старая версия и новая версия. Задумка в том, что старая версия открывает только пути /u, а новая версия открывает /u и, допустим, /l/a от «look alikes».

Мы закатали рукава, думаем, сейчас всё сделаем. Но не тут-то было.

Помните, я в начале рассказывал как настраивать ссылки? Так вот, пути, которые поддерживает приложение, указываются в специальном файле на сайте. И только на сайте. Соответственно, как только мы добавляем туда новый путь, эта штуковина начинает открываться и в старом приложении, и в новом:

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

Но вам я могу сказать — ребята, подумайте об этом заранее. Вот здесь проверьте, что можете ли открывать URL, и в нужных случаях возвращайте false:

Тогда, если у пользователей старая версия приложения попытается открыть что-то, что оно не поддерживает, у вас вместо него будет открываться Safari. А как только новое поддержит, canOpenUrl начнёт возвращать true, и открываться будет уже оно.

Фантастический алгоритм невероятного успеха

Вот таких мы набили шишек, и настало время фантастического алгоритма, который я вам обещал. Важный момент: это фантастический алгоритм невероятного успеха для тех, кто не хочет использовать branch.io. К этому моменту мы узнали, что их бизнес стоит не то чтобы «ни на чём», кое-что они делают. Все эти хаки, которые мы сделали, в принципе, так или иначе поддерживаются в их SDK и инфраструктуре (но посоветовать я их не могу, потому что никогда не пробовал). Однако, если вы хотите всё сделать сами, если вам нравится всё кастомизировать или вам жалко денег, сейчас я расскажу, что делать.

Во-первых, придумайте схему и не забудьте, чтобы в схеме у вас был и ref, и страница. Тогда вы сможете красиво отображать каркасы и при этом вести любую статистику.

Второе. Очень простое, сделайте то, что Apple вам говорит: apple-app-site-association, домены и код.

И не забудьте в коде про canOpenUrl, проверьте, что вы реально можете это открыть, а иначе отправляйте в Safari.

Третье. Не забудьте про HTML-превью и Open Graph-теги, иначе всё будет выглядеть некрасиво. Кстати, хотя в этом докладе я тему Android не раскрываю, для фейсбука на Android нужно поддержать отдельные og-теги с андроидовской ссылкой. Кроме того для некоторых webview андроида можно оставить iframe с задержкой на перенаправление, вроде того о котором я рассказывал в начале.

В HTML-превью, опять же, не забудьте, что вам нужны два домена: один в кнопке, другой в самом превью.

Не забудьте про deferred deep linking (отложенный диплинк, то есть диплинк в ещё не установленное приложение). Здесь у нас AppsFlyer, а у вас может быть и ваше личное решение, которое позволит вам трекать установку и открывать контент после установки пользователем.

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

Но у меня есть ещё маленький бонус-трек, который я добавил в последний момент. Помните, как в ранней схеме, начиная с iOS 9, у нас выдавалась ошибка Safari в том случае, если у пользователя не установлено приложение? Это некрасиво, мы не хотим, чтобы пользователи видели ошибку. А помните этот слайд?

Мы рассылаем письма этим пользователям, и эта кнопка одновременно логинит пользователя (там есть токен для логина) и открывает контент.

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

Соответственно, если приложение установлено, мы можем использовать старую схему. К ней не нужно столько хаков, и она с большей вероятностью сработает в Gmail-клиенте (который использует SafariViewController) и других. А всех остальных отправим на мобильную версию. Соответственно, новая версия фантастического алгоритма невероятного успеха имеет ещё один пункт:

Минутка рекламы. Если доклад вам понравился и хочется ещё подобного — 20-21 апреля пройдёт Mobius 2018 Piter, и в его программе тоже много интересного!

Ссылка на страницу (Page Link)

Поле «Ссылка на страницу» позволяет выбрать 1 или более записей, страниц или пользовательских типов постов. Это поле полезно для обычной ссылки на пост, поскольку оно будет возвращать URL-адрес поста (постоянная ссылка). Чтобы получить больше данных из выбранного сообщения, пожалуйста, используйте поле объект записи.

История изменений

  • Добавлено «Разрешить ссылки на архивы» в 5.4.0

Настройки

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

Если настройка выбрана, то API будет возвращать массив вместо одного значения.

Использование в шаблоне

Базовый

В примере показано как получить и вывести ссылку на запись.

a href=" the_field('page_link'); ?>">Прочти это!a>

Несколько значений

В примере показано как получить и вывести множественную ссылку на запись.

 // переменные $urls = get_field('urls'); // проверяем на существование поля if( $urls ): ?> h3>Прочтите ещеh3> ul>  foreach( $urls as $url ): ?> li> a href=" echo $url ?>"> echo $url; ?>a> li>  endforeach; ?> ul>  endif; ?>

Продвинутое использование

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

 // переменные $post_id = get_field('url', false, false); // проверяем на существование поля if( $post_id ): ?> a href=" echo get_the_permalink($post_id); ?>"> echo get_the_title($post_id); ?>a>  endif; ?>
Смотрите также

Пишите на почту info@wp-book.ru
Получение ACF PRO 6
Покупка плагина
ACF PRO v6.2.4 + v5.12.6
— без лицензии и обновлений
— только архив с актуальной версией плагина
v6.2.4 (v5 по запросу)

— лицензия на неограниченное количество сайтов
— обновления без ограничений по времени
— архив с плагином

Старая цена: 22000 ₽ (249$)
Оплата Картой, ЮMoney
После оплаты, пожалуйста, напишите на почту info@wp-book.ru об оплате.

Интернет-магазин «acfwp.ru», расположенный на доменном имени acfwp.ru, именуемое в дальнейшем «Продавец», публикует Публичную оферту о продаже Товара дистанционным способом.

1.1. Публичная оферта (далее – «Оферта») — публичное предложение Продавца, адресованное неопределенному кругу лиц, заключить с Продавцом договор купли-продажи товара дистанционным способом (далее — «Договор») на условиях, содержащихся в настоящей Оферте, включая все Приложения.

1.2. Заказ Товара на сайте Интернет-магазина – позиции, указанные Покупателем из ассортимента Товара, предложенного к продаже, при оформлении заявки на приобретение Товара на сайте Интернет-магазина или через Оператора.

2.1. Заказ Покупателем Товара, размещенного на сайте Интернет-магазина означает, что Покупатель согласен со всеми условиями настоящей Оферты.

2.2. Администрация сайта Интернет-магазина имеет право вносить изменения в Оферту без уведомления Покупателя.

2.3. Срок действия Оферты не ограничен, если иное не указано на сайте Интернет-магазина.

2.4. Продавец предоставляет Покупателю полную и достоверную информацию о Товаре, включая информацию об основных потребительских свойствах Товара, месте изготовления, а также информацию о гарантийном сроке и сроке годности Товара на сайте Интернет магазина, в разделе Контакты.

3.1. Цена на каждую позицию Товара указана на сайте Интернет-магазина.

3.2. Продавец имеет право в одностороннем порядке изменить цену на любую позицию Товара.

3.3. В случае изменения цены на заказанный Товар Продавец обязуется в течение 10 дней проинформировать Покупателя об изменении цены Товара.

3.4. Покупатель вправе подтвердить либо аннулировать Заказ на приобретение Товара, если цена изменена Продавцом после оформления Заказа.

3.5. Изменение Продавцом цены на оплаченный Покупателем Товар не допускается.

3.6. Продавец указывает стоимость доставки Товара на сайте Интернет-магазина либо сообщает Покупателю при оформлении заказа Оператором.

3.7. Обязательства Покупателя по оплате Товара считаются исполненными с момента поступления Продавцом денежных средств.

3.8. Расчеты между Продавцом и Покупателем за Товар производятся способами, указанными на сайте Интернет-магазина в разделе Контакты

4.1. Заказ Товара осуществляется Покупателем сервис сайта Интернет-магазина acf.acfwp.ru.

4.2. При регистрации на сайте Интернет-магазина Покупатель обязуется предоставить следующую регистрационную информацию:

4.2.1. фамилия, имя, отчество Покупателя или указанного им лица (получателя);

4.2.2. адрес, по которому следует доставить Товар (если доставка до адреса Покупателя);

4.2.3. адрес электронной почты;

4.2.4. контактный телефон.

4.3. Наименование, количество, ассортимент, артикул, цена выбранного Покупателем Товара указываются в корзине Покупателя на сайте Интернет-магазина.

4.4. Если Продавцу необходима дополнительная информация, он вправе запросить ее у Покупателя. В случае не предоставления необходимой информации Покупателем, Продавец не несет ответственности за выбранный Покупателем Товар.

4.6. Принятие Покупателем условий настоящей Оферты осуществляется посредством внесения Покупателем соответствующих данных в регистрационную форму на сайте Интернет-магазина или при оформлении Заказа через Оператора. После оформления Заказа через Оператора данные о Покупателе регистрируются в базе данных Продавца. Утвердив Заказ выбранного Товара, Покупатель предоставляет Оператору необходимую информацию в соответствии с порядком, указанном в п. 4.2. настоящей Оферты.

4.7. Продавец не несет ответственности за содержание и достоверность информации, предоставленной Покупателем при оформлении Заказа.

4.8. Покупатель несет ответственность за достоверность предоставленной информации при оформлении Заказа.

4.9. Договор купли-продажи дистанционным способом между Продавцом и Покупателем считается заключенным с момента получения Продавцом сообщения о намерении Покупателя приобрести Товар.

  1. ДОСТАВКА И ПЕРЕДАЧА ТОВАРА ПОКУПАТЕЛЮ

5.1. Продавец оказывает Покупателю услуги по доставке Товара одним из способов указанных на сайте Интернет-магазина.

5.2. Если Договор купли-продажи товара дистанционным способом (далее – Договор) заключен с условием о доставке Товара Покупателю, Продавец обязан в установленный Договором срок доставить Товар в место, указанное Покупателем, а если место доставки Товара Покупателем не указано, то по месту его жительства или регистрации.

5.3. Место доставки Товара Покупатель указывает при оформлении Заказа на приобретение Товара.

5.4. Срок доставки Товара Покупателю состоит из срока обработки заказа и срока доставки.

5.5. Доставленный Товар передается Покупателю, а при отсутствии Покупателя — любому лицу, предъявившему квитанцию или иной документ, подтверждающий заключение Договора или оформление доставки Товара.

5.7. Информация о Товаре доводится до сведения Покупателя в технической документации, прилагаемой к Товару, на этикетках, путем нанесения маркировки или иным способом, принятым для отдельных видов товаров.

5.8. Сведения об обязательном подтверждении соответствия Товара представляются в порядке и способами, которые установлены законодательством Российской Федерации о техническом регулировании, и включают в себя сведения о номере документа, подтверждающего такое соответствие, о сроке его действия и об организации, его выдавшей.

Внимательно ознакомьтесь с текстом публичной оферты, и если Вы не согласны с каким-либо пунктом оферты, Вы вправе отказаться от покупки Товаров, предоставляемых Продавцом, и не совершать действий, указанный в п. 2.1. настоящей Оферты.

iOS: Открываем deep link’и, notification’ы и shortcut’ы

iOS: Открываем deep link’и, notification’ы и shortcut’ы

Один инструмент, чтобы править всеми

Доводилось ли вам в своём приложении реализовывать поддержку пуш-уведомлений (push notifications)? Если вы уже разрабатывали что-либо более сложное, чем “Hello, World!”, то, скорее всего, ваш ответ — “да”.

А что вы скажете про открытие шорткатов (пунктов меню быстрых действий)? Теперь, когда все новые iOS-устройства поддерживают 3d touch, эта функция уже больше чем просто приятное дополнение.

Поддерживает ли ваше приложение универсальные ссылки? Это относительно новая возможность, популярность которой в современных приложениях растёт с каждым днём. Даже если вы пока её не используете, возможно, сейчас самое время начать.

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

Но что если ваше приложение должно поддерживать все эти функции? Неужели необходимо реализовывать их в трёх отдельных компонентах?

Прежде чем мы продолжим, давайте определимся с терминами:

  1. Универсальные ссылки — это способ перехватывать некоторые URL’ы и вместо обработки URL’а в Safari, открывать соответствующую страницу приложения. Универсальные ссылки требуют определённой работы на уровне бэкенда, поэтому в данном уроке мы будем говорить конкретно о deep links (глубинных ссылках). Deep links работают по тому же принципу, но имеют дело конкретно с пользовательскими URL-схемами. Их применение не сильно отличается, поэтому для вас не составит труда добавить поддержку универсальных ссылок, если возникнет такая необходимость.
  2. Шорткаты предоставляют возможность запустить приложение сразу на определённом разделе (экране), исходя из выбранного пункта меню быстрых действий, которое возникает, если плавно надавить на иконку приложения. Эта функция работает на устройствах с включённым 3d-touch.
  3. Уведомления: когда вы касаетесь уведомления (неважно, локальное оно или удалённое), приложение запустится на определённой странице или выполнит определённые действия.

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

Apple назвали это опциями запуска (launching options), которые обрабатываются в AppDelegate методом didFinishLaunchingWithOptions.

Проблема в том, что применение всех опций запуска — достаточно запутанная задача, зачастую выливающаяся в сотни строк избыточного кода. Ещё сложнее реализовать переход приложения на передний план, а не его запуск. Обработка с шорткатов, deep link’ов и уведомлений происходит в разных методах делегата, и на первый взгляд не имеет ничего общего.

Для большей ясности скажу ещё раз: все эти функции служат одной цели — открыть определённый экран приложения.

Вопрос только в том, как заставить всех их работать оптимальным образом.

Подготовка проекта

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

  • Страница “Мои сообщения” (предпросмотр): доступна через шорткат
  • Сообщения (определённый чат): доступны через пуш-уведомления
  • Создать новый список: доступно через шорткат только для профиля хозяина (принимающей стороны)
  • Мои последние действия: доступны через шорткат
  • Запрос брони: доступен как через email-ссылку (deep link), так и через пуш-уведомление

Для начала создайте простой проект с ViewController’ом, NavigationBar’ом и кнопкой для смены профиля “Switch Profile”:

Подготовка проекта

Во View Controller’е будет содержаться текущий тип профиля и механизм переключения профилей.

Я не использую паттерны проектирования, поскольку это не относится к теме статьи. В реальном приложении лучше подобрать более подходящую структуру и не хранить ProfileType прямо во ViewController’е.

enum ProfileType: String < case guest = "Guest" // default case host = "Host" >class ViewController: UIViewController < var currentProfile = ProfileType.guest override func viewDidLoad() < super.viewDidLoad() configureFor(profileType: currentProfile) >@IBAction func didPressSwitchProfile(_ sender: Any) < currentProfile = currentProfile == .guest ? .host : .guest configureFor(profileType: currentProfile) >func configureFor(profileType: ProfileType) < title = profileType.rawValue >>

Один инструмент, чтобы править всеми

Для удобства далее в этой статье все переходы на определённый экран приложения я буду называть deep link, независимо от того, какой именно механизм применяется.

Теперь, когда у нас есть базовая структура и графический интерфейс, давайте организуем наш список deep link — элементов:

enum DeeplinkType < enum Messages < case root case details(id: String) >case messages(Messages) case activity case newListing case request(id: String) >

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

Далее мы можем создать для управления одиночку, где будут все deep linking options:

let Deeplinker = DeepLinkManager() class DeepLinkManager < fileprivate init() <>>

Добавьте опциональное свойство для хранения текущего DeeplinkType:

let Deeplinker = DeepLinkManager() class DeepLinkManager < fileprivate init() <>private var deeplinkType: DeeplinkType? >

Исходя из deeplinkType приложение будет определять, какой экран ему следует открыть:

let Deeplinker = DeepLinkManager() class DeepLinkManager < fileprivate init() <>private var deeplinkType: DeeplinkType? // check existing deepling and perform action func checkDeepLink() < >>

И при запуске, и при переходе на передний план приложение будет вызывать в appDelegate метод didBecomeActive. Здесь мы будем проверять наличие любых deep link’ов, которые сможем обработать:

func applicationDidBecomeActive(_ application: UIApplication) < // handle any deeplink Deeplinker.checkDeepLink() >

Всякий раз, когда приложение становится активным, оно будет проверять наличие deep link’а, который надлежит открыть. Создадим класс-навигатор, который будет открывать соответствующий экран приложения в зависимости от DeeplinkType:

class DeeplinkNavigator < static let shared = DeeplinkNavigator() private init() < >func proceedToDeeplink(_ type: DeeplinkType) < >>

В рамках данного примера мы будем просто отображать alert (окно предупреждения) с именем переданного DeeplinkType:

private var alertController = UIAlertController() private func displayAlert(title: String) < alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert) let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil) alertController.addAction(okButton) if let vc = UIApplication.shared.keyWindow?.rootViewController < if vc.presentedViewController != nil < alertController.dismiss(animated: false, completion: < vc.present(self.alertController, animated: true, completion: nil) >) > else < vc.present(alertController, animated: true, completion: nil) >> >

В методе proceedToDeeplink мы пробегаем по разным DeeplinkType и определяем, какой alert нужно отобразить:

func proceedToDeeplink(_ type: DeeplinkType) < switch type < case .activity: displayAlert(title: "Activity") case .messages(.root): displayAlert(title: "Messages Root") case .messages(.details(id: let id)): displayAlert(title: "Messages Details \(id)") case .newListing: displayAlert(title: "New Listing") case .request(id: let id): displayAlert(title: "Request Details \(id)") >>

Вернёмся в DeepLinkManager и используем навигатор, чтобы обработать deepLink:

// check existing deeplink and perform action func checkDeepLink() < guard let deeplinkType = deeplinkType else < return >DeeplinkNavigator().proceedToDeeplink(deeplinkType) // reset deeplink after handling self.deeplinkType = nil // (1) >

Не забудьте после использования сбросить значение deepLink к nil (1). В противном случае эта же ссылка будет использована, когда вы в следующий раз откроете приложение.

Теперь нам нужно просто проверить, имеются ли deep links (шорткаты, deep links или уведомления), которые следует обработать, определить их DeeplinkType и передать их DeepLinkManager.

Сперва мы должны произвести некоторую базовую подготовку для шорткатов, deep links и уведомлений.

Хоть мы и хотим, чтобы DeepLinkManager обрабатывал любые виды deep links, следует помнить об SRP (single responsibility principle — принцип единственной ответственности). Не будем смешивать процессы разбора различных типов deep links.

Шорткаты

Общепринятый метод создания статических шорткатов подразумевает использование файла info.plist. Вот короткий урок на эту тему, ознакомьтесь если вам интересно.

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

Во-первых, создадим класс ShortcutParser. Он будет отвечать исключительно за шорткаты.

class ShortcutParser < static let shared = ShortcutParser() private init() < >>

Затем нужно задать возможные ключи шорткатов:

enum ShortcutKey: String 

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

func registerShortcuts(for profileType: ProfileType) < let activityIcon = UIApplicationShortcutIcon(templateImageName: "Alert Icon") let activityShortcutItem = UIApplicationShortcutItem(type: ShortcutKey.activity.rawValue, localizedTitle: "Recent Activity", localizedSubtitle: nil, icon: activityIcon, userInfo: nil) let messageIcon = UIApplicationShortcutIcon(templateImageName: "Messenger Icon") let messageShortcutItem = UIApplicationShortcutItem(type: ShortcutKey.messages.rawValue, localizedTitle: "Messages", localizedSubtitle: nil, icon: messageIcon, userInfo: nil) UIApplication.shared.shortcutItems = [activityShortcutItem, messageShortcutItem] switch profileType < case .host: let newListingIcon = UIApplicationShortcutIcon(templateImageName: "New Listing Icon") let newListingShortcutItem = UIApplicationShortcutItem(type: ShortcutKey.newListing.rawValue, localizedTitle: "New Listing", localizedSubtitle: nil, icon: newListingIcon, userInfo: nil) UIApplication.shared.shortcutItems?.append(newListingShortcutItem) case .guest: break >>

Создаём activityShortcutItem и messageShortcutItem для обоих типов профилей. Если текущий пользователь — хозяин (принимающая сторона), то добавляем newListingShortcutItem. Для каждого шортката мы используем UIApplicationShortcutIcon (эта иконка будет отображаться рядом с текстом соответствующего пункта меню быстрых действий при плавном надавливании на иконку приложения).

Этот метод мы будем вызывать из нашего ViewController’а: когда пользователь меняет профиль, шорткаты будут перенастроены в соответствии с новым типом профиля:

func configureFor(profileType: ProfileType) 

Статические шорткаты лучше задавать через appDelegate, чтобы приложению не требовалось для составления меню быстрых действий загружать ViewController.

Скомпилируйте и запустите приложение, чтобы проверить его поведение:

  1. Плавно надавите на иконку приложения, чтобы увидеть меню быстрых действий (шорткаты)
  2. Смените профиль
  3. Снова плавно надавите на иконку приложения, чтобы увидеть другие варианты в меню

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

Перейдите в AppDelegate и добавьте метод делегата performActionForShortcutItem:

// MARK: Shortcuts func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) 

тот метод определит, когда сработает шорткат, и completionHandler сообщит делегату, следует ли его обрабатывать. Мы не будем помещать логику работы с шорткатами в appDelegate, вместо этого создадим метод в классе DeeplinkManager:

@discardableResult func handleShortcut(item: UIApplicationShortcutItem) -> Bool < deeplinkType = . // we will parse the item here return deeplinkType != nil >

Этот метод вначале попытается получить из шорткат-элемента DeeplinkType, и затем вернёт булево значение, обозначающее, удалась ли эта операция. При этом метод сохранит полученный из шортката тип в переменную deeplinkType.

@discardableResult говорит компилятору игнорировать неиспользуемое получаемое в методе значение, благодаря чему мы не получаем предупреждение “unused result”

Вернитесь в appDelegate и завершите метод performActionFor:

// MARK: Shortcuts func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) 

Последнее что мы должны сделать — это получить DeeplinkType на основе шорткат-элемента. У нас уже есть класс ShortcutParser, отвечающий за все действия, относящиеся к шорткату. Добавьте ещё один метод:

func handleShortcut(_ shortcut: UIApplicationShortcutItem) -> DeeplinkType? < switch shortcut.type < case ShortcutKey.activity.rawValue: return .activity case ShortcutKey.messages.rawValue: return .messages(.root) case ShortcutKey.newListing.rawValue: return .newListing default: return nil >>

Теперь вернитесь в DeeplinkManager и дополните метод handleShortcut:

@discardableResult func handleShortcut(item: UIApplicationShortcutItem) -> Bool 

Вот и вся подготовка, необходимая для обработки шорткатов! Давайте ещё раз пошагово пройдёмся по ней. Когда мы касаемся иконки шортката:

  1. Активность шортката вызывает метод performActionForShortcutItem класса appDelegate
  2. Метод performActionForShortcutItem передаёт ShortcutItem в DeeplinkManager
  3. DeeplinkManager пробует получить DeeplinkType на основе ShortcutItem, используя ShortcutParser
  4. В applicationDidBecomeActive мы проверяем наличие каких-либо DeeplinkTypes
  5. Если DeeplinkType существует (т.е. шаг 3 выполнен успешно), мы выполняем соответствующее действие используя DeeplinkNavigator
  6. Как только шорткат был обработан, DeeplinkManager сбрасывает значение текущего шорткат-элемента к nil, чтобы не использовать его повторно

Запустите приложение и проверьте как оно работает на двух сценариях: когда приложение запускается из закрытого состояния и когда оно вызывается из фонового режима. В обоих случаях вы должны увидеть alert с соответствующим сообщением:

Deeplink’и, которые мы хотим обрабатывать, будут иметь следующий формат:

Сохраните эти URL’ы в “Заметках” на вашем тестовом устройстве. Если сейчас коснуться любой из этих ссылок, ничего не произойдёт.

Если бы это была универсальная ссылка, по касанию бы открылся браузер с соответствующим URL’ом.

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

AppDelegate обнаружит, было ли приложение открыто через deeplink-URL и если это так, инициирует метод openUrl:

// MARK: Deeplinks func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool 

Возвращаемое значение сообщает делегату, следует ли открывать URL.

Если вы хотите реализовать поддержку универсальных ссылок (представлены в iOS9), добавьте также следующий метод делегата:

// MARK: Universal Links func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool < if userActivity.activityType == NSUserActivityTypeBrowsingWeb < if let url = userActivity.webpageURL < >> return false >

ContinueUserActivity также вызывается когда вы запускаете приложение через элементы Spotlights (в рамках данной статьи это не обсуждается).

Следуя тому же шаблону, что мы использовали для шорткатов, создаём DeeplinkParser:

class DeeplinkParser < static let shared = DeeplinkParser() private init() < >>

Чтобы парсить Deeplink, создадим метод, который принимает URL и возвращает опциональный DeeplinkType:

func parseDeepLink(_ url: URL) -> DeeplinkType? < guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let host = components.host else < return nil >var pathComponents = components.path.components(separatedBy: "/") // the first component is empty pathComponents.removeFirst() switch host < case "messages": if let messageId = pathComponents.first < return DeeplinkType.messages(.details(id: messageId)) >case "request": if let requestId = pathComponents.first < return DeeplinkType.request(id: requestId) >default: break > return nil >

Имейте в виду, что этот метод разбора будет зависеть от структуры ваших deeplink’ов, и моё решение — просто пример возможной реализации.

Теперь мы можем подключить этот парсер к нашему основному deeplink-классу. Добавьте этот метод в DeeplinkManager:

@discardableResult func handleDeeplink(url: URL) -> Bool 

В appDelegate дополним методы openUrl и continueUserActivity:

// MARK: Deeplinks func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool < return Deeplinker.handleDeeplink(url: url) >// MARK: Universal Links func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool < if userActivity.activityType == NSUserActivityTypeBrowsingWeb < if let url = userActivity.webpageURL < return Deeplinker.handleDeeplink(url: url) >> return false >

Теперь нужно сделать ещё кое-что: сообщить нашему приложению, какой именно тип ссылок ему следует обнаруживать. Добавьте этот сниппет в файл info.plist (правый клик по info.plist -> Open As -> Source Code):

После добавления этого кода попробуйте открыть plist как Property List. Вы должны увидеть следующие строки:

Так мы сообщаем приложению, что оно должно выявлять только ссылки с URL’ом “deeplinkTutorial”.

Давайте ещё раз пробежимся по всем шагам:

  1. Пользователь касается deeplink’а вне приложения
  2. AppDelegate обнаруживает ссылку и запускает метод делегата openUrl (или метод делегата ContinueUserActivity для универсальных ссылок)
  3. Метод openUrl передаёт ссылку в Deeplink Manager
  4. Deeplink Manager пробует получить DeeplinkType используя DeeplinkParser
  5. В методе applicationDidBecomeActive мы производим проверку наличия DeeplinkType’ов
  6. Если существует DeeplinkType (т.е. шаг 4 завершился успешно), мы выполняем соответствующее действие с помощью DeeplinkNavigator’а
  7. Как только шорткат был обработан, DeeplinkManager сбрасывает значение текущего шорткат-элемента к nil, чтобы не использовать его повторно

Запустите приложение, и попробуйте открыть ссылку, которую сохранили в заметках:

Уведомления

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

Для отправки APNS-уведомлений вы можете использовать локальный сервер и PusherAPI (это один из наиболее простых способов).

Мы затронем только часть между тапом по пуш-уведомлению и получением видимого результата.

Когда приложение закрыто или работает в фоновом режиме, тап по уведомлению запустит в appDelegate метод didReceiveRemoteNotification:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 

Также этот метод будет инициирован когда приложение примет пуш-уведомление, будучи запущенным на переднем плане. Поскольку нас интересуют только сценарии, когда необходимо открыть приложение на определённой странице, мы не будем рассматривать обработку уведомлений на переднем плане.

Для обработки уведомлений мы создадим NotificationParser:

class NotificationParser < static let shared = NotificationParser() private init() < >func handleNotification(_ userInfo: [AnyHashable : Any]) -> DeeplinkType? < return nil >>

Теперь мы можем связать этот метод с Deeplink Manager’ом:

func handleRemoteNotification(_ notification: [AnyHashable: Any]) 

И дополнить метод класса appDelegate didReceiveRemoteNotification:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 

Последним шагом будет дополнение метода разбора в NotificationParser’е. Он может различаться в зависимости от структуры ваших уведомлений, но основная техника разбора будет аналогична:

func handleNotification(_ userInfo: [AnyHashable : Any]) -> DeeplinkType? < if let data = userInfo["data"] as? [String: Any] < if let messageId = data["messageId"] as? String < return DeeplinkType.messages(.details(id: messageId)) >> return nil >

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

apns: <
aps: <
alert: <
title: «New Message!»,
subtitle: «»,
body: «Hello!»
>,
«mutable-content»: 0,
category: «pusher»
>,
data: <
«messageId»: «1»
>
>

В этом примере для отправки уведомлений я использую локальный NodeJS-сервер и Pusher API. Настройка занимает всего несколько минут и требует базового уровня владения NodeJS или навыков копирования-вставки.

Запустите приложение, сверните его в фон и отправьте уведомление. Когда получите уведомление, коснитесь его, чтобы открыть приложение:

Вот что происходит за кадром:

  1. Когда вы касаетесь уведомления, приложение инициирует метод делегата didReceiveRemoteNotification
  2. didReceiveRemoteNotification передаёт информацию из уведомления в Deeplink Manager
  3. Deeplink Manager пробует получить Deeplink Type по Notification User Info используя NotificationParser
  4. В методе applicationDidBecomeActive мы производим проверку наличия DeeplinkType’ов
  5. Если существует DeeplinkType (т.е. шаг 3 завершился успешно), мы выполняем соответствующее действие с помощью DeeplinkNavigator’а
  6. Как только шорткат был обработан, DeeplinkManager сбрасывает значение текущего шорткат-элемента к nil, чтобы не использовать его повторно

Этот подход позволяет легко добавлять или изменять любые элементы и не требует значительных изменений кода. И, что особенно важно, вы можете разбирать deeplink, соответствующим парсером. Например, чтобы добавить “Новый запрос” в обработчик уведомлений, вам всего лишь нужно изменить метод handleNotification в NotificationParser’е:

func handleNotification(_ userInfo: [AnyHashable : Any]) -> DeeplinkType? < if let data = userInfo["data"] as? [String: Any] < if let messageId = data["messageId"] as? String < return DeeplinkType.messages(.details(id: messageId)) >if let requestId = data["requestId"] as? String < return DeeplinkType.request(.details(id: requestId)) >> return nil >

Обратите внимание, мы не используем didFinishLaunchingWithOptions ни для одного из этих deeplink’ов. Всё обрабатывается средствами applicationDidBecomeActive.

Поздравляю! Теперь ваше приложение оснащено унифицированной поддержкой шорткатов, deeplink’ов и уведомлений!

Смотрите итоговый проект здесь.

Я также пишу в “American Express Engineering Blog”. Оцените мои другие работы и работы моих талантливых коллег на AmericanExpress.io.

Что за покупка? ��

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

ниже текст Сообщения

Недостаточно средств на счете для оплаты покупки в Apple. Номер заказа Apple MVG933SL7Z стоимостью 649,00 руб, комиссия 0 руб. На вашем счете 0,00 руб. Пополните баланс без комиссии через мобильное приложение «Мой Билайн» https://mybee.page.link/mkf или другим удобным способом, а затем повторите запрос. Команда Билайн

Show more Less

iPhone 11, iOS 14

Posted on Jan 23, 2021 4:27 AM

Me too (1645) Me too Me too (1645) Me too Reply
Question marked as Best reply
User level: Level 10
726,137 points

Posted on Jan 23, 2021 7:07 AM

Щелкните здесь для получения информации. Если в статье это не рассматривается, воспользуйтесь ссылкой в разделе 5.

Show more Less

Similar questions

Расширение семейного доступа. Реализуйте пожалуйста! Не знаю пишу там или нет, но почему бы не реализовать идею подписки расширения семейного доступа и добавление членов в семью свыше 6 — ти человек за дополнительную плату, можно даже кратную стоимости минимальной подписке отдельной учетной записи icloud. Очень не удобно когда из за того что у тебя семья больше 6-ти человек, приходится создавать и следить за несколькими семейными аккаунтами.

Подписка на приложение Литрес 19 января в приложении Литрес мне было предложено приобрести абонемент. По условию: «первый месяц — бесплатно, затем 299 руб. в месяц». Я ввела данные своей карты, и в этот же день с моего счёта было списано 299 рублей, несмотря на прописанные условия бесплатного месяца. Я связалась со службой поддержки Литрес, и они сказали обсудить вопрос о возврате денежных средств со службой поддержки Apple. Прошу Вас вернуть мне списанные денежные средства. Скриншот чека об операции прилагаю [Image Edited by Moderator to Remove Personal Information]

Вы можете сделать так, чтобы распознованием внимания не убавляла звук в некоторых приложениях? извините меня компания эпл. Но вы могли бы сделать так, что бы Распознование внимания не убавляла звук в приложениях, либо же пожалуйста сделайте так, что бы эта фишка была как отдельно настройка. Допустим убавлять звук, когда айфон видит вас и тд. Чтобы оно было отдельно. Если вы это сделаете, буду очень признателен. ❤️

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *