1. Прежде чем начать
В этой лабораторной работе вы узнаете, как использовать Maps SDK для iOS со SwiftUI.
Предпосылки
- Базовые знания Swift
- Базовые знания SwiftUI
Что ты будешь делать?
- Включите и используйте Maps SDK для iOS, чтобы добавить Google Maps в приложение iOS с помощью SwiftUI.
- Добавьте маркеры на карту.
- Передача состояния между SwiftUI и объектом
GMSMapView
.
Что вам понадобится
- Xcode 11.0 или более поздняя версия
- Аккаунт Google с включенной функцией выставления счетов
- Карт SDK для iOS
- Карфаген
2. Настройте
Для следующего шага включения включите Maps SDK для iOS .
Настройте платформу Google Карт
Если у вас еще нет учетной записи Google Cloud Platform и проекта с включенным выставлением счетов, ознакомьтесь с руководством « Начало работы с Google Maps Platform», чтобы создать учетную запись для выставления счетов и проект.
- В Cloud Console щелкните раскрывающееся меню проектов и выберите проект, который вы хотите использовать для этой кодовой лаборатории.
- Включите API и SDK платформы Google Карт, необходимые для этой лабораторной работы, в Google Cloud Marketplace . Для этого следуйте инструкциям в этом видео или в этой документации .
- Сгенерируйте ключ API на странице «Учётные данные» в Cloud Console. Вы можете следовать инструкциям в этом видео или в этой документации . Для всех запросов к платформе Google Карт требуется ключ API.
3. Загрузите стартовый код
Чтобы вы могли начать как можно быстрее, вот пример кода, который поможет вам разобраться с этой практической работой. Вы можете сразу перейти к решению, но если хотите выполнить все шаги по его созданию самостоятельно, продолжайте читать.
- Клонируйте репозиторий, если у вас установлен
git
.
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git
Или вы можете нажать следующую кнопку, чтобы загрузить исходный код.
- Получив код, в терминале
cd
в каталогstarter/GoogleMapsSwiftUI
. - Выполните команду
carthage update --platform iOS
, чтобы загрузить Maps SDK для iOS. - Наконец, откройте файл
GoogleMapsSwiftUI.xcodeproj
в Xcode.
4. Обзор кода
В загруженном вами стартовом проекте для вас предоставлены и реализованы следующие классы:
-
AppDelegate
—UIApplicationDelegate
приложения. Здесь будет инициализирован Maps SDK для iOS. -
City
— структура, представляющая город (содержит название и координаты города). -
MapViewController
— ограниченный UIKitUIViewController
содержащий карту Google ( GMSMapView )-
SceneDelegate
—UIWindowSceneDelegate
приложения, из которого создается экземплярContentView
.
-
Кроме того, следующие классы имеют частичные реализации и будут завершены вами к концу этой лабораторной работы:
-
ContentView
— представление SwiftUI верхнего уровня, содержащее ваше приложение. -
MapViewControllerBridge
— класс, связывающий представление UIKit с представлением SwiftUI. В частности, этот класс делаетMapViewController
доступным в SwiftUI.
5. SwiftUI против UIKit
SwiftUI был представлен в iOS 13 как альтернативный UIKit фреймворк для разработки приложений iOS. По сравнению со своим предшественником UIKit, SwiftUI предлагает ряд преимуществ. Вот некоторые из них:
- Представления автоматически обновляются при изменении состояния. При использовании объектов State любое изменение содержащегося в них базового значения приведет к автоматическому обновлению пользовательского интерфейса.
- Предварительный просмотр в реальном времени ускоряет разработку. Он минимизирует необходимость в сборке и развертывании кода в эмуляторе для визуального просмотра изменений, поскольку предварительный просмотр SwiftUI можно легко увидеть в Xcode.
- Источник истины — Swift. Все представления в SwiftUI объявлены на Swift, поэтому использование Interface Builder больше не требуется.
- Взаимодействие с UIKit. Взаимодействие с UIKit гарантирует, что существующие приложения смогут постепенно использовать SwiftUI с существующими представлениями. Кроме того, библиотеки, которые пока не поддерживают SwiftUI, например , Maps SDK для iOS , по-прежнему можно использовать в SwiftUI.
Есть и некоторые недостатки:
- SwiftUI доступен только на iOS 13 и более поздних версиях.
- Иерархию представлений невозможно проверить в предварительном просмотре Xcode.
Состояние SwiftUI и поток данных
SwiftUI предлагает новый способ создания пользовательского интерфейса с использованием декларативного подхода: вы сообщаете SwiftUI, как должно выглядеть ваше представление, включая все его состояния, а система сделает всё остальное. SwiftUI обрабатывает обновление представления при каждом изменении его базового состояния в результате события или действия пользователя. Такой подход обычно называют однонаправленным потоком данных . Хотя подробности этого подхода выходят за рамки данной практической работы, мы рекомендуем ознакомиться с принципами его работы в документации Apple по состоянию и потоку данных .
Объедините UIKit и SwiftUI с помощью UIViewRepresentable или UIViewControllerRepresentable
Поскольку Maps SDK для iOS создан на основе UIKit и не предоставляет представления, совместимого со SwiftUI, для его использования в SwiftUI требуется соответствие требованиям UIViewRepresentable
или UIViewControllerRepresentable
. Эти протоколы позволяют SwiftUI включать объекты UIView
и UIViewController
, созданные в UIKit, соответственно. Хотя для добавления карты Google в представление SwiftUI можно использовать любой из этих протоколов, на следующем этапе мы рассмотрим использование UIViewControllerRepresentable
для включения объекта UIViewController
, содержащего карту.
6. Добавить карту
В этом разделе вы добавите Google Maps в представление SwiftUI.
Добавьте свой ключ API
Ключ API, созданный вами на предыдущем этапе, необходимо предоставить Maps SDK для iOS, чтобы связать вашу учетную запись с картой, которая будет отображаться в приложении.
Чтобы предоставить свой ключ API, откройте файл AppDelegate.swift
и перейдите к методу application(_, didFinishLaunchingWithOptions)
. SDK инициализируется с помощью метода GMSServices.provideAPIKey()
со строкой "YOUR_API_KEY". Замените эту строку своим ключом API. Выполнение этого шага инициализирует Maps SDK для iOS при запуске приложения.
Добавьте карту Google с помощью MapViewControllerBridge
Теперь, когда ваш ключ API предоставлен SDK, следующим шагом будет отображение карты в приложении.
Контроллер представления, предоставленный в стартовом коде, MapViewController
содержит в своём представлении GMSMapView
. Однако, поскольку этот контроллер представления был создан в UIKit, вам потребуется подключить этот класс к SwiftUI, чтобы его можно было использовать внутри ContentView
. Для этого:
- Откройте файл
MapViewControllerBridge
в Xcode.
Этот класс соответствует UIViewControllerRepresentable — протоколу, необходимому для обёртки UIKit UIViewController
, чтобы его можно было использовать в качестве представления SwiftUI. Другими словами, соответствие этому протоколу упрощает связывание представления UIKit с представлением SwiftUI. Для соответствия этому протоколу требуется реализация двух методов:
-
makeUIViewController(context)
— этот метод вызывается SwiftUI для создания базовогоUIViewController
. Здесь вы создаёте экземплярUIViewController
и передаёте ему начальное состояние. -
updateUIViewController(_, context)
— этот метод вызывается SwiftUI при каждом изменении состояния. Именно здесь вы можете внести любые изменения в базовыйUIViewController
, чтобы отреагировать на изменение состояния.
- Создать
MapViewController
Внутри функции makeUIViewController(context)
создайте новый экземпляр MapViewController
и верните его как результат. После этого ваш MapViewControllerBridge
должен выглядеть так:
MapViewControllerBridge
import GoogleMaps
import SwiftUI
struct MapViewControllerBridge: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> MapViewController {
return MapViewController()
}
func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
}
}
Используйте MapViewControllerBridge в ContentView
Теперь, когда MapViewControllerBridge
создает экземпляр MapViewController
, следующим шагом будет использование этой структуры в ContentView
для отображения карты.
- Откройте файл
ContentView
в Xcode.
ContentView
создается в SceneDelegate
и содержит представление приложения верхнего уровня. Карта будет добавлена из этого файла.
- Создайте
MapViewControllerBridge
внутри свойстваbody
.
В свойстве body
этого файла уже предоставлен и реализован ZStack
. ZStack
содержит интерактивный и перетаскиваемый список городов, который вы будете использовать на следующем этапе. Сейчас в ZStack
создайте MapViewControllerBridge
в качестве первого дочернего представления ZStack
, чтобы карта отображалась в приложении за представлением списка городов. После этого содержимое свойства body
в ContentView
должно выглядеть следующим образом:
ContentView
var body: some View {
let scrollViewHeight: CGFloat = 80
GeometryReader { geometry in
ZStack(alignment: .top) {
// Map
MapViewControllerBridge()
// Cities List
CitiesList(markers: $markers) { (marker) in
guard self.selectedMarker != marker else { return }
self.selectedMarker = marker
self.zoomInCenter = false
self.expandList = false
} handleAction: {
self.expandList.toggle()
} // ...
}
}
}
- Теперь запустите приложение. На экране вашего устройства должна появиться загрузочная карта, а также список городов, который можно перетаскивать в нижней части экрана.
7. Добавьте маркеры на карту.
На предыдущем шаге вы добавили карту вместе с интерактивным списком городов. В этом разделе вы добавите маркеры для каждого города из этого списка.
Маркеры как состояние
ContentView
объявляет свойство с именем markers
, представляющее собой список GMSMarker
, представляющих каждый город, объявленный в статическом свойстве cities
. Обратите внимание, что это свойство аннотировано свойством-оболочкой SwiftUI State , что указывает на то, что оно должно управляться SwiftUI. Таким образом, при обнаружении каких-либо изменений этого свойства, например, добавления или удаления маркера, представления, использующие это состояние, будут обновлены.
ContentView
static let cities = [
City(name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: 37.7576, longitude: -122.4194)),
City(name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: 47.6131742, longitude: -122.4824903)),
City(name: "Singapore", coordinate: CLLocationCoordinate2D(latitude: 1.3440852, longitude: 103.6836164)),
City(name: "Sydney", coordinate: CLLocationCoordinate2D(latitude: -33.8473552, longitude: 150.6511076)),
City(name: "Tokyo", coordinate: CLLocationCoordinate2D(latitude: 35.6684411, longitude: 139.6004407))
]
/// State for markers displayed on the map for each city in `cities`
@State var markers: [GMSMarker] = cities.map {
let marker = GMSMarker(position: $0.coordinate)
marker.title = $0.name
return marker
}
Обратите внимание, что ContentView
использует свойство markers
для отображения списка городов, передавая его классу CitiesList
.
CitiesList
struct CitiesList: View {
@Binding var markers: [GMSMarker]
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
// ...
// List of Cities
List {
ForEach(0..<self.markers.count) { id in
let marker = self.markers[id]
Button(action: {
buttonAction(marker)
}) {
Text(marker.title ?? "")
}
}
}.frame(maxWidth: .infinity)
}
}
}
}
Передайте состояние в MapViewControllerBridge с помощью @Binding
В дополнение к списку городов, отображающих данные из свойства markers
, передайте это свойство в структуру MapViewControllerBridge
, чтобы его можно было использовать для отображения этих маркеров на карте. Для этого:
- Объявите новое свойство
markers
вMapViewControllerBridge
, аннотированное@Binding
MapViewControllerBridge
struct MapViewControllerBridge: : UIViewControllerRepresentable {
@Binding var markers: [GMSMarker]
// ...
}
- В
MapViewControllerBridge
обновите методupdateUIViewController(_, context)
, чтобы использовать свойствоmarkers
Как упоминалось в предыдущем шаге, updateUIViewController(_, context)
будет вызываться SwiftUI при каждом изменении состояния. Именно в этом методе мы хотим обновить карту, чтобы отобразить маркеры в markers
. Для этого необходимо обновить свойство map
каждого маркера. После завершения этого шага ваш MapViewControllerBridge
должен выглядеть следующим образом:
import GoogleMaps
import SwiftUI
struct MapViewControllerBridge: UIViewControllerRepresentable {
@Binding var markers: [GMSMarker]
func makeUIViewController(context: Context) -> MapViewController {
return MapViewController()
}
func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
// Update the map for each marker
markers.forEach { $0.map = uiViewController.map }
}
}
- Передайте свойство
markers
изContentView
вMapViewControllerBridge
Поскольку вы добавили новое свойство в MapViewControllerBridge
, теперь требуется, чтобы значение этого свойства было передано в инициализатор MapViewControllerBridge
. Поэтому при попытке сборки приложения вы заметите, что оно не компилируется. Чтобы исправить это, обновите ContentView
, где создаётся MapViewControllerBridge
, и передайте свойство markers
следующим образом:
struct ContentView: View {
// ...
var body: some View {
// ...
GeometryReader { geometry in
ZStack(alignment: .top) {
// Map
MapViewControllerBridge(markers: $markers)
// ...
}
}
}
}
Обратите внимание, что префикс $
используется для передачи markers
в MapViewControllerBridge
, поскольку он ожидает связанное свойство. $
— зарезервированный префикс для использования с обёртками свойств Swift. При применении к состоянию он вернёт привязку Binding .
- Запустите приложение, чтобы увидеть маркеры, отображаемые на карте.
8. Анимация в выбранном городе
На предыдущем шаге вы добавили маркеры на карту, передавая состояние State из одного представления SwiftUI в другое. На этом шаге вы создадите анимацию для города или маркера после нажатия на него в интерактивном списке. Для создания анимации вы будете реагировать на изменения состояния, изменяя положение камеры карты при каждом изменении. Подробнее о концепции камеры карты см. в разделе Камера и представление .
Анимированная карта выбранного города
Чтобы анимировать карту выбранного города:
- Определить новую привязку в
MapViewControllerBridge
У ContentView
есть свойство State под названием selectedMarker
, которое инициализируется значением nil и обновляется при выборе города в списке. Это обрабатывается функцией buttonAction
представления CitiesList
в ContentView
.
ContentView
CitiesList(markers: $markers) { (marker) in
guard self.selectedMarker != marker else { return }
self.selectedMarker = marker
// ...
}
При каждом изменении значения selectedMarker
MapViewControllerBridge
должен учитывать это изменение состояния, чтобы иметь возможность анимировать карту относительно выбранного маркера. Поэтому определите новую привязку в MapViewControllerBridge
типа GMSMarker
и назовите свойство selectedMarker
.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
@Binding var selectedMarker: GMSMarker?
}
- Обновите
MapViewControllerBridge
для анимации карты при каждом измененииselectedMarker
.
После объявления новой привязки необходимо обновить функцию updateUIViewController_, context)
объекта MapViewControllerBridge
, чтобы карта анимировалась в соответствии с выбранным маркером. Для этого скопируйте следующий код:
struct MapViewControllerBridge: UIViewControllerRepresentable {
@Binding var selectedMarker: GMSMarker?
func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
markers.forEach { $0.map = uiViewController.map }
selectedMarker?.map = uiViewController.map
animateToSelectedMarker(viewController: uiViewController)
}
private func animateToSelectedMarker(viewController: MapViewController) {
guard let selectedMarker = selectedMarker else {
return
}
let map = viewController.map
if map.selectedMarker != selectedMarker {
map.selectedMarker = selectedMarker
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
map.animate(toZoom: kGMSMinZoomLevel)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
map.animate(toZoom: 12)
})
}
}
}
}
}
Функция animateToSelectedMarker(viewController)
выполнит последовательность анимаций карты с помощью функции animate(with)
GMSMapView
.
- Передайте
selectedMarker
ContentView
вMapViewControllerBridge
После того как MapViewControllerBridge
объявит новую привязку, обновите ContentView
, чтобы передать selectedMarker
, в котором создается экземпляр MapViewControllerBridge
.
ContentView
struct ContentView: View {
// ...
var body: some View {
// ...
GeometryReader { geometry in
ZStack(alignment: .top) {
// Map
MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker)
// ...
}
}
}
}
После выполнения этого шага карта будет анимироваться каждый раз при выборе нового города в списке.
Анимируйте вид SwiftUI, чтобы подчеркнуть город
SwiftUI упрощает процесс анимации представлений, поскольку он сам управляет анимацией переходов между состояниями. Чтобы продемонстрировать это, вы добавите больше анимаций, сфокусировав вид на выбранном городе после завершения анимации карты. Для этого выполните следующие действия:
- Добавьте замыкание
onAnimationEnded
вMapViewControllerBridge
Поскольку анимация SwiftUI будет выполняться после ранее добавленной вами последовательности анимации карты, объявите новое замыкание с именем onAnimationEnded
в MapViewControllerBridge
и вызовите это замыкание с задержкой 0,5 секунды после последней анимации карты в методе animateToSelectedMarker(viewController)
.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
var onAnimationEnded: () -> ()
private func animateToSelectedMarker(viewController: MapViewController) {
guard let selectedMarker = selectedMarker else {
return
}
let map = viewController.map
if map.selectedMarker != selectedMarker {
map.selectedMarker = selectedMarker
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
map.animate(toZoom: kGMSMinZoomLevel)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
map.animate(toZoom: 12)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
// Invoke onAnimationEnded() once the animation sequence completes
onAnimationEnded()
})
})
}
}
}
}
}
- Реализуйте
onAnimationEnded
вMapViewControllerBridge
Реализуйте замыкание onAnimationEnded
, где MapViewControllerBridge
создаётся внутри ContentView
. Скопируйте и вставьте следующий код, который добавляет новое состояние с именем zoomInCenter
, а также изменяет вид с помощью clipShape
и изменяет диаметр обрезанной фигуры в зависимости от значения zoomInCenter
ContentView
struct ContentView: View {
@State var zoomInCenter: Bool = false
// ...
var body: some View {
// ...
GeometryReader { geometry in
ZStack(alignment: .top) {
// Map
let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
self.zoomInCenter = true
})
.clipShape(
Circle()
.size(
width: diameter,
height: diameter
)
.offset(
CGPoint(
x: (geometry.size.width - diameter) / 2,
y: (geometry.size.height - diameter) / 2
)
)
)
.animation(.easeIn)
.background(Color(red: 254.0/255.0, green: 1, blue: 220.0/255.0))
}
}
}
}
- Запустите приложение и посмотрите анимацию!
9. Отправьте событие в SwiftUI
На этом этапе вы будете прослушивать события, отправляемые GMSMapView
, и отправлять их в SwiftUI. В частности, вы настроите делегат для представления карты и будете прослушивать события перемещения камеры, чтобы при фокусировке на городе и перемещении камеры карты в результате жеста фокусировка на представлении карты смещалась, и вы могли видеть больше её части.
Используйте координаторы SwiftUI
GMSMapView
генерирует события, такие как изменение положения камеры или нажатие маркера. Механизм отслеживания этих событий реализован через протокол GMSMapViewDelegate . SwiftUI представляет концепцию координатора, который используется специально для выполнения функций делегата для контроллеров представлений UIKit. Таким образом, в мире SwiftUI координатор должен отвечать за соответствие протоколу GMSMapViewDelegate
. Для этого выполните следующие действия:
- Создайте координатор с именем
MapViewCoordinator
вMapViewControllerBridge
Создайте вложенный класс внутри класса MapViewControllerBridge
и назовите его MapViewCoordinator
. Этот класс должен соответствовать GMSMapViewDelegate
и должен объявлять MapViewControllerBridge
как свойство.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
var mapViewControllerBridge: MapViewControllerBridge
init(_ mapViewControllerBridge: MapViewControllerBridge) {
self.mapViewControllerBridge = mapViewControllerBridge
}
}
}
- Реализуйте
makeCoordinator()
вMapViewControllerBridge
Затем реализуйте метод makeCoordinator()
в MapViewControllerBridge
и верните экземпляр MapViewCoodinator
, созданный на предыдущем шаге.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeCoordinator() -> MapViewCoordinator {
return MapViewCoordinator(self)
}
}
- Установите
MapViewCoordinator
как делегата представления карты.
После создания пользовательского координатора следующим шагом будет назначение его делегатом для представления карты контроллера представления. Для этого обновите инициализацию контроллера представления в makeUIViewController(context)
. Созданный на предыдущем шаге координатор будет доступен из объекта Context.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeUIViewController(context: Context) -> MapViewController {
let uiViewController = MapViewController()
uiViewController.map.delegate = context.coordinator
return uiViewController
}
}
- Добавьте замыкание в
MapViewControllerBridge
, чтобы событие перемещения камеры могло распространяться вверх
Поскольку цель — обновлять представление в соответствии с перемещениями камеры, объявите новое свойство замыкания mapViewWillMove
, принимающее логическое значение, в MapViewControllerBridge
и вызовите это замыкание в методе делегата mapView(_, willMove)
в MapViewCoordinator
. Передайте значение gesture
в замыкание, чтобы представление SwiftUI реагировало только на события перемещения камеры, связанные с жестами.
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
var mapViewWillMove: (Bool) -> ()
//...
final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
// ...
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
self.mapViewControllerBridge.mapViewWillMove(gesture)
}
}
}
- Обновите ContentView, чтобы передать значение для
mapWillMove
После объявления нового замыкания в MapViewControllerBridge
обновите ContentView
, чтобы передать значение для этого нового замыкания. В этом замыкании переключите состояние zoomInCenter
на false
, если событие перемещения связано с жестом. Это фактически снова отобразит карту в полноэкранном режиме при её перемещении жестом.
ContentView
struct ContentView: View {
@State var zoomInCenter: Bool = false
// ...
var body: some View {
// ...
GeometryReader { geometry in
ZStack(alignment: .top) {
// Map
let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
self.zoomInCenter = true
}, mapViewWillMove: { (isGesture) in
guard isGesture else { return }
self.zoomInCenter = false
})
// ...
}
}
}
}
- Запустите приложение, чтобы увидеть новые изменения!
10. Поздравления
Поздравляю с тем, что вы добрались так далеко! Вы проделали большую работу, и надеюсь, полученные знания позволят вам создать собственное приложение SwiftUI с использованием Maps SDK для iOS.
Что вы узнали
- Различия между SwiftUI и UIKit
- Как объединить SwiftUI и UIKit с помощью UIViewControllerRepresentable
- Как внести изменения в вид карты с помощью State and Binding
- Как отправить событие из представления карты в SwiftUI с помощью координатора
Что дальше?
- Карт SDK для iOS
- официальная документация по Maps SDK для iOS
- Places SDK для iOS — найдите местные компании и достопримечательности вокруг вас
- maps-sdk-for-ios-samples
- пример кода на GitHub, демонстрирующий все функции Maps SDK для iOS.
- SwiftUI — официальная документация Apple по SwiftUI
- Помогите нам создать контент, который будет вам наиболее полезен, ответив на следующий опрос:
Какие еще практические занятия вы хотели бы увидеть?
Не нашли нужную вам практическую работу? Запросите её в новом выпуске здесь .