Внедряем MapKit Яндекс Карт в iOS приложение
Всем привет! Меня зовут Серёжа, и я занимаюсь веб-разработкой и разработкой на iOS вот уже 5 лет. На одном из проектов мне поставили задачу: внедрить Яндекс Карты в приложение. Однако я столкнулся с тем, что в открытом доступе мало нужной и полезной информации об этой теме. Эта статья — мои способы решения задач на Swift с использованием Yandex MapKit. Делюсь с вами своим опытом!
В статье я рассказываю о том, как:
- Показать точку на карте по координатам или адресу;
- Выбрать определённую точку и вывести её данные;
- Отобразить большое количество точек по адресу.
Подготовка
Для создания интерфейса я использовал программный метод (UIKit)
Для работы с Yandex MapKit на iOS необходимы:
- MacOS Catalina и выше
- Xcode (желательно 12.1 и выше)
- Homebrew
- Ruby
- CocoaPods
- Ключ для доступа к API Яндекса (можно запросить бесплатно)
- Целевая версия iOS 12 и выше (iOS 13+ для Apple Silicon) Учтите, что требования могут измениться. Статья написана в июне 2023 года.
Во время работы на процессорах Apple Silicon, на версии MapKit SDK ниже 4.3.0 возможны вылеты приложения во время открытия карты. Особенно во время отладки. Пример ошибки:

С версией MapKit SDK 4.3.0 (2 марта 2023) появилось следующее обновление:
Для эмуляторов с процессором M1 карта автоматически переключается на использование Metal API.
Источник: Версии MapKit – Яндекс MapKit. Руководство разработчика
После обновления до последней версии вылеты без причины прекратились. На реальном устройстве проблем тоже не возникло.
Демо-приложение Яндекса
Демо-приложение MapKit позволяет использовать возможности Яндекс.Карт в мобильных приложениях для iOS и Android. Здесь есть разделы:

- Показ карты;
- Добавление объектов и пинов;
- Месторасположение пользователя;
- Пробки;
- Панорама;
- Поиск и подсказки;
- Выбор объекта по клику.
На всякий случай, оставлю ссылки на руководство для MapKit от разработчика и демо-версию приложения:
Работа с картой
Добавление карт в проект
Для начала необходимо добавить зависимость в CocoaPods, чтобы загрузить библиотеку.
Если в проекте не подключён CocoaPods, то нужно инициализировать его. Для этого в терминале в корневой директории прописываем pod init .
После инициализации, проект необходимо открывать с помощью нового файла проекта с расширением .xcworkspace (пример: MapKitDemo.xcworkspace )
pod 'YandexMapsMobile', '4.3.1-full’
Нужно учесть, что есть две версии библиотеки: lite и full. Lite-версия позволяет работать с онлайн и оффлайн картами, показывает локацию и пробки. Full-версия предоставляет маршруты автомобилей, велосипедов, пешеходов, общественного транспорта; поиск; подсказки; геокодирование; отображение панорам.
После добавления зависимостей, появляется возможность сделать import YandexMapsMobile . Добавляем следующие строчки в главный метод AppDelegate :
/* imports */ import YandexMapsMobile @main class AppDelegate: UIResponder, UIApplicationDelegate < func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) ->Bool < /* code */ /* Init YandexMaps MapKit */ YMKMapKit.setApiKey("your-api-key") YMKMapKit.setLocale("ru_RU") YMKMapKit.sharedInstance() /* code */ return true >>
Создаём базовый UIView для удобства использования в других модулях:
import UIKit import YandexMapsMobile class YBaseMapView: UIView < @objc public var mapView: YMKMapView! required init?(coder aDecoder: NSCoder) < super.init(coder: aDecoder) setup() >override init(frame: CGRect) < super.init(frame: frame) setup() >private func setup() < // OpenGl is deprecated under M1 simulator, we should use Vulkan mapView = YMKMapView(frame: bounds, vulkanPreferred: YBaseMapView.isM1Simulator()) mapView.mapWindow.map.mapType = .map >static func isM1Simulator() -> Bool < return (TARGET_IPHONE_SIMULATOR & TARGET_CPU_ARM64) != 0 >>
Создание карты и добавление точки
Добавляем заранее созданный view в новый компонент, чтобы отобразить карту:
final class YandexMapSampleViewController: UIViewController < // 1. Создать элемент lazy var mapView: YMKMapView = YBaseMapView().mapView override func viewDidLoad() < super.viewDidLoad() // 2. Добавить в родительский view во viewDidLoad() view.addSubview(mapView) // 3. Настроить constraints. Приведён пример со SnapKit mapView.snp.makeConstraints < $0.leading.trailing.top.equalToSuperview() $0.bottom.equalTo(view.safeAreaLayoutGuide) >// 4. Вызов функции добавления точки на карту self.addPlacemarkOnMap() > >
Теперь создаём отдельную функцию для добавления точки на карту:
func addPlacemarkOnMap() < // Задание координат точки let point = YMKPoint(latitude: 47.228836, longitude: 39.715875) let viewPlacemark: YMKPlacemarkMapObject = mapView.mapWindow.map.mapObjects.addPlacemark(with: point) // Настройка и добавление иконки viewPlacemark.setIconWith( UIImage(named: "map_search_result_primary")!, // Убедитесь, что у вас есть иконка для точки style: YMKIconStyle( anchor: CGPoint(x: 0.5, y: 0.5) as NSValue, rotationType: YMKRotationType.rotate.rawValue as NSNumber, zIndex: 0, flat: true, visible: true, scale: 1.5, tappableArea: nil ) ) >

Интерактивные точки на карте
Чтобы сделать точку кликабельной, необходимо реализовать интерфейс YMKMapObjectTapListener и указать его при создании точки. Я использую расширение классов Swift, которое упрощает понимание кода и не загромождает основной класс.
В реализации прослушивателя событий нам предлагают создать имплементацию для одной функции:
extension YandexMapSampleViewController: YMKMapObjectTapListener < func onMapObjectTap(with mapObject: YMKMapObject, point: YMKPoint) ->Bool < // your code here >>
extension YandexMapSampleViewController: YMKMapObjectTapListener < func onMapObjectTap(with mapObject: YMKMapObject, point: YMKPoint) ->Bool < guard let placemark = mapObject as? YMKPlacemarkMapObject else < // Сценарий на случай ошибки return false >// Сценарий на случай успеха. Бизнес-логику добавляют сюда. // Пример self.focusOnPlacemark(placemark) return true > func focusOnPlacemark(_ placemark: YMKPlacemarkMapObject) < // Поменять расположение камеры, чтобы сфокусироваться на точке mapView.mapWindow.map.move( with: YMKCameraPosition(target: placemark.geometry, zoom: 18, azimuth: 0, tilt: 0), animationType: YMKAnimation(type: YMKAnimationType.smooth, duration: duration), cameraCallback: nil ) >>
Чтобы прослушиватель событий работал, нужно указать его при создании точки:
// Создание переменной точки не показано (см. пример выше) viewPlacemark.addTapListener(with: self)
После этого любой клик по точке будет следовать через созданную функцию.
Пользовательские данные точки
Задача: приблизить карту и отобразить данные на экране с помощью нажатия на точку на карте. Решение: при создании каждой точки нужно заполнить переменную YMKPlacemarkMapObject.userData нужными данными. Это может быть строка с названием/адресом, или же другой объект любого типа.
let viewPlacemark: YMKPlacemarkMapObject = mapView.mapWindow.map.mapObjects.addPlacemark(with: point) /* Здесь будет код, добавляющий иконку к точке */ viewPlacemark.userData = "Точка на карте"
Чтобы использовать переданные точки, нужно обратиться к ним и, при необходимости, привести к желаемому типу данных.
Пример функции фокусировки камеры на точке:
func focusOnPlacemark(placemark: YMKPlacemarkMapObject) < // Поменять расположение камеры, чтобы сфокусироваться на точке mapView.mapWindow.map.move( with: YMKCameraPosition(target: placemark.geometry, zoom: 18, azimuth: 0, tilt: 0), animationType: YMKAnimation(type: YMKAnimationType.smooth, duration: duration), cameraCallback: nil // Опциональный callback по завершению работы камеры ) if let placemarkName: String = placemark.userData as? String < // Пример self.displaySelectedPlacemarkName(placemarkName) >else < // do nothing >> func displaySelectedPlacemarkName(_ placemarkName: String) < // your code here >
Пример заранее созданной панели для отображения данных точки:

Поиск по адресу
Для поиска по адресу (прямое геокодирование), используйте full версию MapKit ( lite не подойдёт).
Делаем так: отправляем адрес и получаем в ответ координаты, которые можно показать на карте.
Пример класса с поиском:
final class YandexMapsAddressSearchInteractor < lazy var searchManager: YMKSearchManager? = YMKSearch.sharedInstance().createSearchManager(with: .combined) var searchSession: YMKSearchSession? // Окно поиска let BOUNDING_BOX = YMKBoundingBox( southWest: YMKPoint(latitude: 55.55, longitude: 37.42), northEast: YMKPoint(latitude: 55.95, longitude: 37.82) ) func searchAddress(_ address: String?, completion: @escaping(YMapsSearchVoid)) < guard let address = address else < return >searchManager = YMKSearch.sharedInstance().createSearchManager(with: .combined) // Callback функция, которая выполняется по завершению поиска let responseHandler = < (searchResponse: YMKSearchResponse?, error: Error?) ->Void in if let response = searchResponse < // Передаваемая callback функция. Обрабатывать результат нужно здесь completion(response) >else < let searchError = (error! as NSError).userInfo[YRTUnderlyingErrorKey] as! YRTError var errorMessage = L10n.ymapsUnknownError if searchError.isKind(of: YRTNetworkError.self) || searchError.isKind(of: YMKSearchCacheUnavailableError.self) < errorMessage = L10n.ymapsNetworkError >else if searchError.isKind(of: YRTRemoteError.self) < errorMessage = L10n.ymapsServerError >// showErrorMessage(errorMessage) > > searchSession = searchManager!.submit( withText: address, geometry: YMKGeometry(boundingBox: BOUNDING_BOX), searchOptions: YMKSearchOptions(), responseHandler: responseHandler ) > >
Пример использования поиска:
func showAddressOnMap(_ address: String) < interactor?.searchAddress(address) < [weak self] response in // Обработка только первого результата из ответа if response.collection.children.count >0 < let searchResults: [YMKGeoObjectCollectionItem] = response.collection.children if let mapObject = searchResults[0].obj < if let point = mapObject.geometry.first?.point < self?.view?.addPlacemarkOnMap(point) >> > else < // self?.showSearchError("No results found") >> >
Для более детального изучения поиска Яндекс Карт советую ознакомиться с этой статьёй. В ней описана работа поиска и его модулей с примерами. Учтите, что большинство примеров приведены для работы с Android, но они актуальны и для iOS.
Вот, как описывается параметр geometry в этой статье:
Параметр geometry чуть более хитрый. В зависимости от того, какая именно геометрия передана, поиск будет вести себя по-разному:
Если передать точку, то поиск будет производиться в небольшом окне рядом с этой точкой. Если передать прямоугольное окно (BoundingBox) или полигон из четырёх точек, то оно будет использовано как окно поиска. Простой пример такого окна – видимая область карты. Наконец, если передать полилинию, то описывающее её окно будет использовано как окно поиска, а ранжирование будет производиться с учётом этой полилинии.
Работа со множеством точек
Если нужно показать множество точек за один раз, добавляем несколько сотен (или тысяч) точек на карту по примеру, описанному выше. Но как большое количество точек будет выглядеть на карте? И что делать, если есть только список адресов?
Кластеризация
Чтобы визуально не нагружать карту, применим кластеризацию точек.

Реализация похожа на обычное добавление точки на карту. Разница в том, что вместо mapView.mapWindow.map.mapObjects.addPlacemark() используется clusteredColletion.addPlacemark() , создаваемая с помощью mapView.mapWindow.map.mapObjects.addClusterizedPlacemarkCollection() . После добавления точек в кластер(-ы), вызываем функцию clusteredColletion?.clusterPlacemarks() для правильного отображения кластеров на карте.
final class YandexMapClusterSampleViewController: UIViewController < // Переменная коллекции-кластера private var clusteredColletion: YMKClusterizedPlacemarkCollection? = nil func viewDidLoad() < super.viewDidLoad() // Инициализация коллекции-кластера clusteredColletion = mapView.mapWindow.map.mapObjects.addClusterizedPlacemarkCollection(with: self) renderClusters() >// Фукнция для добавлении точки в кластер func addPlacemarkToCluster(point: YMKPoint) < guard let clusteredColletion = clusteredColletion else < return >// Добавление точки в кластер let viewPlacemark: YMKPlacemarkMapObject = clusteredColletion.addPlacemark(with: point) // Настройка и добавление иконки viewPlacemark.setIconWith( UIImage(named: "map_search_result_primary")!, // Убедитесь, что у вас есть иконка для точки style: YMKIconStyle( anchor: CGPoint(x: 0.5, y: 0.5) as NSValue, rotationType: YMKRotationType.rotate.rawValue as NSNumber, zIndex: 0, flat: true, visible: true, scale: 1.5, tappableArea: nil ) ) > // Фукнция для отображения кластера private func renderClusters() < self.clusteredColletion?.clusterPlacemarks(withClusterRadius: 60, minZoom: UInt(OutletMapView.DEFAULT_CAMERA_ZOOM)) >>
Чтобы кастомизировать визуальную часть (к примеру, как на скриншоте), нужно реализовать функцию, которая динамически создаёт новое изображение кластера в зависимости от количества точек, сгруппированных в нём. Это делается следующим образом:
extension YandexMapClusterSampleViewController: YMKClusterListener < func onClusterAdded(with cluster: YMKCluster) < cluster.appearance.setIconWith(clusterImage(cluster.size)) cluster.addClusterTapListener(with: self) >func clusterImage(_ clusterSize: UInt) -> UIImage < let scale = UIScreen.main.scale let text = (clusterSize as NSNumber).stringValue let font = UIFont.systemFont(ofSize: FONT_SIZE * scale) let size = text.size(withAttributes: [NSAttributedString.Key.font: font]) let textRadius = sqrt(size.height * size.height + size.width * size.width) / 2 let internalRadius = textRadius + MARGIN_SIZE * scale let externalRadius = internalRadius + STROKE_SIZE * scale let iconSize = CGSize(width: externalRadius * 2, height: externalRadius * 2) UIGraphicsBeginImageContext(iconSize) let ctx = UIGraphicsGetCurrentContext()! ctx.setFillColor(Asset.green.color.cgColor) ctx.fillEllipse(in: CGRect( origin: .zero, size: CGSize(width: 2 * externalRadius, height: 2 * externalRadius))); ctx.setFillColor(UIColor.white.cgColor) ctx.fillEllipse(in: CGRect( origin: CGPoint(x: externalRadius - internalRadius, y: externalRadius - internalRadius), size: CGSize(width: 2 * internalRadius, height: 2 * internalRadius))); (text as NSString).draw( in: CGRect( origin: CGPoint(x: externalRadius - size.width / 2, y: externalRadius - size.height / 2), size: size), withAttributes: [ NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: UIColor.black]) let image = UIGraphicsGetImageFromCurrentImageContext()! return image >>
Пример кода, который приближает камеру при нажатии на кластер:
extension YandexMapClusterSampleViewController: YMKClusterTapListener < func onClusterTap(with cluster: YMKCluster) ->Bool < mapView.mapWindow.map.move( with: YMKCameraPosition(target: cluster.appearance.geometry, zoom: 20, azimuth: 0, tilt: 0), animationType: YMKAnimation(type: YMKAnimationType.smooth, duration: 0,4), cameraCallback: completion ) return true >>
Рекомендую дополнительно реализовать интерфейс YMKMapCameraListener с его методом onCameraPositionChanged и хранить значение cameraPosition.zoom, чтобы можно было вычислять необходимый зум камеры и менять его без резких скачков.
Вы супер! Теперь карта визуально не перегружена множеством точек!
Множественное геокодирование
Если у точек нет заранее сохранённых координат, нужно искать их по адресу. В случае работы с одной-двумя точками запрашивать API Яндекса с клиента вполне обычное дело, но когда речь идёт о сотнях – или даже тысячах точек – разумным решением будет использовать множественное геокодирование на стороне сервера. Выполнять тысячи запросов с мобильного устройства невыгодно с точки зрения производительности и лимита запросов согласно лицензии.
Множественное геокодирование работает так: сервер получает список адресов, и по каждому делает запрос к HTTP Геокодеру Яндекса.
На момент написания статьи, Яндекс опубликовал всего один пост на эту тему (ещё в 2014 году). К сожалению, в своей документации Яндекс прилагает всего один пример и библиотеку, написанную для Node.js. Для применения множественного геокодирования в приложениях на других языках программирования придётся реализовывать вызовы к API вручную.
Для работы с HTTP Геокодером потребуется ключ разработчика для доступа к API JavaScript и Геокодеру.
Ниже приведён код, использующий библиотеку node-multi-geocoder .
Пример из GitHub не работал из-за параметра apikey , который был описан как key . Я проверил.
let geocoder = new MultiGeocoder(< provider: 'yandex', coordorder: 'latlong'>); let provider = geocoder.getProvider(); let getRequestParams = provider.getRequestParams; provider.getRequestParams = function() < let result = getRequestParams.apply(provider, arguments); result.apikey = "your-key-here"; return result; >let geoResponse = await geocoder.geocode([ "address 1", "address 2", "address 3", "address 4" ]);
Аналоги библиотеки
Нашёл репозиторий с аналогичным функционалом, написанном на C#:
Нюансы пользовательского соглашения
Пользоваться API Яндекс Карт можно бесплатно, но в таком случае запрещается сохранять результат работы геокодера. Другими словами, если искать координаты по адресу, то координаты сохранять к себе в базу данных запрещено.
Коммерческая лицензия позволяет снять это ограничение:
Коммерческая лицензия делится на стандартную (от 120 тыс. рублей в год) и расширенную (от 620 тыс. рублей в год). В стандартной лицензии запрещено сохранять или изменять данные, полученные с помощью API. Источник
В качестве компромисса, Яндекс предлагает кэшировать результат максимум на 30 дней. Советую обращаться в поддержку Яндекса с вашим конкретным случаем и уточнять юридические вопросы.
Заключение
MapKit SDK Яндекс Карт для iOS открывает полезные возможности, которые важно уметь применять iOS разработчику в России. Документация Яндекса недавно обновилась, и теперь там можно найти описание классов и методов на Swift, но понятных примеров не так много. В своей статье я раскрыл основные подходы работы с MapKit от Яндекса и поделился личном опытом. Надеюсь, статья вам поможет!
Почему в iOS 12 в режиме CarPlay не появились Яндекс.Карты
Во-первых, не забывайте, что вы используете бета-версию прошивки iOS 12. Это значит, что в процессе эксплуатации можете столкнуться с непредвиденными проблемами и глюками. Работоспособность всех модулей системы гарантирована лишь с финальным релизом системы в сентябре.
Во-вторых, разработчики сторонних картографических сервисов должны доработать свои приложения для работы с обновленным CarPlay. Возможно, интересующее Вас приложение до сих пор не обновлено.

В-третьих, некоторые программные фишки, которые представители Apple обещают на WWDC в июне, появляются в системе через полгода или даже позже. Подобное случилось, например, с AirPlay второго поколения.
Анонсировали фишку еще в начале прошлого лета, а добавили в iOS лишь весной нынешнего года.
Никто не удивится, если и с CarPlay случится нечто подобное.
На данный момент не стоит ждать все обещанные фишки, которые показывают на WWDC. Конференция направлена в первую очередь на разработчиков ПО, чтобы те знали, как развивать свои программы и сервисы в ближайшее время.
Пользователи получают лишь намек на возможности, которые появятся в iPhone в течение следующего года. Большинство фишек появляются с первой бета-версии iOS, некоторые добавляют лишь в сентябре, а некоторые могут попасть в систему намного позже.
Яндекс.Карты 13.7.3
![]()
![]()
Яндекс Карты для iOS – утилита со встроенным интерфейсом интерпретации схематических и спутниковых карт местности. Имеет навигационный интерфейс и протоколы позиционирования GPS/Глонасс, благодаря чему пользователи могут узнавать свое местоположение в реальном времени с точностью до нескольких метров при движении, и с точностью до метра при стоянии на месте.
Вдобавок, с помощью приложения можно просматривать панорамы интересующих улиц, получать актуальною информацию о состоянии дорожного трафика, прокладывать маршруты, советоваться с другими участниками движения и т.д.
Функционал Яндекс Карт для iOS
- Интерпретирует и выводит на экран девайса схематические и спутниковые карты местности;
- Определяет местоположение по GPS/Глонасс;
- Позволяет рассчитывать, как прямые маршруты, так и маршруты с пересадками;
- Подгружает (опционально) в режиме реального времени актуальною информацию о степени загруженности дорожного трафика, проводимых ремонтных работах, случившихся ДТП, природных и техногенных катаклизмах, гражданских волнениях и прочую полезную информацию;
- Позволяет оперативно обмениваться информацией с другими пользователями;
- Воспроизведение панорам и виртуальных 3D Туров.
Особенности программы
- Более 200 предустановленных карт местности РФ, Беларуси, Казахстана, Украины и Турции;
- Интегрированная функция создания фото и видео панорам;
- Возможность самостоятельного движения в режиме виртуального 3D тура в любом интересующем направлении;
- Наличие настраиваемых фильтров информационной ленты;
- Функция импорта/экспорта избранных данных на любое устройство с установленными Яндекс картами;
- Поддержка всех функций приложения при использовании Apple Watch – любое действие можно совершать прямо с экрана интеллектуальных часов;
- Щадящая нагрузка на трафик и железо;
- Карты местности постоянно совершенствуются на основе предоставляемых пользователями данных;
- Регулярные автоматические апдейты (опционально).
Минусы
- Отсутствие возможности импорта данных в аналогичные программы;
- Качество детализации карт стран, находящихся вне постсоветского пространства, оставляет желать лучшего;
- При выключенной функции автоматических апдейстов, приложение необходимо время от времени обновлять вручную, иначе со временем оно перестанет работать.
Как установить Яндекс.Навигатор на iPhone 4 с iOS 7.1.2?
До сих пор многие используют iPhone 4. Некоторые по финансовым причинам, другие потому что смартфон им нравится полностью удовлетворяет их потребности. Но, в силу возраста самого телефона и операционной системы версии iOS 7.1.2 на нем, на смартфон нельзя загрузить программу «Яндекс.Навигатор» и еще ряд других приложений, не поддерживаемых iOS 7.1.2.
Отчаиваться не надо, ибо существует ряд способов, которые позволяют закачивать почти все необходимые вам приложения. Сейчас мы их рассмотрим.
Как скачать «Яндекс Навигатор» на iPhone 4 с iOS 7.1.2?
Яндекс.Навигатор сейчас является наиболее популярным в России. Не всегда точный, но так у всех приложений-навигаторов.

Если вы зайдете в магазин App Store и захотите скачать его, то увидите что установка невозможна по причине того, что требуется минимум iOS 8.0.
Это понятно, так как iPhone 4 появился в 2010 году и успел устареть. Помимо операционной системы устаревает сама начинка телефона, и от этого деваться некуда.
Сейчас мы расскажем вам как можно установить более старые версии программ.
Важно: подключать айфон к компьютеру с помощью кабеля не нужно!
Инструкция:
- Запускаем на ПК iTunes, если его нет, то берем отсюда: СКАЧАТЬ ITUNES.
- Логинимся в свой аккаунт.
- Скачиваем необходимые приложения, в нашем случае это «Яндекс Навигатор».
- Далее в самом телефоне заходим в магазин App Store.
- Идем в разделы «Обновления» — «Покупки», нажимаем «Установить» напротив Яндекс.Навигатора.
- Появится оповещение, что можно установить версию подходящую для iOS 7.1.2, нажимаем «Да».
Воспользовавшись этим вариантом вы сможете установить практически любое приложение, которое не совместимо с вашей версией операционной системы. Способ не такой быстрый как хотелось бы, но вполне реализуемый.
ВНИМАНИЕ. App Store убрали из последней версии iTunes. Вам потребуется скачать и установить iTunes 12.6.3. Сделать это можно ТУТ .
Есть и другие способы установки приложений, например, скачать Jailbreak на ваш телефон.
Есть магазин Cydia, в котором можно найти нужные версии приложений. Но надо отметить, что ваши персональные данные окажутся под угрозой, ведь тут за безопасность никто не отвечает и все делается только на ваш страх и риск.
Можно установить приложение «левых» интернет ресурсов. Надо отключить «Установка со сторонних ресурсов» в настройках, и можно спокойно пользоваться не только магазином App Store.
Каждый выбирает наиболее удобный для него способ.
iPhone 4 хорошее устройство, но он уже устарел, и главная причина не менять его — финансы.
На счастье, технический прогресс не стоит на месте, сейчас можно подобрать новый смартфон на «Андроиде» по вполне доступной цене.
Если вы поклонник компании Apple, то можно посоветовать восстановленные, либо б/у, а также обратить внимание на модели iPhone 6 и 6S, на данный момент они не так дорого стоят, и отвечают всем требованиям.