Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom zoom control example #11

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
4B96E77B2608C14C0057C366 /* Arcdegree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B96E7792608C14C0057C366 /* Arcdegree.swift */; };
4B96EBDC2608C5780057C366 /* View+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B96EBDA2608C5780057C366 /* View+Helpers.swift */; };
4B96EBDD2608C5780057C366 /* Channel+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B96EBDB2608C5780057C366 /* Channel+Helpers.swift */; };
4B96D147260224AB0057C366 /* CustomZoomControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B96D146260224AB0057C366 /* CustomZoomControl.swift */; };
4BADDFCC2528016600FBF589 /* SuggestResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BADDFCB2528016600FBF589 /* SuggestResultView.swift */; };
4BADDFCF252801BD00FBF589 /* SuggestResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BADDFCE252801BC00FBF589 /* SuggestResultViewModel.swift */; };
4BADDFD2252802A500FBF589 /* SearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BADDFD1252802A500FBF589 /* SearchResultView.swift */; };
Expand Down Expand Up @@ -98,6 +99,7 @@
4B96E7792608C14C0057C366 /* Arcdegree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Arcdegree.swift; sourceTree = "<group>"; };
4B96EBDA2608C5780057C366 /* View+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Helpers.swift"; sourceTree = "<group>"; };
4B96EBDB2608C5780057C366 /* Channel+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Channel+Helpers.swift"; sourceTree = "<group>"; };
4B96D146260224AB0057C366 /* CustomZoomControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomZoomControl.swift; sourceTree = "<group>"; };
4BADDFCB2528016600FBF589 /* SuggestResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestResultView.swift; sourceTree = "<group>"; };
4BADDFCE252801BC00FBF589 /* SuggestResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestResultViewModel.swift; sourceTree = "<group>"; };
4BADDFD1252802A500FBF589 /* SearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -273,6 +275,7 @@
children = (
4BBE7E1F25264F2B00D7EBDB /* MapView.swift */,
0991909525B96E1C00F4235B /* MapControl.swift */,
4B96D146260224AB0057C366 /* CustomZoomControl.swift */,
);
path = Map;
sourceTree = "<group>";
Expand Down Expand Up @@ -423,6 +426,7 @@
4BADDFD2252802A500FBF589 /* SearchResultView.swift in Sources */,
4B96D1902603682B0057C366 /* StylePickerViewModel.swift in Sources */,
0991909625B96E1C00F4235B /* MapControl.swift in Sources */,
4B96D147260224AB0057C366 /* CustomZoomControl.swift in Sources */,
4B4291652527A49C006E74BE /* SuggestView.swift in Sources */,
B709914F25FA6B6F00B2F1A5 /* RenderedObjectInfo+Helpers.swift in Sources */,
4B96D18E260348C10057C366 /* StylePickerView.swift in Sources */,
Expand Down
6 changes: 5 additions & 1 deletion app/Container.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class Container {
return sdk.sourceFactory
},
routeEditorSourceFactory: { [sdk = self.sdk] routeEditor in
return RouteEditorSource(context: sdk.context, routeEditor: routeEditor)
return sdk.sourceFactory.createRouteEditorSource(routeEditor: routeEditor)
},
routeEditorFactory: { [sdk = self.sdk] in
return RouteEditor(context: sdk.context)
Expand All @@ -51,6 +51,10 @@ final class Container {
[mapFactory = self.mapFactory] in
mapFactory.mapView
},
customZoomControlFactory: {
[mapFactory = self.mapFactory] in
CustomZoomControl(map: mapFactory.map)
},
mapControlFactory: self.mapFactory.mapControlFactory
)
return viewFactory
Expand Down
104 changes: 104 additions & 0 deletions app/Map/CustomZoomControl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import UIKit
import PlatformSDK

/// Блок управления масштабом карты.
public final class CustomZoomControl: UIControl {
public static override var requiresConstraintBasedLayout: Bool { true }
private let stack: UIStackView

init(map: Map) {
let model = ZoomControlModel(map: map)

let zoomIn = ZoomButton(model: model, direction: .zoomIn)
let zoomInImage = UIImage(systemName: "plus.magnifyingglass")
zoomIn.setImage(zoomInImage, for: .normal)

let zoomOut = ZoomButton(model: model, direction: .zoomOut)
let zoomOutImage = UIImage(systemName: "minus.magnifyingglass")
zoomOut.setImage(zoomOutImage, for: .normal)

self.stack = UIStackView(arrangedSubviews: [zoomIn, zoomOut])
self.stack.distribution = .fillEqually
self.stack.alignment = .fill
self.stack.axis = .vertical

super.init(frame: .zero)

self.addSubview(self.stack)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Use init(map:)")
}

public override func layoutSubviews() {
super.layoutSubviews()
self.stack.frame = self.bounds
}
}

private final class ZoomButton: UIButton {
private let model: ZoomControlModel
private let direction: ZoomControlButton
private var connection: ICancellable = NoopCancellable()

init(model: ZoomControlModel, direction: ZoomControlButton) {
self.model = model
self.direction = direction

super.init(frame: .zero)

self.contentVerticalAlignment = .fill
self.contentHorizontalAlignment = .fill

self.addTarget(
self,
action: #selector(self.startZoom),
for: .touchDown
)
self.addTarget(
self,
action: #selector(self.stopZoom),
for: [.touchCancel, .touchUpInside, .touchUpOutside]
)

// Реагируем на отключение действия — выключаем кнопку.
// Такое возможно при достижении предела изменения масштаба.
self.connection = self.model.isEnabled(button: self.direction).sink {
[weak self] isEnabled in
DispatchQueue.main.async {
self?.isEnabled = isEnabled
}
}
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc private func startZoom() {
UIView.animate(
withDuration: 0.25,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 5,
animations: {
self.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
})
self.model.setPressed(button: self.direction, value: true)
}

@objc private func stopZoom() {
UIView.animate(
withDuration: 0.25,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 5,
animations: {
self.transform = CGAffineTransform.identity
})
self.model.setPressed(button: self.direction, value: false)
}
}
4 changes: 1 addition & 3 deletions app/Root/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ struct RootView: View {
HStack {
Spacer()
self.viewFactory.makeZoomControl()
.frame(width: 60, height: 128)
.fixedSize()
.transformEffect(.init(scaleX: 0.8, y: 0.8))
.frame(width: 60, height: 120)
.padding(10)
}
}
Expand Down
5 changes: 4 additions & 1 deletion app/Root/RootViewFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ struct RootViewFactory {
private let markerViewModel: MarkerViewModel
private let routeViewModel: RouteViewModel
private let mapUIViewFactory: () -> UIView & IMapView
private let customZoomControlFactory: () -> UIView
private let mapControlFactory: IMapControlFactory

init(
viewModel: RootViewModel,
markerViewModel: MarkerViewModel,
routeViewModel: RouteViewModel,
mapUIViewFactory: @escaping () -> UIView & IMapView,
customZoomControlFactory: @escaping () -> UIView,
mapControlFactory: IMapControlFactory
) {
self.viewModel = viewModel
self.markerViewModel = markerViewModel
self.routeViewModel = routeViewModel
self.mapUIViewFactory = mapUIViewFactory
self.customZoomControlFactory = customZoomControlFactory
self.mapControlFactory = mapControlFactory
}

Expand All @@ -27,7 +30,7 @@ struct RootViewFactory {
}

func makeZoomControl() -> some View {
MapControl(controlFactory: self.mapControlFactory.makeZoomControl)
MapControl(controlFactory: self.customZoomControlFactory)
}

func makeSearchView() -> some View {
Expand Down
63 changes: 63 additions & 0 deletions docs/ru/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,3 +463,66 @@ private func tap(point: ScreenPoint, tapRadius: ScreenDistance) {
self.getRenderedObjectsCancellable = cancel
}
```

## Создание кнопки управления масштабом

В SDK есть готовый к использованию блок управления масштабом, создаваемый
с помощью метода `PlatformSDK.Container.mapControlFactory.makeZoomControl()`.

Пример описывает создание собственной кнопки управления масштабом в фиксированном
направлении: `direction` указывает, будет кнопка уменьшать или увеличивать.
Ударживание кнопки в нажатом состоянии продолжает непрерывное изменение
масштаба. Когда предел будет достигнут — кнопка визуально изменит состояние.

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

Экземпляр `ZoomControlModel` можно получить с помощью функции
`PlatformSDK.createZoomControlModel(map:)`.

```
final class ZoomButton: UIButton {
private let model: ZoomControlModel
private let direction: ZoomControlButton
private var connection: ICancellable = NoopCancellable()

init(model: ZoomControlModel, direction: ZoomControlButton) {
self.model = model
self.direction = direction

super.init(frame: .zero)

self.addTarget(
self,
action: #selector(self.startZoom),
for: .touchDown
)
self.addTarget(
self,
action: #selector(self.stopZoom),
for: [.touchCancel, .touchUpInside, .touchUpOutside]
)

// Реагируем на отключение действия — выключаем кнопку.
// Такое возможно при достижении предела изменения масштаба.
self.connection = self.model.isEnabled(button: self.direction).sink {
[weak self] isEnabled in
DispatchQueue.main.async {
self?.isEnabled = isEnabled
}
}
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc private func startZoom() {
self.model.setPressed(button: self.direction, value: true)
}

@objc private func stopZoom() {
self.model.setPressed(button: self.direction, value: false)
}
}
```