From 96ebb830d1ef91ecb2c3ad7009768fa5cc821763 Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Sat, 20 Feb 2021 14:22:10 +0700 Subject: [PATCH 01/10] Add print-based demonstration of tapping map location --- app/Root/RootView.swift | 14 ++++++++++++++ app/Root/RootViewModel.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/app/Root/RootView.swift b/app/Root/RootView.swift index 9b01515..53c1263 100644 --- a/app/Root/RootView.swift +++ b/app/Root/RootView.swift @@ -1,6 +1,8 @@ import SwiftUI struct RootView: View { + private static let mapCoordinateSpace = "map" + private let viewModel: RootViewModel private let viewFactory: RootViewFactory @@ -22,6 +24,8 @@ struct RootView: View { ZStack() { ZStack(alignment: .bottomTrailing) { self.viewFactory.makeMapView() + .coordinateSpace(name: Self.mapCoordinateSpace) + .simultaneousGesture(self.drag) if !self.showMarkers { self.settingsButton().frame(width: 100, height: 100, alignment: .bottomTrailing) } @@ -100,5 +104,15 @@ struct RootView: View { ]) } } + + private var drag: some Gesture { + DragGesture( + minimumDistance: 0, + coordinateSpace: .named(Self.mapCoordinateSpace) + ) + .onEnded { info in + self.viewModel.tap(info.location) + } + } } diff --git a/app/Root/RootViewModel.swift b/app/Root/RootViewModel.swift index e8126e8..df64dd9 100644 --- a/app/Root/RootViewModel.swift +++ b/app/Root/RootViewModel.swift @@ -2,6 +2,7 @@ import SwiftUI import PlatformSDK final class RootViewModel { + private static let tapRadius: CGFloat = 5 let searchStore: SearchStore @@ -9,9 +10,12 @@ final class RootViewModel { private let sourceFactory: () -> ISourceFactory private let locationManagerFactory: () -> LocationService? private let map: Map + private let toMap: CGAffineTransform private var locationService: LocationService? private var moveCameraCancellable: Cancellable? + private var getRenderedObjectsCancellable: Cancellable? + private let testPoints: [(position: CameraPosition, time: TimeInterval, type: CameraAnimationType)] = { return [ (.init( @@ -58,6 +62,9 @@ final class RootViewModel { self.locationManagerFactory = locationManagerFactory self.map = map + let scale = UIScreen.main.nativeScale + self.toMap = CGAffineTransform(scaleX: scale, y: scale) + let service = SearchService( searchManagerFactory: self.searchManagerFactory, scheduler: DispatchQueue.main @@ -128,4 +135,24 @@ final class RootViewModel { } } } + + func tap(_ location: CGPoint) { + let mapLocation = location.applying(self.toMap) + let tapPoint = ScreenPoint(x: Float(mapLocation.x), y: Float(mapLocation.y)) + let tapRadius = ScreenDistance(value: Float(Self.tapRadius)) + let cancel = self.map.getRenderedObjects(centerPoint: tapPoint, radius: tapRadius) + .sink(receiveValue: { infos in + // Достаточно взять первый маркер. В данном примере перечислим все + // маркера в окрестности. + for info in infos { + if let object = info.item.item { + print("Tapped object: \(object).") + } + } + }, + failure: { error in + print("Failed to fetch objects: \(error)") + }) + self.getRenderedObjectsCancellable = cancel + } } From ff2f182564f0f768bce4abda35e294ceb74851fe Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Fri, 12 Mar 2021 11:46:00 +0700 Subject: [PATCH 02/10] Add map location info card --- app.xcodeproj/project.pbxproj | 44 ++++++++- app/Assets.xcassets/Colors/Contents.json | 6 ++ .../Colors/dgs_green.colorset/Contents.json | 20 ++++ .../splash_logo.imageset/Contents.json | 12 +++ .../splash_logo.imageset/splash_logo.pdf | Bin 0 -> 5149 bytes app/Base.lproj/LaunchScreen.storyboard | 28 +++++- app/{ => Extensions}/Channel+Helpers.swift | 0 app/Extensions/Future+Helpers.swift | 20 ++++ app/Extensions/GeoPoint+Helpers.swift | 15 +++ .../RenderedObjectInfo+Helpers.swift | 28 ++++++ app/Extensions/TrafficRoute+Helpers.swift | 8 ++ app/{ => Extensions}/View+Helpers.swift | 0 app/MapObjectCardView/MapObjectCardView.swift | 46 +++++++++ .../MapObjectCardViewModel.swift | 49 ++++++++++ app/Root/RootView.swift | 61 ++++++------ app/Root/RootViewFactory.swift | 4 + app/Root/RootViewModel.swift | 90 ++++++++++++++---- 17 files changed, 377 insertions(+), 54 deletions(-) create mode 100644 app/Assets.xcassets/Colors/Contents.json create mode 100644 app/Assets.xcassets/Colors/dgs_green.colorset/Contents.json create mode 100644 app/Assets.xcassets/splash_logo.imageset/Contents.json create mode 100644 app/Assets.xcassets/splash_logo.imageset/splash_logo.pdf rename app/{ => Extensions}/Channel+Helpers.swift (100%) create mode 100644 app/Extensions/Future+Helpers.swift create mode 100644 app/Extensions/GeoPoint+Helpers.swift create mode 100644 app/Extensions/RenderedObjectInfo+Helpers.swift create mode 100644 app/Extensions/TrafficRoute+Helpers.swift rename app/{ => Extensions}/View+Helpers.swift (100%) create mode 100644 app/MapObjectCardView/MapObjectCardView.swift create mode 100644 app/MapObjectCardView/MapObjectCardViewModel.swift diff --git a/app.xcodeproj/project.pbxproj b/app.xcodeproj/project.pbxproj index a2939e3..349a685 100644 --- a/app.xcodeproj/project.pbxproj +++ b/app.xcodeproj/project.pbxproj @@ -48,6 +48,12 @@ 4BBE80292526E22F00D7EBDB /* SearchResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE80282526E22F00D7EBDB /* SearchResultViewModel.swift */; }; 4BBE802D2526F8A600D7EBDB /* RootViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE802C2526F8A600D7EBDB /* RootViewFactory.swift */; }; 4BBE80302526FB0D00D7EBDB /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE802F2526FB0D00D7EBDB /* Container.swift */; }; + B709913925F9CBCF00B2F1A5 /* MapObjectCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B709913825F9CBCF00B2F1A5 /* MapObjectCardView.swift */; }; + B709914C25FA11CD00B2F1A5 /* MapObjectCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B709914B25FA11CD00B2F1A5 /* MapObjectCardViewModel.swift */; }; + B709914F25FA6B6F00B2F1A5 /* RenderedObjectInfo+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B709914E25FA6B6F00B2F1A5 /* RenderedObjectInfo+Helpers.swift */; }; + B709915125FA6B9D00B2F1A5 /* GeoPoint+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B709915025FA6B9D00B2F1A5 /* GeoPoint+Helpers.swift */; }; + B709915325FA6BD300B2F1A5 /* TrafficRoute+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B709915225FA6BD300B2F1A5 /* TrafficRoute+Helpers.swift */; }; + B709915525FA72A600B2F1A5 /* Future+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B709915425FA72A600B2F1A5 /* Future+Helpers.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -107,6 +113,12 @@ 4BBE80282526E22F00D7EBDB /* SearchResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultViewModel.swift; sourceTree = ""; }; 4BBE802C2526F8A600D7EBDB /* RootViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewFactory.swift; sourceTree = ""; }; 4BBE802F2526FB0D00D7EBDB /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + B709913825F9CBCF00B2F1A5 /* MapObjectCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapObjectCardView.swift; sourceTree = ""; }; + B709914B25FA11CD00B2F1A5 /* MapObjectCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapObjectCardViewModel.swift; sourceTree = ""; }; + B709914E25FA6B6F00B2F1A5 /* RenderedObjectInfo+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RenderedObjectInfo+Helpers.swift"; sourceTree = ""; }; + B709915025FA6B9D00B2F1A5 /* GeoPoint+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GeoPoint+Helpers.swift"; sourceTree = ""; }; + B709915225FA6BD300B2F1A5 /* TrafficRoute+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrafficRoute+Helpers.swift"; sourceTree = ""; }; + B709915425FA72A600B2F1A5 /* Future+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Future+Helpers.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -172,12 +184,12 @@ 4B27063424A3F24C00F12B48 /* AppDelegate.swift */, 4B1354DB24BCCDC7004E8158 /* NativeApp.entitlements */, 4B27063624A3F24C00F12B48 /* SceneDelegate.swift */, - 095BE97725B80EC800D09D75 /* View+Helpers.swift */, - 0991909725B977CD00F4235B /* Channel+Helpers.swift */, + B709914D25FA6B1000B2F1A5 /* Extensions */, 4BBE822A252741EE00D7EBDB /* Root */, 4BBE822B2527420900D7EBDB /* Search */, 4BBE822C2527422000D7EBDB /* Map */, 4BBE802F2526FB0D00D7EBDB /* Container.swift */, + B709914A25FA0FD700B2F1A5 /* MapObjectCardView */, 09C834F925BFCD4800D347F4 /* Route */, 095BE97225B6FF3700D09D75 /* Marker */, 0938CA9F25B5BB8A00100316 /* LocationService */, @@ -242,6 +254,28 @@ name = Frameworks; sourceTree = ""; }; + B709914A25FA0FD700B2F1A5 /* MapObjectCardView */ = { + isa = PBXGroup; + children = ( + B709913825F9CBCF00B2F1A5 /* MapObjectCardView.swift */, + B709914B25FA11CD00B2F1A5 /* MapObjectCardViewModel.swift */, + ); + path = MapObjectCardView; + sourceTree = ""; + }; + B709914D25FA6B1000B2F1A5 /* Extensions */ = { + isa = PBXGroup; + children = ( + 095BE97725B80EC800D09D75 /* View+Helpers.swift */, + 0991909725B977CD00F4235B /* Channel+Helpers.swift */, + B709914E25FA6B6F00B2F1A5 /* RenderedObjectInfo+Helpers.swift */, + B709915025FA6B9D00B2F1A5 /* GeoPoint+Helpers.swift */, + B709915225FA6BD300B2F1A5 /* TrafficRoute+Helpers.swift */, + B709915425FA72A600B2F1A5 /* Future+Helpers.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -325,11 +359,15 @@ 4BADDFCC2528016600FBF589 /* SuggestResultView.swift in Sources */, 0991909825B977CD00F4235B /* Channel+Helpers.swift in Sources */, 4BADDFEB2529AD9C00FBF589 /* SearchNavigation.swift in Sources */, + B709913925F9CBCF00B2F1A5 /* MapObjectCardView.swift in Sources */, 4B4291682527A4DB006E74BE /* SuggestViewModel.swift in Sources */, 4BBE80292526E22F00D7EBDB /* SearchResultViewModel.swift in Sources */, 0938CAA125B5BBA300100316 /* LocationService.swift in Sources */, 4BADDFD525282A2400FBF589 /* MarkedUpTextView.swift in Sources */, 095BE97625B7042500D09D75 /* MarkerViewModel.swift in Sources */, + B709915525FA72A600B2F1A5 /* Future+Helpers.swift in Sources */, + B709914C25FA11CD00B2F1A5 /* MapObjectCardViewModel.swift in Sources */, + B709915125FA6B9D00B2F1A5 /* GeoPoint+Helpers.swift in Sources */, 4BADDFCF252801BD00FBF589 /* SuggestResultViewModel.swift in Sources */, 4B27063524A3F24C00F12B48 /* AppDelegate.swift in Sources */, 4BADDFE12528941E00FBF589 /* DirectoryObjectViewModel.swift in Sources */, @@ -342,6 +380,7 @@ 09C834FD25BFCD8500D347F4 /* RouteViewModel.swift in Sources */, 4BBE802D2526F8A600D7EBDB /* RootViewFactory.swift in Sources */, 4BBE80222526DCA200D7EBDB /* SearchResultItemView.swift in Sources */, + B709915325FA6BD300B2F1A5 /* TrafficRoute+Helpers.swift in Sources */, 4BADDFFC252AD63800FBF589 /* SearchAction.swift in Sources */, 4BADDFE42528947F00FBF589 /* FormattedAddressView.swift in Sources */, 4BADDFDE25288F9D00FBF589 /* DirectoryObjectView.swift in Sources */, @@ -352,6 +391,7 @@ 0991909625B96E1C00F4235B /* MapControl.swift in Sources */, 4B4291652527A49C006E74BE /* SuggestView.swift in Sources */, 095BE97825B80EC800D09D75 /* View+Helpers.swift in Sources */, + B709914F25FA6B6F00B2F1A5 /* RenderedObjectInfo+Helpers.swift in Sources */, 4BBE80302526FB0D00D7EBDB /* Container.swift in Sources */, 4BADDFDB252866F700FBF589 /* SearchService.swift in Sources */, 4B27063724A3F24C00F12B48 /* SceneDelegate.swift in Sources */, diff --git a/app/Assets.xcassets/Colors/Contents.json b/app/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/app/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/Assets.xcassets/Colors/dgs_green.colorset/Contents.json b/app/Assets.xcassets/Colors/dgs_green.colorset/Contents.json new file mode 100644 index 0000000..6632701 --- /dev/null +++ b/app/Assets.xcassets/Colors/dgs_green.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "41", + "green" : "153", + "red" : "63" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/Assets.xcassets/splash_logo.imageset/Contents.json b/app/Assets.xcassets/splash_logo.imageset/Contents.json new file mode 100644 index 0000000..e4a9ea7 --- /dev/null +++ b/app/Assets.xcassets/splash_logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "splash_logo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/Assets.xcassets/splash_logo.imageset/splash_logo.pdf b/app/Assets.xcassets/splash_logo.imageset/splash_logo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d13293a7c4a5f6287c91454dfeb068a02f7e6ce8 GIT binary patch literal 5149 zcmeHKdsGuw8n114v12Lhsfq~bRSpdxWF`rTBqq4>QVRj(Q3ZJolL3Z?Ogb4PQt)xg zL$}>lt*of^#S+BQ(zS;QU6IP;_(00B%BrXZA8l;`adijTXZybeX8WA?$Ips*t_QYqwe24d%d2i zDJ++6IwQVR@#UHW$NaXpE?MkRRWhJz77A64JO3eUSDc#@-RYRR<_E{1=-v#~>MnJ} z!aMJhUp%=NUH2oHoY~Rn&GJ65ro!??`|A7sFU=l!>}+%%u3z|L%Z@LqYuGziJv*H$J&O7gLT?SG z2B#l+)STr0isSOFp5Hdfc2^(#WAclxl-~dJZNHi$Bi$DQ9cG-^u=r+nS89%WW8~e$ z@Egar6@drTn|mG>rhU-3=25oGYrAUudW|h5PO}fzl|{|+PnGS^C6fDmH*Y=KmE!tN z`}&Jbt-(%}zAO5}O49F?cqqO5(!_};+gcJo8)0wuSDMjTGw*jNcM@|vk>2^&^roa! zXHC`eS>e|O3%hECQhM~O>@Asa+m1 zWhcHa_bJuX)UFb`r;YrWlA`bPEZOHGdo!(jI3mGsar4nPu76+MaOg(DV|QbOM0Po+H|w`snzYSMe|@uF^XTx} zWP$mN;o))Ry2clF^*1*gO&`Q71ER?NeXS=PPAO}X-J9MxlpnrViib5QwCygYHNUxA zviG&8M*YJ@=EnNA%~7ANocm;8%UihL`HI=g+(!>QT3{$Gr-lvK--^CH)Bf1)bV2mp z(w~We%zYgV(v$S<+#|!MFk0YHcl#qphnG6$E-Px9**f&>J@r78OPP9mg~x^T0Zm`= z%;jY%Lt1H4YP-YBKQtv)4&JM4j&v>{i;q4X6phSKq(r*x|K&=$Y*T4fQv55LoSl-2 zBR#b#8`8AdpI-fLzhjs#>cuRHYIx@6SHJ&#;Y$mPkF&Fv3@l*#9^W&?r>#G?4RL8{ zFcL!zUzX_x=Q!2h*DO(M&#pLptLeQ1&OYkVk`%Z0gXItI=eWI0AI>fsi|+7SU_R$r zMGkE`GxqfCv(Q_IIvW0tmYa|L6CrgP>rlccZk)Lp=04#R%P|>u&g~@Q!_JR)n2}u> z$HLJDN+l zVg~NJXauD(zy!G5W5;99lq@KsPSK73*~`SR zsFP(FCW0OKoJ=kcd|oD!EQMvCtS1VP+u36ooYImE|N9h26_7Awt{Aa2%>piQOUJC% zWzvAdo-d|>#Tk~*P$UfyBVJx%(cy?!FquW^!i-dAJWXp8^csjRGEpuB-$3pT`2HOb=86%JtTlUj}2TUyO#z^00zad{amKUVA1 zk%;YQSu78ba8!f7W+dDf5S!{iyQrQ22W@K0lbY+(m*hjp6h17*~ByGJXR$T zz#3Y-Ylv~n`aDL<+tm;#sU>r{O96#5!vzF@QbQ;RZZK$uzmItHapXJv#^Ov%1zGB( zL^)q@%e9JSW7SlmlgwDo?1KE?1J~POqB9722!#Zli^2mKwZs}V+$n5rG#=d~Aw-G~ zcTyA<5v%~L1v*|aORg}dUv+Vy{pxGl$&?_ThHDzGDIqW=<>~I4hHFX)Oi6jVyQblq z5&~0F{{M8>#D8tbfK`Hg?w>S#n#{SaoJ#axIh71ZY`iKE`wwm;3Y0-A`Wz3Ei|zQ* z1P_Arfa5`O#GVA9b_@ve7IFh5*^v4`y5JS2(@bVPK(sV40MZB>NeYll1FR(J-?qx} Ae*gdg literal 0 HcmV?d00001 diff --git a/app/Base.lproj/LaunchScreen.storyboard b/app/Base.lproj/LaunchScreen.storyboard index 865e932..370af0f 100644 --- a/app/Base.lproj/LaunchScreen.storyboard +++ b/app/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,11 @@ - - + + + - + + + @@ -11,10 +14,19 @@ - + - + + + + + + + + + + @@ -22,4 +34,10 @@ + + + + + + diff --git a/app/Channel+Helpers.swift b/app/Extensions/Channel+Helpers.swift similarity index 100% rename from app/Channel+Helpers.swift rename to app/Extensions/Channel+Helpers.swift diff --git a/app/Extensions/Future+Helpers.swift b/app/Extensions/Future+Helpers.swift new file mode 100644 index 0000000..efeec41 --- /dev/null +++ b/app/Extensions/Future+Helpers.swift @@ -0,0 +1,20 @@ +import SwiftUI +import PlatformSDK + +extension Future { + + @inlinable public func sinkOnMainThread( + receiveValue: @escaping (Value) -> Void, + failure: @escaping (Error) -> Void + ) -> PlatformSDK.Cancellable { + self.sink { value in + DispatchQueue.main.async { + receiveValue(value) + } + } failure: { error in + DispatchQueue.main.async { + failure(error) + } + } + } +} diff --git a/app/Extensions/GeoPoint+Helpers.swift b/app/Extensions/GeoPoint+Helpers.swift new file mode 100644 index 0000000..a25c25d --- /dev/null +++ b/app/Extensions/GeoPoint+Helpers.swift @@ -0,0 +1,15 @@ +import PlatformSDK + +extension GeoPoint: CustomStringConvertible { + + public var description: String { + "Latitude: \(self.latitude.value)\nLongitude: \(self.longitude.value)" + } +} + +extension GeoPointWithElevation: CustomStringConvertible { + + public var description: String { + "Latitude: \(self.latitude.value)\nLongitude: \(self.longitude.value)\nElevation: \(self.elevation.value)" + } +} diff --git a/app/Extensions/RenderedObjectInfo+Helpers.swift b/app/Extensions/RenderedObjectInfo+Helpers.swift new file mode 100644 index 0000000..d643173 --- /dev/null +++ b/app/Extensions/RenderedObjectInfo+Helpers.swift @@ -0,0 +1,28 @@ +import PlatformSDK + +extension RenderedObjectInfo: CustomStringConvertible { + + public var description: String { + let pointDescription = self.closestMapPoint.description + switch self.item.item { + case let dgisMapObject as DgisMapObject: + return "Id: \(dgisMapObject.id().value)" + case let searchResult as SearchResultMarkerObject: + if let id = searchResult.id() { + return "Id: \(id.value)" + } else { + return searchResult.markerPosition().description + } + case let cluster as ClusterObject: + return "Objects count: \(cluster.objectCount())" + case let route as RouteMapObject: + return route.route()?.description ?? pointDescription + case let routePoint as RoutePointMapObject: + return routePoint.route()?.description ?? pointDescription + case is MyLocationMapObject, is GeometryMapObject: + return pointDescription + default: + return pointDescription + } + } +} diff --git a/app/Extensions/TrafficRoute+Helpers.swift b/app/Extensions/TrafficRoute+Helpers.swift new file mode 100644 index 0000000..a76e9a9 --- /dev/null +++ b/app/Extensions/TrafficRoute+Helpers.swift @@ -0,0 +1,8 @@ +import PlatformSDK + +extension TrafficRoute: CustomStringConvertible { + + public var description: String { + "Distance: \(self.length().millimeters * 1000)m" + } +} diff --git a/app/View+Helpers.swift b/app/Extensions/View+Helpers.swift similarity index 100% rename from app/View+Helpers.swift rename to app/Extensions/View+Helpers.swift diff --git a/app/MapObjectCardView/MapObjectCardView.swift b/app/MapObjectCardView/MapObjectCardView.swift new file mode 100644 index 0000000..3afbe62 --- /dev/null +++ b/app/MapObjectCardView/MapObjectCardView.swift @@ -0,0 +1,46 @@ +import SwiftUI + +struct MapObjectCardView: View { + + @ObservedObject private var viewModel: MapObjectCardViewModel + + init(viewModel: MapObjectCardViewModel) { + self.viewModel = viewModel + } + + var body: some View { + ZStack { + HStack(alignment: .top){ + VStack(alignment: .leading) { + Text(self.viewModel.title) + .font(Font.system(size: 24, weight: .regular)) + .foregroundColor(.black) + .padding([.top, .leading], 16) + Text(self.viewModel.description) + .font(Font.system(size: 12, weight: .regular)) + .foregroundColor(.black) + .padding(.top, 2) + .padding([.bottom, .leading], 16) + } + Spacer() + VStack(alignment: .trailing) { + Button(action: { + self.viewModel.close() + }) { + Image(systemName: "xmark.circle.fill") + .resizable() + .frame(width: 20, height: 20) + } + .padding([.top, .trailing], 16) + } + .padding(.leading, 16) + } + } + .background( + RoundedRectangle(cornerRadius: 20, style: .circular) + .fill(Color.white) + .shadow(color: Color.black.opacity(0.2), radius: 3) + ) + .padding([.leading, .bottom, .trailing], 16) + } +} diff --git a/app/MapObjectCardView/MapObjectCardViewModel.swift b/app/MapObjectCardView/MapObjectCardViewModel.swift new file mode 100644 index 0000000..a01e275 --- /dev/null +++ b/app/MapObjectCardView/MapObjectCardViewModel.swift @@ -0,0 +1,49 @@ +import SwiftUI +import PlatformSDK + +final class MapObjectCardViewModel: ObservableObject { + + typealias CloseCallback = () -> Void + + private let objectInfo: RenderedObjectInfo + private let closeCallback: CloseCallback + private var getDirectoryObjectCancellable: Cancellable? + + var title: String { + guard let mapObject = self.objectInfo.item.item else { + return String(describing: type(of: self.objectInfo.item)) + } + return String(describing: type(of: mapObject)) + } + + @Published var description: String + + init( + objectInfo: RenderedObjectInfo, + closeCallback: @escaping CloseCallback + ) { + self.objectInfo = objectInfo + self.description = objectInfo.description + self.closeCallback = closeCallback + self.fetchObjectInfo() + } + + func close() { + self.closeCallback() + } + + private func fetchObjectInfo() { + guard let dgisMapObject = self.objectInfo.item.item as? DgisMapObject else { return } + self.getDirectoryObjectCancellable = dgisMapObject.directoryObject().sinkOnMainThread( + receiveValue: { + [weak self] directoryObject in + guard let directoryObject = directoryObject else { return } + + self?.description = "Id: \(dgisMapObject.id().value)\nTitle: \(directoryObject.title())" + }, + failure: { error in + print("Unable to fetch directoryObject. Error: \(error).") + } + ) + } +} diff --git a/app/Root/RootView.swift b/app/Root/RootView.swift index 53c1263..dbfe263 100644 --- a/app/Root/RootView.swift +++ b/app/Root/RootView.swift @@ -3,12 +3,9 @@ import SwiftUI struct RootView: View { private static let mapCoordinateSpace = "map" - private let viewModel: RootViewModel + @ObservedObject private var viewModel: RootViewModel private let viewFactory: RootViewFactory - @State private var showMarkers: Bool = false - @State private var showRoutes: Bool = false - init( viewModel: RootViewModel, viewFactory: RootViewFactory @@ -21,31 +18,37 @@ struct RootView: View { var body: some View { NavigationView { - ZStack() { - ZStack(alignment: .bottomTrailing) { - self.viewFactory.makeMapView() - .coordinateSpace(name: Self.mapCoordinateSpace) - .simultaneousGesture(self.drag) - if !self.showMarkers { - self.settingsButton().frame(width: 100, height: 100, alignment: .bottomTrailing) - } - if self.showMarkers { - self.viewFactory.makeMarkerView(show: $showMarkers).followKeyboard($keyboardOffset) + GeometryReader { geometry in + ZStack { + ZStack(alignment: .bottomTrailing) { + self.viewFactory.makeMapView() + .coordinateSpace(name: Self.mapCoordinateSpace) + .simultaneousGesture(self.drag) + if !self.viewModel.showMarkers { + self.settingsButton().frame(width: 100, height: 100, alignment: .bottomTrailing) + } + if self.viewModel.showMarkers { + self.viewFactory.makeMarkerView(show: self.$viewModel.showMarkers).followKeyboard($keyboardOffset) + } + if self.viewModel.showRoutes { + self.viewFactory.makeRouteView(show: self.$viewModel.showRoutes).followKeyboard($keyboardOffset) + } + if let cardViewModel = self.viewModel.selectedObjectCardViewModel { + self.viewFactory.makeMapObjectCardView(cardViewModel) + .transition(.move(edge: .bottom)) + } } - if self.showRoutes { - self.viewFactory.makeRouteView(show: $showRoutes).followKeyboard($keyboardOffset) + if self.viewModel.showMarkers || self.viewModel.showRoutes { + Image(systemName: "multiply").frame(width: 40, height: 40, alignment: .center).foregroundColor(.red).opacity(0.4) } + self.zoomControls() } - if self.showMarkers || self.showRoutes { - Image(systemName: "multiply").frame(width: 40, height: 40, alignment: .center).foregroundColor(.red).opacity(0.4) - } - self.zoomControls() + .navigationBarItems( + leading: self.navigationBarLeadingItem() + ) + .navigationBarTitle("2GIS", displayMode: .inline) + .edgesIgnoringSafeArea(.all) } - .navigationBarItems( - leading: self.navigationBarLeadingItem() - ) - .navigationBarTitle("2GIS", displayMode: .inline) - .edgesIgnoringSafeArea(.all) }.navigationViewStyle(StackNavigationViewStyle()) } @@ -95,10 +98,10 @@ struct RootView: View { self.viewModel.showCurrentPosition() }, .default(Text("Тест добавления маркеров")) { - self.showMarkers = true + self.viewModel.showMarkers = true }, .default(Text("Тест поиска маршрута")) { - self.showRoutes = true + self.viewModel.showRoutes = true }, .cancel(Text("Отмена")) ]) @@ -111,7 +114,9 @@ struct RootView: View { coordinateSpace: .named(Self.mapCoordinateSpace) ) .onEnded { info in - self.viewModel.tap(info.location) + if abs(info.translation.width) < 10, abs(info.translation.height) < 10 { + self.viewModel.tap(info.startLocation) + } } } } diff --git a/app/Root/RootViewFactory.swift b/app/Root/RootViewFactory.swift index d544e8e..670df35 100644 --- a/app/Root/RootViewFactory.swift +++ b/app/Root/RootViewFactory.swift @@ -42,4 +42,8 @@ struct RootViewFactory { func makeRouteView(show: Binding) -> some View { return RouteView(viewModel: self.routeViewModel, show: show) } + + func makeMapObjectCardView(_ viewModel: MapObjectCardViewModel) -> some View { + return MapObjectCardView(viewModel: viewModel) + } } diff --git a/app/Root/RootViewModel.swift b/app/Root/RootViewModel.swift index df64dd9..ff7ef26 100644 --- a/app/Root/RootViewModel.swift +++ b/app/Root/RootViewModel.swift @@ -1,11 +1,19 @@ import SwiftUI import PlatformSDK -final class RootViewModel { - private static let tapRadius: CGFloat = 5 +final class RootViewModel: ObservableObject { + + private enum Constants { + static let tapRadius: CGFloat = 5 + static let markerSize: CGFloat = 16 + } let searchStore: SearchStore + @Published var showMarkers: Bool = false + @Published var showRoutes: Bool = false + @Published var selectedObjectCardViewModel: MapObjectCardViewModel? + private let searchManagerFactory: () -> ISearchManager private let sourceFactory: () -> ISourceFactory private let locationManagerFactory: () -> LocationService? @@ -15,6 +23,15 @@ final class RootViewModel { private var moveCameraCancellable: Cancellable? private var getRenderedObjectsCancellable: Cancellable? + private var getDirectoryObjectCancellable: Cancellable? + private var selectedObjectMarker: GeometryMapObject? + private lazy var mapObjectSource: GeometryMapObjectSource? = { + guard let source = self.sourceFactory().createGeometryMapObjectSourceBuilder().createSource() else { + return nil + } + self.map.addSource(source: source) + return source + }() private let testPoints: [(position: CameraPosition, time: TimeInterval, type: CameraAnimationType)] = { return [ @@ -116,6 +133,28 @@ final class RootViewModel { } } + func tap(_ location: CGPoint) { + + self.hideSelectedMarker() + self.getRenderedObjectsCancellable?.cancel() + + let mapLocation = location.applying(self.toMap) + let tapPoint = ScreenPoint(x: Float(mapLocation.x), y: Float(mapLocation.y)) + let tapRadius = ScreenDistance(value: Float(Constants.tapRadius)) + let cancel = self.map.getRenderedObjects(centerPoint: tapPoint, radius: tapRadius).sinkOnMainThread( + receiveValue: { + [weak self] infos in + guard let info = infos.first else { return } + + self?.handle(selectedObject: info) + }, + failure: { error in + print("Failed to fetch objects: \(error)") + } + ) + self.getRenderedObjectsCancellable = cancel + } + private func move(at index: Int) { guard index < self.testPoints.count else { return } @@ -136,23 +175,36 @@ final class RootViewModel { } } - func tap(_ location: CGPoint) { - let mapLocation = location.applying(self.toMap) - let tapPoint = ScreenPoint(x: Float(mapLocation.x), y: Float(mapLocation.y)) - let tapRadius = ScreenDistance(value: Float(Self.tapRadius)) - let cancel = self.map.getRenderedObjects(centerPoint: tapPoint, radius: tapRadius) - .sink(receiveValue: { infos in - // Достаточно взять первый маркер. В данном примере перечислим все - // маркера в окрестности. - for info in infos { - if let object = info.item.item { - print("Tapped object: \(object).") - } + private func hideSelectedMarker() { + if let marker = self.selectedObjectMarker { + self.mapObjectSource?.removeObject(item: marker) + } + self.selectedObjectCardViewModel = nil + } + + private func handle(selectedObject: RenderedObjectInfo) { + do { + let point = GeoPoint( + latitude: selectedObject.closestMapPoint.longitude, + longitude: selectedObject.closestMapPoint.latitude + ) + + let mapObject = try MarkerBuilder() + .setIcon(image: UIImage(systemName: "mappin.and.ellipse")!.withTintColor(#colorLiteral(red: 0.2470588235, green: 0.6, blue: 0.1607843137, alpha: 1))) + .setPosition(point: point) + .setSize(Constants.markerSize) + .build() + self.selectedObjectMarker = mapObject + self.selectedObjectCardViewModel = MapObjectCardViewModel( + objectInfo: selectedObject, + closeCallback: { + [weak self] in + self?.hideSelectedMarker() } - }, - failure: { error in - print("Failed to fetch objects: \(error)") - }) - self.getRenderedObjectsCancellable = cancel + ) + self.mapObjectSource?.addObject(item: mapObject) + } catch { + print("Failed to build marker. Error: \(error).") + } } } From 61b1bec77ca584304fd096487a099bd4e0b3c33c Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Tue, 16 Mar 2021 14:28:33 +0700 Subject: [PATCH 03/10] Update to SDK 0.7.2 --- app.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app.xcodeproj/project.pbxproj b/app.xcodeproj/project.pbxproj index 349a685..ed260b3 100644 --- a/app.xcodeproj/project.pbxproj +++ b/app.xcodeproj/project.pbxproj @@ -602,7 +602,7 @@ repositoryURL = "https://github.com/2gis/native-sdk-ios-swift-package.git"; requirement = { kind = exactVersion; - version = 0.7.0; + version = 0.7.2; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0fe212c..0f331dc 100644 --- a/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/2gis/native-sdk-ios-swift-package.git", "state": { "branch": null, - "revision": "cc36422ceba49f519f23cde743124d35213a6b79", - "version": "0.7.0" + "revision": "8a87d506a618134187410814aa10ec66fb4e8dc0", + "version": "0.7.2" } } ] From 99d46d26dc4638b7b783e44379492e8faaac4ab2 Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Tue, 16 Mar 2021 14:29:15 +0700 Subject: [PATCH 04/10] Fix old-style markers use --- .../MapObjectCardViewModel.swift | 18 ++++++++---------- app/Root/RootViewModel.swift | 12 +++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/app/MapObjectCardView/MapObjectCardViewModel.swift b/app/MapObjectCardView/MapObjectCardViewModel.swift index a01e275..c1e3b83 100644 --- a/app/MapObjectCardView/MapObjectCardViewModel.swift +++ b/app/MapObjectCardView/MapObjectCardViewModel.swift @@ -5,31 +5,29 @@ final class MapObjectCardViewModel: ObservableObject { typealias CloseCallback = () -> Void - private let objectInfo: RenderedObjectInfo - private let closeCallback: CloseCallback - private var getDirectoryObjectCancellable: Cancellable? - var title: String { - guard let mapObject = self.objectInfo.item.item else { - return String(describing: type(of: self.objectInfo.item)) - } + let mapObject = self.objectInfo.item.item return String(describing: type(of: mapObject)) } @Published var description: String + private let objectInfo: RenderedObjectInfo + private let onClose: CloseCallback + private var getDirectoryObjectCancellable: Cancellable? + init( objectInfo: RenderedObjectInfo, - closeCallback: @escaping CloseCallback + onClose: @escaping CloseCallback ) { self.objectInfo = objectInfo self.description = objectInfo.description - self.closeCallback = closeCallback + self.onClose = onClose self.fetchObjectInfo() } func close() { - self.closeCallback() + self.onClose() } private func fetchObjectInfo() { diff --git a/app/Root/RootViewModel.swift b/app/Root/RootViewModel.swift index ff7ef26..075a516 100644 --- a/app/Root/RootViewModel.swift +++ b/app/Root/RootViewModel.swift @@ -25,10 +25,8 @@ final class RootViewModel: ObservableObject { private var getRenderedObjectsCancellable: Cancellable? private var getDirectoryObjectCancellable: Cancellable? private var selectedObjectMarker: GeometryMapObject? - private lazy var mapObjectSource: GeometryMapObjectSource? = { - guard let source = self.sourceFactory().createGeometryMapObjectSourceBuilder().createSource() else { - return nil - } + private lazy var mapObjectSource: GeometryMapObjectSource = { + let source = self.sourceFactory().createGeometryMapObjectSourceBuilder().createSource() self.map.addSource(source: source) return source }() @@ -177,7 +175,7 @@ final class RootViewModel: ObservableObject { private func hideSelectedMarker() { if let marker = self.selectedObjectMarker { - self.mapObjectSource?.removeObject(item: marker) + self.mapObjectSource.removeObject(item: marker) } self.selectedObjectCardViewModel = nil } @@ -197,12 +195,12 @@ final class RootViewModel: ObservableObject { self.selectedObjectMarker = mapObject self.selectedObjectCardViewModel = MapObjectCardViewModel( objectInfo: selectedObject, - closeCallback: { + onClose: { [weak self] in self?.hideSelectedMarker() } ) - self.mapObjectSource?.addObject(item: mapObject) + self.mapObjectSource.addObject(item: mapObject) } catch { print("Failed to build marker. Error: \(error).") } From e711121cdeb9347efd5817a960f57656c66d9ada Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Tue, 16 Mar 2021 15:51:38 +0700 Subject: [PATCH 05/10] Use modern markers --- app/Container.swift | 23 ++++++++----- app/Root/RootViewModel.swift | 63 ++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/app/Container.swift b/app/Container.swift index 99c0ec3..cdaaa44 100644 --- a/app/Container.swift +++ b/app/Container.swift @@ -52,14 +52,21 @@ final class Container { } private func makeRootViewModel() -> RootViewModel { - let rootViewModel = RootViewModel(searchManagerFactory: { [sdk = self.sdk] in - return sdk.searchManagerFactory.makeOnlineManager()! - }, sourceFactory: { [sdk = self.sdk] in - return sdk.sourceFactory - }, locationManagerFactory: { [weak self] in - guard let self = self else { return nil } - return self.locationManager - }, map: self.sdk.map) + let rootViewModel = RootViewModel( + searchManagerFactory: { [sdk = self.sdk] in + sdk.searchManagerFactory.makeOnlineManager()! + }, + sourceFactory: { [sdk = self.sdk] in + sdk.sourceFactory + }, + imageFactory: { [sdk = self.sdk] in + sdk.imageFactory + }, + locationManagerFactory: { [weak self] in + self?.locationManager + }, + map: self.sdk.map + ) return rootViewModel } } diff --git a/app/Root/RootViewModel.swift b/app/Root/RootViewModel.swift index 075a516..5b01777 100644 --- a/app/Root/RootViewModel.swift +++ b/app/Root/RootViewModel.swift @@ -4,8 +4,7 @@ import PlatformSDK final class RootViewModel: ObservableObject { private enum Constants { - static let tapRadius: CGFloat = 5 - static let markerSize: CGFloat = 16 + static let tapRadius: CGFloat = 1 } let searchStore: SearchStore @@ -16,6 +15,7 @@ final class RootViewModel: ObservableObject { private let searchManagerFactory: () -> ISearchManager private let sourceFactory: () -> ISourceFactory + private let imageFactory: () -> IImageFactory private let locationManagerFactory: () -> LocationService? private let map: Map private let toMap: CGAffineTransform @@ -24,11 +24,14 @@ final class RootViewModel: ObservableObject { private var moveCameraCancellable: Cancellable? private var getRenderedObjectsCancellable: Cancellable? private var getDirectoryObjectCancellable: Cancellable? - private var selectedObjectMarker: GeometryMapObject? - private lazy var mapObjectSource: GeometryMapObjectSource = { - let source = self.sourceFactory().createGeometryMapObjectSourceBuilder().createSource() - self.map.addSource(source: source) - return source + private var selectedMarker: Marker? + private lazy var mapObjectManager: MapObjectManager = createMapObjectManager(map: self.map) + private lazy var selectedMarkerIcon: PlatformSDK.Image = { + let factory = self.imageFactory() + let icon = UIImage(systemName: "mappin.and.ellipse")! + .withTintColor(#colorLiteral(red: 0.2470588235, green: 0.6, blue: 0.1607843137, alpha: 1)) + .withConfiguration(UIImage.SymbolConfiguration(scale: .large)) + return factory.make(image: icon) }() private let testPoints: [(position: CameraPosition, time: TimeInterval, type: CameraAnimationType)] = { @@ -69,11 +72,13 @@ final class RootViewModel: ObservableObject { init( searchManagerFactory: @escaping () -> ISearchManager, sourceFactory: @escaping () -> ISourceFactory, + imageFactory: @escaping () -> IImageFactory, locationManagerFactory: @escaping () -> LocationService?, map: Map ) { self.searchManagerFactory = searchManagerFactory self.sourceFactory = sourceFactory + self.imageFactory = imageFactory self.locationManagerFactory = locationManagerFactory self.map = map @@ -174,35 +179,29 @@ final class RootViewModel: ObservableObject { } private func hideSelectedMarker() { - if let marker = self.selectedObjectMarker { - self.mapObjectSource.removeObject(item: marker) + if let marker = self.selectedMarker { + marker.remove() } self.selectedObjectCardViewModel = nil } private func handle(selectedObject: RenderedObjectInfo) { - do { - let point = GeoPoint( - latitude: selectedObject.closestMapPoint.longitude, - longitude: selectedObject.closestMapPoint.latitude - ) - - let mapObject = try MarkerBuilder() - .setIcon(image: UIImage(systemName: "mappin.and.ellipse")!.withTintColor(#colorLiteral(red: 0.2470588235, green: 0.6, blue: 0.1607843137, alpha: 1))) - .setPosition(point: point) - .setSize(Constants.markerSize) - .build() - self.selectedObjectMarker = mapObject - self.selectedObjectCardViewModel = MapObjectCardViewModel( - objectInfo: selectedObject, - onClose: { - [weak self] in - self?.hideSelectedMarker() - } - ) - self.mapObjectSource.addObject(item: mapObject) - } catch { - print("Failed to build marker. Error: \(error).") - } + let mapPoint = selectedObject.closestMapPoint + let markerPoint = GeoPointWithElevation( + latitude: mapPoint.latitude, + longitude: mapPoint.longitude + ) + let markerOptions = MarkerOptions( + position: markerPoint, + icon: self.selectedMarkerIcon + ) + self.selectedMarker = self.mapObjectManager.addMarker(options: markerOptions) + self.selectedObjectCardViewModel = MapObjectCardViewModel( + objectInfo: selectedObject, + onClose: { + [weak self] in + self?.hideSelectedMarker() + } + ) } } From d0742a9341225a2acc3fad56cb12f020d9182431 Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Tue, 16 Mar 2021 17:57:34 +0700 Subject: [PATCH 06/10] Show marker info in pop-up --- .../MapObjectCardViewModel.swift | 36 ++++++++++++++----- app/Root/RootViewModel.swift | 3 +- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/MapObjectCardView/MapObjectCardViewModel.swift b/app/MapObjectCardView/MapObjectCardViewModel.swift index c1e3b83..e19ac01 100644 --- a/app/MapObjectCardView/MapObjectCardViewModel.swift +++ b/app/MapObjectCardView/MapObjectCardViewModel.swift @@ -5,11 +5,7 @@ final class MapObjectCardViewModel: ObservableObject { typealias CloseCallback = () -> Void - var title: String { - let mapObject = self.objectInfo.item.item - return String(describing: type(of: mapObject)) - } - + @Published var title: String = "Some place" @Published var description: String private let objectInfo: RenderedObjectInfo @@ -31,17 +27,39 @@ final class MapObjectCardViewModel: ObservableObject { } private func fetchObjectInfo() { - guard let dgisMapObject = self.objectInfo.item.item as? DgisMapObject else { return } - self.getDirectoryObjectCancellable = dgisMapObject.directoryObject().sinkOnMainThread( + let mapObject = self.objectInfo.item.item + switch mapObject { + case let object as DgisMapObject: + self.fetchInfo(dgisMapObject: object) + case let marker as Marker: + self.fetchInfo(marker: marker) + default: + self.fetchInfo(genericMapObject: mapObject) + } + } + + private func fetchInfo(dgisMapObject object: DgisMapObject) { + self.getDirectoryObjectCancellable = object.directoryObject().sinkOnMainThread( receiveValue: { [weak self] directoryObject in guard let directoryObject = directoryObject else { return } - self?.description = "Id: \(dgisMapObject.id().value)\nTitle: \(directoryObject.title())" + self?.title = directoryObject.title() + self?.description = "ID: \(object.id().value)" }, failure: { error in - print("Unable to fetch directoryObject. Error: \(error).") + print("Unable to fetch a directory object. Error: \(error).") } ) } + + private func fetchInfo(marker: Marker) { + let text = marker.text + self.title = text.isEmpty ? "Marker" : text + self.description = "\(marker.position)" + } + + private func fetchInfo(genericMapObject object: MapObject) { + self.title = String(describing: object) + } } diff --git a/app/Root/RootViewModel.swift b/app/Root/RootViewModel.swift index 5b01777..09bc20c 100644 --- a/app/Root/RootViewModel.swift +++ b/app/Root/RootViewModel.swift @@ -137,7 +137,6 @@ final class RootViewModel: ObservableObject { } func tap(_ location: CGPoint) { - self.hideSelectedMarker() self.getRenderedObjectsCancellable?.cancel() @@ -147,8 +146,8 @@ final class RootViewModel: ObservableObject { let cancel = self.map.getRenderedObjects(centerPoint: tapPoint, radius: tapRadius).sinkOnMainThread( receiveValue: { [weak self] infos in + // The first object is the closest one to the tapped point. guard let info = infos.first else { return } - self?.handle(selectedObject: info) }, failure: { error in From c8d82e14bf983ae951f3617b56639efe47296f02 Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Tue, 16 Mar 2021 18:45:34 +0700 Subject: [PATCH 07/10] Add an example of map tap handling --- app/Root/RootViewModel.swift | 23 ++++++++++++++++------- docs/ru/examples.md | 3 +++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/Root/RootViewModel.swift b/app/Root/RootViewModel.swift index 09bc20c..8cb96e1 100644 --- a/app/Root/RootViewModel.swift +++ b/app/Root/RootViewModel.swift @@ -4,7 +4,7 @@ import PlatformSDK final class RootViewModel: ObservableObject { private enum Constants { - static let tapRadius: CGFloat = 1 + static let tapRadius = ScreenDistance(value: 1) } let searchStore: SearchStore @@ -137,18 +137,27 @@ final class RootViewModel: ObservableObject { } func tap(_ location: CGPoint) { + let mapLocation = location.applying(self.toMap) + let tapPoint = ScreenPoint(x: Float(mapLocation.x), y: Float(mapLocation.y)) + self.tap(point: tapPoint, tapRadius: Constants.tapRadius) + } + + /// - Parameter point: A tap point in *pixel* (native scale) cooordinates. + /// - Parameter tapRadius: Radius around tap point in which objects will + /// be detected. + private func tap(point: ScreenPoint, tapRadius: ScreenDistance) { self.hideSelectedMarker() self.getRenderedObjectsCancellable?.cancel() - let mapLocation = location.applying(self.toMap) - let tapPoint = ScreenPoint(x: Float(mapLocation.x), y: Float(mapLocation.y)) - let tapRadius = ScreenDistance(value: Float(Constants.tapRadius)) - let cancel = self.map.getRenderedObjects(centerPoint: tapPoint, radius: tapRadius).sinkOnMainThread( + let cancel = self.map.getRenderedObjects(centerPoint: point, radius: tapRadius).sink( receiveValue: { - [weak self] infos in + infos in // The first object is the closest one to the tapped point. guard let info = infos.first else { return } - self?.handle(selectedObject: info) + DispatchQueue.main.async { + [weak self] in + self?.handle(selectedObject: info) + } }, failure: { error in print("Failed to fetch objects: \(error)") diff --git a/docs/ru/examples.md b/docs/ru/examples.md index 5a4e09c..9f43896 100644 --- a/docs/ru/examples.md +++ b/docs/ru/examples.md @@ -171,6 +171,9 @@ let polyline = objectsManager.addPolyline(options: options) /// - Parameter tapRadius: Radius around tap point in which objects will /// be detected. private func tap(point: ScreenPoint, tapRadius: ScreenDistance) { + self.hideSelectedMarker() + self.getRenderedObjectsCancellable?.cancel() + let cancel = self.map.getRenderedObjects(centerPoint: point, radius: tapRadius).sink( receiveValue: { infos in From 33b9a22207509e6bc61af1221d7958f8a45530fc Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Tue, 16 Mar 2021 21:39:06 +0700 Subject: [PATCH 08/10] fixup! Add an example of map tap handling --- docs/ru/examples.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/ru/examples.md b/docs/ru/examples.md index 9f43896..5a4e09c 100644 --- a/docs/ru/examples.md +++ b/docs/ru/examples.md @@ -171,9 +171,6 @@ let polyline = objectsManager.addPolyline(options: options) /// - Parameter tapRadius: Radius around tap point in which objects will /// be detected. private func tap(point: ScreenPoint, tapRadius: ScreenDistance) { - self.hideSelectedMarker() - self.getRenderedObjectsCancellable?.cancel() - let cancel = self.map.getRenderedObjects(centerPoint: point, radius: tapRadius).sink( receiveValue: { infos in From 7e718d18abb3a36720573ceb80f82f799f8b9ac3 Mon Sep 17 00:00:00 2001 From: Sergey Lagner Date: Wed, 17 Mar 2021 12:49:16 +0700 Subject: [PATCH 09/10] my location marker example --- docs/ru/examples.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/ru/examples.md b/docs/ru/examples.md index 5a4e09c..3492774 100644 --- a/docs/ru/examples.md +++ b/docs/ru/examples.md @@ -153,6 +153,20 @@ let polyline = objectsManager.addPolyline(options: options) // TBD +## Мое местоположение + +### Маркер местоположения на карте +```swift +// создаем источник для отображения маркера на карте +let source = createMyLocationMapObjectSource( + context: sdkContext, + directionBehaviour: MyLocationDirectionBehaviour.followMagneticHeading) + +// добавляем источник в карту +map.addSource(source: source) +``` + + ## Получение информации о точке прикосновения к карте Передаём точку нажатия в пиксельных координатах. Для наиболее подходящего From 84fa856a945caede33317e8c114e0ead5f156fd3 Mon Sep 17 00:00:00 2001 From: Anatoly Petrov Date: Thu, 18 Mar 2021 15:02:22 +0700 Subject: [PATCH 10/10] Update to SDK v0.8.0 --- app.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../xcshareddata/xcschemes/app.xcscheme | 4 ++-- .../RenderedObjectInfo+Helpers.swift | 12 ++++++------ app/Extensions/TrafficRoute+Helpers.swift | 2 +- .../MapObjectCardViewModel.swift | 18 ++++++++++++------ app/Marker/MarkerViewModel.swift | 2 +- app/Route/RouteViewModel.swift | 4 ++-- app/Search/DirectoryObjectViewModel.swift | 6 +++--- app/Search/SearchResultItemViewModel.swift | 4 ++-- app/Search/SearchResultViewModel.swift | 3 +-- app/Search/SearchService.swift | 10 +++++----- app/Search/SuggestResultViewModel.swift | 2 +- app/Search/SuggestViewModel.swift | 12 ++++++------ 14 files changed, 45 insertions(+), 40 deletions(-) diff --git a/app.xcodeproj/project.pbxproj b/app.xcodeproj/project.pbxproj index ed260b3..7409c32 100644 --- a/app.xcodeproj/project.pbxproj +++ b/app.xcodeproj/project.pbxproj @@ -602,7 +602,7 @@ repositoryURL = "https://github.com/2gis/native-sdk-ios-swift-package.git"; requirement = { kind = exactVersion; - version = 0.7.2; + version = 0.8.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0f331dc..49a75a3 100644 --- a/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/2gis/native-sdk-ios-swift-package.git", "state": { "branch": null, - "revision": "8a87d506a618134187410814aa10ec66fb4e8dc0", - "version": "0.7.2" + "revision": "6f770e55a6d65abfadb6fdfbbc364a6c26af3066", + "version": "0.8.0" } } ] diff --git a/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme b/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme index 335a7be..0cdfd4b 100644 --- a/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme +++ b/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme @@ -32,8 +32,8 @@ Bool { @@ -46,7 +46,7 @@ private extension SuggestHandler { var object: DirectoryObject? { switch self { case .objectHandler(let handler): - return handler!.item() + return handler?.item default: return nil }