mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
Move the interactive map into a new MapLibreShim framework
Second step towards loading MapLibre on demand: everything that touches it now lives in one framework, so that framework can later be left unlinked. The interactive map view, its annotation view and the coordinate animator move into MapLibreShim, which reaches the app through MapLibreShimProtocol. The app side keeps its MapLibreMapView call site and becomes a thin wrapper that builds a MapViewConfiguration, passing the app-only pieces (map style URLs, marker views, tint colour) across as closures so the shim needs nothing from the app. LocationAnnotation loses its MapLibre conformance and becomes a plain struct, with the shim mapping it onto its own annotation type. MapLibre's log forwarding moves into the shim as well, since TargetConfiguration can no longer reach it.
This commit is contained in:
@@ -312,6 +312,7 @@
|
||||
30CC1DB7CE357659C82AA115 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; };
|
||||
30DA1A668482E5B95E8F1F2B /* GalleryRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04EFA0E3139C01E1CFDC93B /* GalleryRoomTimelineView.swift */; };
|
||||
30E5628F74AD3C27A061BF25 /* QRCodeLoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC1B7CB061C9865B2B91B56 /* QRCodeLoginScreenViewModel.swift */; };
|
||||
310E1D9D246277A4FFA66162 /* MapLibreMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3989F50ECBC5AEE2E5A062DA /* MapLibreMapView.swift */; };
|
||||
311868F5E65BA61AFA6CCC2C /* CompoundHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B89D6C760E8CAE29CA28FB1 /* CompoundHook.swift */; };
|
||||
3118D9ABFD4BE5A3492FF88A /* ElementCallConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC437C491EA6996513B1CEAB /* ElementCallConfiguration.swift */; };
|
||||
31CCF3159E5CE1C05AE5781B /* HTMLFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A5FBB74981BF8EFFDAE90D /* HTMLFixtures.swift */; };
|
||||
@@ -555,6 +556,7 @@
|
||||
5AA81A4E2D40A32A9E7F71F2 /* ShareExtensionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3ADF21BE301D0DA48F2A7E /* ShareExtensionView.swift */; };
|
||||
5AC5CD6D893073EE4D9A277E /* ShareExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D27299A36536DBF91AE8FA6 /* ShareExtensionViewController.swift */; };
|
||||
5AE6404C4FD4848ACCFF9EDC /* SecureBackupLogoutConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */; };
|
||||
5B2CE8FE14CB16F2FCAFC4B2 /* MapLibreShim.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 44E8BAA982EC260E74CCCB22 /* MapLibreShim.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */; };
|
||||
5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; };
|
||||
5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; };
|
||||
@@ -566,6 +568,7 @@
|
||||
5D52925FEB1B780C65B0529F /* PinnedEventsTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4F6D7000EDCD187E0989E7 /* PinnedEventsTimelineScreen.swift */; };
|
||||
5D53AE9342A4C06B704247ED /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; };
|
||||
5D56CE09743C6B90C21B04C2 /* RoomMembersListScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */; };
|
||||
5D586C5CED7B7B086C61B84A /* MapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = 7E8600FD98955C620F83BFBA /* MapLibre */; };
|
||||
5D99F63CC88BB29383019FC6 /* ShareExtensionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */; };
|
||||
5DB4334CBBA142376FF5FFEC /* preview_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 200626E8353AB2729444F991 /* preview_image.jpg */; };
|
||||
5DD85A0FE3D85AEC3C7EFE36 /* DeveloperOptionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */; };
|
||||
@@ -660,11 +663,13 @@
|
||||
6AEB650311F694A5702255C9 /* UserProfileScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B4932E4EFBC8FAC10972CD /* UserProfileScreenCoordinator.swift */; };
|
||||
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; };
|
||||
6B3AB95A6993646A5081BFF9 /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43F773904F87FF5ADFE4DD1 /* TestablePreview.swift */; };
|
||||
6B47F2C6B11A1EDF0480DA71 /* CoordinateAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10A8171DA78BA39F07D669B1 /* CoordinateAnimator.swift */; };
|
||||
6B61F5B27412ED4BC2F9769C /* test_audio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 66B96842BF5F8ACA1AC84C55 /* test_audio.mp3 */; };
|
||||
6B80C24A52411EAF10E06E96 /* SecurityAndPrivacyScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBADF8010C813D905C172CE /* SecurityAndPrivacyScreenModels.swift */; };
|
||||
6B8F03A85E83755D98DF0443 /* SearchScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4724D5CB16CFA5853EF8F9 /* SearchScreenViewModel.swift */; };
|
||||
6BA9214F31A24C7A84287A56 /* SentryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 337AD37403015231CAE3E80B /* SentryEvent.swift */; };
|
||||
6BAE34CFA9821709CFE61E50 /* DeveloperOptionsScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F60FDB3AEFC60830BD289FE /* DeveloperOptionsScreenHook.swift */; };
|
||||
6BD28DF22B18AA9269BD97D4 /* MapLibreAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0C086BE461902C44B608393 /* MapLibreAnnotation.swift */; };
|
||||
6BEB10499149444909EBE42F /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */; };
|
||||
6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; };
|
||||
6C8BAF1E91618D69E7D652B2 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; };
|
||||
@@ -708,7 +713,6 @@
|
||||
72D2298DE695A6797CDA1A2A /* SpaceScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B223FA339BF53085328DEE /* SpaceScreenViewModelTests.swift */; };
|
||||
733E2B19AB1FDA3B93293A28 /* AppLockSetupPINScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F275432954C8C6B1B7D966 /* AppLockSetupPINScreen.swift */; };
|
||||
738288EAEE235CAC0893AB9E /* ThreadTimelineScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C9ACDD96F36510C1FC0836B /* ThreadTimelineScreenViewModel.swift */; };
|
||||
73DBE886625AF56FF08D7F76 /* CoordinateAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA74F57B0DA3B9A9DD51F691 /* CoordinateAnimator.swift */; };
|
||||
73F33E9776B7A50B65A031D2 /* AppLockSettingsScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0BA67B3E4EF9D29D14A78CE /* AppLockSettingsScreenViewModelTests.swift */; };
|
||||
73F547BEB41D3DAFAAF6E0AF /* UserProfileScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E2E5103702D13361D09100 /* UserProfileScreenViewModelTests.swift */; };
|
||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; };
|
||||
@@ -806,6 +810,7 @@
|
||||
82434593648CB74121F1A821 /* RowDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C513A6CD99E6C3C163DA1E /* RowDivider.swift */; };
|
||||
8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; };
|
||||
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; };
|
||||
82A33B40198D4EDD9F90493A /* MapViewConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8686F7CDEFAE2CBB3B9117 /* MapViewConfiguration.swift */; };
|
||||
82D1368B43020B55347E1743 /* GalleryGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE0273EC14A781BBF715E37 /* GalleryGridView.swift */; };
|
||||
832A4EA1094B8FE423A08700 /* RoomChangeRolesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */; };
|
||||
8358D145F9BF94F412BEDCA8 /* RoomRolesAndPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */; };
|
||||
@@ -1055,6 +1060,7 @@
|
||||
A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; };
|
||||
A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; };
|
||||
A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; };
|
||||
A79634DCE52B5021F549E0E9 /* MapLibreShim.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44E8BAA982EC260E74CCCB22 /* MapLibreShim.framework */; };
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
|
||||
A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */; };
|
||||
A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; };
|
||||
@@ -1336,12 +1342,14 @@
|
||||
D8459AAD6969B1431ECBE990 /* UnsupportedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E535B3388755B65C34CD10 /* UnsupportedRoomTimelineView.swift */; };
|
||||
D8517B8EED58D24396FB71E7 /* DeactivateAccountScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BAE25A0E9E9F2F1BBA8930 /* DeactivateAccountScreenViewModel.swift */; };
|
||||
D885B783B95AD7832D4EF5DD /* CharacterSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8C01DEEA83903D45069BBD /* CharacterSet.swift */; };
|
||||
D89E46C3AAD462407D332046 /* MapLibreShimProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726D7EB0E16F7E4F6DD95395 /* MapLibreShimProtocol.swift */; };
|
||||
D8CFA0EE46376F9FF04EEE45 /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4853C923A1AF43711D025EAF /* TextRoomTimelineView.swift */; };
|
||||
D8F1462EA00AFC939FF9ACCA /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 203D1ACC20287F8986C959D3 /* target.yml */; };
|
||||
D97C782FE0005995C36FA04A /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */; };
|
||||
D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
|
||||
D9F80CE61BF8FF627FDB0543 /* LoadableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352359663A0E52BA20761EE /* LoadableImage.swift */; };
|
||||
DA10C99BA43A0F1E732F6274 /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2711E5996016ABD6EAAEB58A /* LogLevel.swift */; };
|
||||
DA4ABC37ED49F6617091F142 /* MapLibreInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 842F63AA68E8EB410B1BA29F /* MapLibreInterface.framework */; };
|
||||
DA7E867F5EAFF8E20B2EE3B6 /* SecureBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D16709ADD4F4BCC710B1E /* SecureBackupScreenModels.swift */; };
|
||||
DA8AE352C8FFCD47327811CA /* LeaveSpaceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822B5573BDF52CF220BF9A17 /* LeaveSpaceModels.swift */; };
|
||||
DAF63A9CF9932CA8F6830F11 /* ShareExtensionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */; };
|
||||
@@ -1352,6 +1360,7 @@
|
||||
DBC3FDE1540B39702A117D8E /* RoomRolesAndPermissionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DDC82DB6A84E700CD5DEC0 /* RoomRolesAndPermissionsTests.swift */; };
|
||||
DBC8D1DBFE9F9CA7662BC8AA /* RoomPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */; };
|
||||
DC08ADC41E792086A340A8B3 /* AccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */; };
|
||||
DC1321AC05EE491E5D5A9A50 /* MapLibreShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89ABAF691BA268B93C06142A /* MapLibreShim.swift */; };
|
||||
DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */; };
|
||||
DC77E9DB2CFBE84A2BDF20C5 /* RoomRolesAndPermissionsFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0833F51229E166BCA141D004 /* RoomRolesAndPermissionsFlowCoordinator.swift */; };
|
||||
DCFE7CB3B9A104330BBB96AD /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */; };
|
||||
@@ -1610,6 +1619,13 @@
|
||||
remoteGlobalIDString = 19F0C845D67E9BEA4BE7133E;
|
||||
remoteInfo = ShareExtension;
|
||||
};
|
||||
5F1D4E7C20485C444FA1D42F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = AC22997D58D612146053154D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 392E7AA23D7BA2CEEB25CF73;
|
||||
remoteInfo = MapLibreShim;
|
||||
};
|
||||
6848AF4480814C5F810FB7EB /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = AC22997D58D612146053154D /* Project object */;
|
||||
@@ -1638,6 +1654,13 @@
|
||||
remoteGlobalIDString = FEB53A5BC378C913769656D8;
|
||||
remoteInfo = NSE;
|
||||
};
|
||||
D96A382EE558F49A8070D92A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = AC22997D58D612146053154D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 96435A765678A64A956EE59C;
|
||||
remoteInfo = MapLibreInterface;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -1661,6 +1684,7 @@
|
||||
files = (
|
||||
C9C562D85999E436C7265AF1 /* MatrixRustSDK in Embed Frameworks */,
|
||||
874704A177B0A6D78E037908 /* MapLibreInterface.framework in Embed Frameworks */,
|
||||
5B2CE8FE14CB16F2FCAFC4B2 /* MapLibreShim.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1772,6 +1796,7 @@
|
||||
105429F29096729EDD3152CF /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||
1059E2AE7878CF7820592637 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = "<group>"; };
|
||||
10A8171DA78BA39F07D669B1 /* CoordinateAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinateAnimator.swift; sourceTree = "<group>"; };
|
||||
10CC626F97AD70FF0420C115 /* RoomSummaryProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedUserDefaults.swift; sourceTree = "<group>"; };
|
||||
10F130DF775CE6BC51A4E392 /* AppLockSetupBiometricsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenModels.swift; sourceTree = "<group>"; };
|
||||
@@ -2009,6 +2034,7 @@
|
||||
38E521D6C2BF8DF0DFB35146 /* DeveloperOptionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreen.swift; sourceTree = "<group>"; };
|
||||
3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenScreenModelProtocol.swift; sourceTree = "<group>"; };
|
||||
398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = "<group>"; };
|
||||
3989F50ECBC5AEE2E5A062DA /* MapLibreMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreMapView.swift; sourceTree = "<group>"; };
|
||||
39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerMock.swift; sourceTree = "<group>"; };
|
||||
39C78D5FF15343724902EB20 /* ClassicAppManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassicAppManager.swift; sourceTree = "<group>"; };
|
||||
3A12D3D8138F1B71AFA7C858 /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = "<group>"; };
|
||||
@@ -2080,6 +2106,7 @@
|
||||
44C314C00533E2C297796B60 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
44C70BD39FC34A9FD2467540 /* SearchScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
44DDC82DB6A84E700CD5DEC0 /* RoomRolesAndPermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsTests.swift; sourceTree = "<group>"; };
|
||||
44E8BAA982EC260E74CCCB22 /* MapLibreShim.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapLibreShim.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
44ECC9D66400727DFFEE12E8 /* TimelineStartRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
450E04B2A976CC4C8CC1807C /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
4525E8C0FBDD27D1ACE90952 /* SecureBackupKeyBackupScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -2326,6 +2353,7 @@
|
||||
722013E20EEED73553EC6F8D /* RoomHistorySharingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHistorySharingState.swift; sourceTree = "<group>"; };
|
||||
723B055A57857BFF0F18D9CB /* test_rotated_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = test_rotated_image.jpg; sourceTree = "<group>"; };
|
||||
72614BFF35B8394C6E13F55A /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = "<group>"; };
|
||||
726D7EB0E16F7E4F6DD95395 /* MapLibreShimProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreShimProtocol.swift; sourceTree = "<group>"; };
|
||||
726E901DF76393E335FD7E8E /* ManageAuthorizedSpacesScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAuthorizedSpacesScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
72C4A2D279386A811BDC6DAE /* LinkNewDeviceScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreen.swift; sourceTree = "<group>"; };
|
||||
72D03D36422177EF01905D20 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@@ -2476,6 +2504,7 @@
|
||||
8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = "<group>"; };
|
||||
89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreen.swift; sourceTree = "<group>"; };
|
||||
89ABAF691BA268B93C06142A /* MapLibreShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreShim.swift; sourceTree = "<group>"; };
|
||||
89BB11A792EF6F70B95B467E /* EncryptionResetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetTests.swift; sourceTree = "<group>"; };
|
||||
89D7846847BA9D2C5346ED9C /* EditAvatarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAvatarButtonStyle.swift; sourceTree = "<group>"; };
|
||||
89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormButtonStyles.swift; sourceTree = "<group>"; };
|
||||
@@ -2656,6 +2685,7 @@
|
||||
A9FC06B3414D3BBD709E72E4 /* CLLocationCoordinate2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocationCoordinate2D.swift; sourceTree = "<group>"; };
|
||||
AA12A7F5EF5C6D0B992869ED /* LiveLocationShareProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationShareProxy.swift; sourceTree = "<group>"; };
|
||||
AA19C32BD97F45847724E09A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Untranslated.strings; sourceTree = "<group>"; };
|
||||
AA8686F7CDEFAE2CBB3B9117 /* MapViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewConfiguration.swift; sourceTree = "<group>"; };
|
||||
AAC9344689121887B74877AF /* UnitTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AACE9B8E1A4AE79A7E2914F6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserProfile+Mock.swift"; sourceTree = "<group>"; };
|
||||
@@ -2971,6 +3001,7 @@
|
||||
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; };
|
||||
E0C086BE461902C44B608393 /* MapLibreAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreAnnotation.swift; sourceTree = "<group>"; };
|
||||
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
||||
E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; };
|
||||
@@ -3127,7 +3158,6 @@
|
||||
FA393404F96DFB505B86E913 /* CurrentValuePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisherTests.swift; sourceTree = "<group>"; };
|
||||
FA3EB5B1848CF4F64E63C6B7 /* PermalinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkTests.swift; sourceTree = "<group>"; };
|
||||
FA723686F23EF45E2B398FBC /* TestablePreviewsDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreviewsDictionary.swift; sourceTree = "<group>"; };
|
||||
FA74F57B0DA3B9A9DD51F691 /* CoordinateAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinateAnimator.swift; sourceTree = "<group>"; };
|
||||
FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
FABAC5C4373B0EC24D399663 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/SAS.strings"; sourceTree = "<group>"; };
|
||||
FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
@@ -3193,6 +3223,15 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B2E8191126927DDE67256B6E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA4ABC37ED49F6617091F142 /* MapLibreInterface.framework in Frameworks */,
|
||||
5D586C5CED7B7B086C61B84A /* MapLibre in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
BF59B36A7B2DB184B62826F6 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -3268,6 +3307,7 @@
|
||||
FC10228E73323BDC09526F97 /* LRUCache in Frameworks */,
|
||||
EAC6FE2CD4F50A43068ADCD8 /* Mantis in Frameworks */,
|
||||
76D4D5312ED8E42B35C29FA3 /* MapLibreInterface.framework in Frameworks */,
|
||||
A79634DCE52B5021F549E0E9 /* MapLibreShim.framework in Frameworks */,
|
||||
754602A7B2AAD443C4228ED4 /* MapLibre in Frameworks */,
|
||||
B0CB16349B96262AA65A04AF /* PostHog in Frameworks */,
|
||||
36AD4DD4C798E22584ED3200 /* SwiftState in Frameworks */,
|
||||
@@ -3651,6 +3691,14 @@
|
||||
path = MediaUploadPreviewScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2368DD797236140F1D21429C /* MapLibreShim */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
57A8D2B00E79926C6E5E6F49 /* Sources */,
|
||||
);
|
||||
path = MapLibreShim;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24FBB8F76D6435033CD07747 /* ManageAuthorizedSpacesScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3746,6 +3794,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2F311A9BA03995E736E065DE /* MapLibreInterface */,
|
||||
2368DD797236140F1D21429C /* MapLibreShim */,
|
||||
);
|
||||
path = MapLibre;
|
||||
sourceTree = "<group>";
|
||||
@@ -4700,6 +4749,17 @@
|
||||
path = AppLockSetupPINScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
57A8D2B00E79926C6E5E6F49 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
10A8171DA78BA39F07D669B1 /* CoordinateAnimator.swift */,
|
||||
E0C086BE461902C44B608393 /* MapLibreAnnotation.swift */,
|
||||
3989F50ECBC5AEE2E5A062DA /* MapLibreMapView.swift */,
|
||||
89ABAF691BA268B93C06142A /* MapLibreShim.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
593C7129C5927E25AD8B688F /* FlowCoordinators */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -4926,6 +4986,7 @@
|
||||
9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */,
|
||||
73F3153AB9D628110878E24F /* libMatrixRustSDKMocks.a */,
|
||||
842F63AA68E8EB410B1BA29F /* MapLibreInterface.framework */,
|
||||
44E8BAA982EC260E74CCCB22 /* MapLibreShim.framework */,
|
||||
0D8F620C8B314840D8602E3F /* NSE.appex */,
|
||||
D95E8C0EFEC0C6F96EDAA71A /* PreviewTests.xctest */,
|
||||
3D8BEEFCA07BEA43F4F4BF77 /* ShareExtension.appex */,
|
||||
@@ -6090,7 +6151,9 @@
|
||||
A7793413E64FE8831FE95F57 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
726D7EB0E16F7E4F6DD95395 /* MapLibreShimProtocol.swift */,
|
||||
33FF3E6EED8FC58755AF3A42 /* MapModels.swift */,
|
||||
AA8686F7CDEFAE2CBB3B9117 /* MapViewConfiguration.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -6468,7 +6531,6 @@
|
||||
C17C3586C93F3A314C1CC318 /* MapLibre */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FA74F57B0DA3B9A9DD51F691 /* CoordinateAnimator.swift */,
|
||||
AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */,
|
||||
622D09D4ECE759189009AEAF /* MapLibreMapView.swift */,
|
||||
B81B6170DB690013CEB646F4 /* MapLibreModels.swift */,
|
||||
@@ -7364,6 +7426,26 @@
|
||||
productReference = AAC9344689121887B74877AF /* UnitTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
392E7AA23D7BA2CEEB25CF73 /* MapLibreShim */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4986BC84F7961B6E169BC9A2 /* Build configuration list for PBXNativeTarget "MapLibreShim" */;
|
||||
buildPhases = (
|
||||
2258F3E0D784959ADD4FCE50 /* Sources */,
|
||||
B2E8191126927DDE67256B6E /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
233E31868DA9FF26EAB3044D /* PBXTargetDependency */,
|
||||
);
|
||||
name = MapLibreShim;
|
||||
packageProductDependencies = (
|
||||
7E8600FD98955C620F83BFBA /* MapLibre */,
|
||||
);
|
||||
productName = MapLibreShim;
|
||||
productReference = 44E8BAA982EC260E74CCCB22 /* MapLibreShim.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
676A87694F0F247B2AF1A142 /* MatrixRustSDKMocks */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4272AFBE93EEF7B03D9B9ECD /* Build configuration list for PBXNativeTarget "MatrixRustSDKMocks" */;
|
||||
@@ -7467,6 +7549,7 @@
|
||||
58C473A5DEA945AACFEA8E9F /* PBXTargetDependency */,
|
||||
2E32BC489F482046B8B1460F /* PBXTargetDependency */,
|
||||
0C42CEF186BBA0F3DBC17B85 /* PBXTargetDependency */,
|
||||
36A05892F02FA9721A47871F /* PBXTargetDependency */,
|
||||
);
|
||||
name = ElementX;
|
||||
packageProductDependencies = (
|
||||
@@ -7580,6 +7663,9 @@
|
||||
32C23C8D224D46EFE62AFAD0 = {
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
392E7AA23D7BA2CEEB25CF73 = {
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
676A87694F0F247B2AF1A142 = {
|
||||
DevelopmentTeam = 7J4U792NQT;
|
||||
};
|
||||
@@ -7694,6 +7780,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
96435A765678A64A956EE59C /* MapLibreInterface */,
|
||||
392E7AA23D7BA2CEEB25CF73 /* MapLibreShim */,
|
||||
C0FAEB81CFD9776CD78CE489 /* ElementX */,
|
||||
FEB53A5BC378C913769656D8 /* NSE */,
|
||||
19F0C845D67E9BEA4BE7133E /* ShareExtension */,
|
||||
@@ -8230,11 +8317,24 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
2258F3E0D784959ADD4FCE50 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6B47F2C6B11A1EDF0480DA71 /* CoordinateAnimator.swift in Sources */,
|
||||
6BD28DF22B18AA9269BD97D4 /* MapLibreAnnotation.swift in Sources */,
|
||||
310E1D9D246277A4FFA66162 /* MapLibreMapView.swift in Sources */,
|
||||
DC1321AC05EE491E5D5A9A50 /* MapLibreShim.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
294638399D803C4F2154D2B7 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D89E46C3AAD462407D332046 /* MapLibreShimProtocol.swift in Sources */,
|
||||
A05D6ACD38082D3E0A14D63E /* MapModels.swift in Sources */,
|
||||
82A33B40198D4EDD9F90493A /* MapViewConfiguration.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -8510,7 +8610,6 @@
|
||||
E8F88136C8E69D9E69621E19 /* ContentScannerServiceProtocol.swift in Sources */,
|
||||
9DD0DFFEFCA51AE241B637D1 /* ContentScanningFailureView.swift in Sources */,
|
||||
681FA9A37E82A14EDFB22D56 /* ContentScanningView.swift in Sources */,
|
||||
73DBE886625AF56FF08D7F76 /* CoordinateAnimator.swift in Sources */,
|
||||
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */,
|
||||
633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */,
|
||||
CE8296D4AD30DDC6D0C67A74 /* CreateRoomScreen.swift in Sources */,
|
||||
@@ -9474,6 +9573,11 @@
|
||||
target = C0FAEB81CFD9776CD78CE489 /* ElementX */;
|
||||
targetProxy = 4D8DD8FE84794CA168A8499A /* PBXContainerItemProxy */;
|
||||
};
|
||||
233E31868DA9FF26EAB3044D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 96435A765678A64A956EE59C /* MapLibreInterface */;
|
||||
targetProxy = D96A382EE558F49A8070D92A /* PBXContainerItemProxy */;
|
||||
};
|
||||
2C29670603B37E38705D5FF1 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = FEB53A5BC378C913769656D8 /* NSE */;
|
||||
@@ -9484,6 +9588,11 @@
|
||||
target = 676A87694F0F247B2AF1A142 /* MatrixRustSDKMocks */;
|
||||
targetProxy = 2324965D972BC4812FE0F380 /* PBXContainerItemProxy */;
|
||||
};
|
||||
36A05892F02FA9721A47871F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 392E7AA23D7BA2CEEB25CF73 /* MapLibreShim */;
|
||||
targetProxy = 5F1D4E7C20485C444FA1D42F /* PBXContainerItemProxy */;
|
||||
};
|
||||
421359F1BC0A1816DD34A2BB /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C0FAEB81CFD9776CD78CE489 /* ElementX */;
|
||||
@@ -9929,6 +10038,32 @@
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
68ED112958B6D89CEFACC03F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).maplibre-shim";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_VERSION = 6.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
6EE786F14472D5CCF8DD0980 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -10252,6 +10387,32 @@
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A84BC5F889292CF399F36B08 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).maplibre-shim";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
SWIFT_VERSION = 6.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
AAE81BF8DCDB30B237B10C3E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -10364,6 +10525,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
4986BC84F7961B6E169BC9A2 /* Build configuration list for PBXNativeTarget "MapLibreShim" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
68ED112958B6D89CEFACC03F /* Debug */,
|
||||
A84BC5F889292CF399F36B08 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
79663128986C62EFAC289176 /* Build configuration list for PBXNativeTarget "UnitTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -10876,6 +11046,11 @@
|
||||
package = E9C4F3A12AA1F65C13A8C8EB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
|
||||
productName = SnapshotTesting;
|
||||
};
|
||||
7E8600FD98955C620F83BFBA /* MapLibre */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 0CBF57301AA172C21F76CE86 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */;
|
||||
productName = MapLibre;
|
||||
};
|
||||
800631D7250B7F93195035F1 /* KeychainAccess */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 61916C63E3F5BD900F08DA0C /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// Implemented by MapLibreShim's entry point, which the app instantiates after
|
||||
/// `dlopen`-ing the framework.
|
||||
public protocol MapLibreShimProtocol {
|
||||
/// Routes MapLibre's logs into the app's logger. Call once after loading the shim.
|
||||
func configureLogging(_ handler: @escaping @Sendable (MapLogSeverity, String) -> Void)
|
||||
|
||||
/// Builds the interactive map view. The app-side `MapLibreMapView` forwards every
|
||||
/// body evaluation here so SwiftUI updates flow through to the underlying map.
|
||||
func makeMapView(configuration: MapViewConfiguration) -> AnyView
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
@@ -33,3 +34,23 @@ public enum MapLibreError: Error, Hashable {
|
||||
case failedLoadingMap
|
||||
case failedLocatingUser
|
||||
}
|
||||
|
||||
/// The severity of a map log message forwarded to the app.
|
||||
public enum MapLogSeverity {
|
||||
case error
|
||||
case warning
|
||||
case info
|
||||
case debug
|
||||
case verbose
|
||||
}
|
||||
|
||||
/// A marker shown on the map, identified so that it can be moved in place when its coordinate changes.
|
||||
public struct MapAnnotation {
|
||||
public let id: String
|
||||
public let coordinate: CLLocationCoordinate2D
|
||||
|
||||
public init(id: String, coordinate: CLLocationCoordinate2D) {
|
||||
self.id = id
|
||||
self.coordinate = coordinate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import SwiftUI
|
||||
|
||||
/// Everything the map implementation needs in order to render, expressed without app
|
||||
/// types so that the implementation framework can be loaded on demand.
|
||||
public struct MapViewConfiguration {
|
||||
/// the final zoom level used when the first user location emit
|
||||
public let zoomLevel: Double
|
||||
/// The initial zoom level used when the map it firstly loaded and the user location is not yet available, in case of annotations this property is not being used
|
||||
public let initialZoomLevel: Double
|
||||
|
||||
/// The initial map center
|
||||
public let mapCenter: CLLocationCoordinate2D
|
||||
|
||||
/// Map annotations
|
||||
public let annotations: [MapAnnotation]
|
||||
|
||||
/// The style to load the map with, for either the dark or the light appearance.
|
||||
public let styleURL: (_ isDark: Bool) -> URL?
|
||||
|
||||
public let tintColor: UIColor
|
||||
|
||||
/// The content of the marker representing the annotation with the given id.
|
||||
public let markerView: (_ annotationID: String) -> AnyView?
|
||||
|
||||
/// Behavior mode of the current user's location, can be hidden, only shown and shown following the user
|
||||
public let showsUserLocationMode: Binding<ShowUserLocationMode>
|
||||
/// Bind view errors if any
|
||||
public let error: Binding<MapLibreError?>
|
||||
/// Coordinate of the center of the map
|
||||
public let mapCenterCoordinate: Binding<CLLocationCoordinate2D?>
|
||||
public let hasLoadedUserLocation: Binding<Bool>
|
||||
public let isLocationAuthorized: Binding<Bool?>
|
||||
/// The radius of uncertainty for the location, measured in meters.
|
||||
public let geolocationUncertainty: Binding<CLLocationAccuracy?>
|
||||
|
||||
/// Called when the user pan on the map
|
||||
public let userDidPan: (() -> Void)?
|
||||
|
||||
public init(zoomLevel: Double,
|
||||
initialZoomLevel: Double,
|
||||
mapCenter: CLLocationCoordinate2D,
|
||||
annotations: [MapAnnotation] = [],
|
||||
styleURL: @escaping (_ isDark: Bool) -> URL?,
|
||||
tintColor: UIColor,
|
||||
markerView: @escaping (_ annotationID: String) -> AnyView?,
|
||||
showsUserLocationMode: Binding<ShowUserLocationMode>,
|
||||
error: Binding<MapLibreError?>,
|
||||
mapCenterCoordinate: Binding<CLLocationCoordinate2D?>,
|
||||
hasLoadedUserLocation: Binding<Bool>,
|
||||
isLocationAuthorized: Binding<Bool?>,
|
||||
geolocationUncertainty: Binding<CLLocationAccuracy?>,
|
||||
userDidPan: (() -> Void)? = nil) {
|
||||
self.zoomLevel = zoomLevel
|
||||
self.initialZoomLevel = initialZoomLevel
|
||||
self.mapCenter = mapCenter
|
||||
self.annotations = annotations
|
||||
self.styleURL = styleURL
|
||||
self.tintColor = tintColor
|
||||
self.markerView = markerView
|
||||
self.showsUserLocationMode = showsUserLocationMode
|
||||
self.error = error
|
||||
self.mapCenterCoordinate = mapCenterCoordinate
|
||||
self.hasLoadedUserLocation = hasLoadedUserLocation
|
||||
self.isLocationAuthorized = isLocationAuthorized
|
||||
self.geolocationUncertainty = geolocationUncertainty
|
||||
self.userDidPan = userDidPan
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -8,7 +8,7 @@
|
||||
import CoreLocation
|
||||
import QuartzCore
|
||||
|
||||
/// Smoothly animates a `LocationAnnotation`'s coordinate from its current
|
||||
/// Smoothly animates a `MapLibreAnnotation`'s coordinate from its current
|
||||
/// position to a new one using a `CADisplayLink` for frame-accurate updates.
|
||||
///
|
||||
/// ## How it works
|
||||
@@ -26,7 +26,7 @@ import QuartzCore
|
||||
/// cancels the previous one and begins from the annotation's current position.
|
||||
final class CoordinateAnimator {
|
||||
private var displayLink: CADisplayLink?
|
||||
private weak var annotation: LocationAnnotation?
|
||||
private weak var annotation: MapLibreAnnotation?
|
||||
private let startCoordinate: CLLocationCoordinate2D
|
||||
private let endCoordinate: CLLocationCoordinate2D
|
||||
private let duration: CFTimeInterval
|
||||
@@ -36,7 +36,7 @@ final class CoordinateAnimator {
|
||||
/// while the display link is running. Keyed by annotation ID.
|
||||
private static var activeAnimators: [String: CoordinateAnimator] = [:]
|
||||
|
||||
private init(annotation: LocationAnnotation,
|
||||
private init(annotation: MapLibreAnnotation,
|
||||
to end: CLLocationCoordinate2D,
|
||||
duration: CFTimeInterval) {
|
||||
self.annotation = annotation
|
||||
@@ -48,7 +48,7 @@ final class CoordinateAnimator {
|
||||
/// Starts animating the annotation's coordinate to `end` over `duration` seconds.
|
||||
/// If the annotation is already being animated, the in-flight animation is
|
||||
/// cancelled and a new one starts from the current position.
|
||||
static func animate(annotation: LocationAnnotation,
|
||||
static func animate(annotation: MapLibreAnnotation,
|
||||
to end: CLLocationCoordinate2D,
|
||||
duration: CFTimeInterval) {
|
||||
guard annotation.coordinate.latitude != end.latitude
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
// Copyright 2023-2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import MapLibre
|
||||
import MapLibreInterface
|
||||
import SwiftUI
|
||||
|
||||
final class MapLibreAnnotation: NSObject, MLNAnnotation, Identifiable {
|
||||
let id: String
|
||||
// @objc dynamic is required to make animations work
|
||||
@objc dynamic var coordinate: CLLocationCoordinate2D
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(_ annotation: MapAnnotation) {
|
||||
id = annotation.id
|
||||
coordinate = annotation.coordinate
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
final class MapLibreAnnotationView: MLNUserLocationAnnotationView {
|
||||
private var hostingController: UIHostingController<AnyView>?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override init(annotation: MLNAnnotation?, reuseIdentifier: String?) {
|
||||
super.init(annotation: annotation, reuseIdentifier:
|
||||
reuseIdentifier)
|
||||
}
|
||||
|
||||
convenience init(annotation: MapLibreAnnotation, content: AnyView?) {
|
||||
self.init(annotation: annotation, reuseIdentifier: "\(Self.self)")
|
||||
let hostingController = UIHostingController(rootView: content ?? AnyView(EmptyView()))
|
||||
self.hostingController = hostingController
|
||||
let view: UIView = hostingController.view
|
||||
view.backgroundColor = .clear
|
||||
view.anchorPoint = .init(x: 0.5, y: 1.0)
|
||||
addSubview(view)
|
||||
view.bounds.size = view.intrinsicContentSize
|
||||
}
|
||||
|
||||
func updateContent(_ content: AnyView?) {
|
||||
hostingController?.rootView = content ?? AnyView(EmptyView())
|
||||
if let hostedView = hostingController?.view {
|
||||
hostedView.bounds.size = hostedView.intrinsicContentSize
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
// Copyright 2023-2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import MapLibre
|
||||
import MapLibreInterface
|
||||
import SwiftUI
|
||||
|
||||
struct MapLibreMapView: UIViewRepresentable {
|
||||
// MARK: - Properties
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
let configuration: MapViewConfiguration
|
||||
|
||||
// MARK: - UIViewRepresentable
|
||||
|
||||
func makeUIView(context: Context) -> MLNMapView {
|
||||
let mapView = makeMapView()
|
||||
mapView.delegate = context.coordinator
|
||||
setupMap(mapView: mapView)
|
||||
return mapView
|
||||
}
|
||||
|
||||
func updateUIView(_ mapView: MLNMapView, context: Context) {
|
||||
// Don't set the same value twice. Otherwise, if there is an error loading the map, a loop
|
||||
// is caused as the `error` binding being set, which triggers this update, which sets a
|
||||
// new URL, which causes another error, and so it goes on round and round in a circle.
|
||||
let dynamicMapURL = configuration.styleURL(colorScheme == .dark)
|
||||
if mapView.styleURL != dynamicMapURL {
|
||||
mapView.styleURL = dynamicMapURL
|
||||
}
|
||||
|
||||
// If the center coordinate was updated externally (not by the map itself), move the map.
|
||||
// Not applied while following a marker, where the camera is driven by the marker's position.
|
||||
if configuration.showsUserLocationMode.wrappedValue.followedMarkerID == nil,
|
||||
let newCenter = configuration.mapCenterCoordinate.wrappedValue,
|
||||
newCenter.latitude != context.coordinator.lastReportedCenter?.latitude
|
||||
|| newCenter.longitude != context.coordinator.lastReportedCenter?.longitude {
|
||||
context.coordinator.lastReportedCenter = newCenter
|
||||
mapView.setCenter(newCenter, animated: true)
|
||||
}
|
||||
|
||||
// Update existing annotation views with fresh SwiftUI content.
|
||||
// This handles the case where the annotation's view data changes after
|
||||
// the annotation was initially placed (e.g. user avatar loads asynchronously).
|
||||
updateAnnotations(in: mapView)
|
||||
|
||||
showUserLocation(in: mapView)
|
||||
|
||||
context.coordinator.updateMarkerFollowing(in: mapView, markerID: configuration.showsUserLocationMode.wrappedValue.followedMarkerID)
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupMap(mapView: MLNMapView) {
|
||||
mapView.addAnnotations(configuration.annotations.map(MapLibreAnnotation.init))
|
||||
mapView.zoomLevel = configuration.annotations.isEmpty ? configuration.initialZoomLevel : configuration.zoomLevel
|
||||
mapView.centerCoordinate = configuration.mapCenter
|
||||
}
|
||||
|
||||
private func updateAnnotations(in mapView: MLNMapView) {
|
||||
let existingByID = Dictionary(uniqueKeysWithValues:
|
||||
(mapView.annotations ?? []).compactMap { $0 as? MapLibreAnnotation }.map { ($0.id, $0) })
|
||||
let updatedByID = Dictionary(uniqueKeysWithValues: configuration.annotations.map { ($0.id, $0) })
|
||||
|
||||
let existingIDs = Set(existingByID.keys)
|
||||
let updatedIDs = Set(updatedByID.keys)
|
||||
|
||||
// Remove annotations that are no longer present
|
||||
let removedIDs = existingIDs.subtracting(updatedIDs)
|
||||
if !removedIDs.isEmpty {
|
||||
let toRemove = removedIDs.compactMap { existingByID[$0] }
|
||||
mapView.removeAnnotations(toRemove)
|
||||
}
|
||||
|
||||
// Add new annotations
|
||||
let addedIDs = updatedIDs.subtracting(existingIDs)
|
||||
if !addedIDs.isEmpty {
|
||||
let toAdd = addedIDs.compactMap { updatedByID[$0] }.map(MapLibreAnnotation.init)
|
||||
mapView.addAnnotations(toAdd)
|
||||
}
|
||||
|
||||
// Update existing annotations that are still present
|
||||
let keptIDs = existingIDs.intersection(updatedIDs)
|
||||
for id in keptIDs {
|
||||
guard let existingAnnotation = existingByID[id],
|
||||
let updatedAnnotation = updatedByID[id] else {
|
||||
continue
|
||||
}
|
||||
CoordinateAnimator.animate(annotation: existingAnnotation,
|
||||
to: updatedAnnotation.coordinate,
|
||||
duration: 1.0)
|
||||
if let annotationView = mapView.view(for: existingAnnotation) as? MapLibreAnnotationView {
|
||||
annotationView.updateContent(configuration.markerView(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeMapView() -> MLNMapView {
|
||||
let mapView = MLNMapView(frame: .zero, styleURL: configuration.styleURL(colorScheme == .dark))
|
||||
mapView.logoViewPosition = .topLeft
|
||||
mapView.attributionButtonPosition = .topLeft
|
||||
mapView.attributionButtonMargins = .init(x: mapView.logoView.frame.maxX + 8, y: mapView.logoView.center.y / 2)
|
||||
mapView.tintColor = configuration.tintColor
|
||||
mapView.allowsRotating = false
|
||||
mapView.allowsTilting = false
|
||||
return mapView
|
||||
}
|
||||
|
||||
private func showUserLocation(in mapView: MLNMapView) {
|
||||
switch (configuration.showsUserLocationMode.wrappedValue, configuration.annotations) {
|
||||
case (.showAndFollow, _):
|
||||
mapView.userTrackingMode = .follow
|
||||
case (.show, let annotations) where !annotations.isEmpty:
|
||||
// In the show mode, if there are annotations, we check the authorizationStatus,
|
||||
// if it's not determined, we wont prompt the user with a request for permissions,
|
||||
// because they should be able to see the annotations without sharing their location information.
|
||||
guard mapView.locationManager.authorizationStatus != .notDetermined else { return }
|
||||
fallthrough
|
||||
case (.show, _):
|
||||
mapView.showsUserLocation = true
|
||||
mapView.setUserTrackingMode(.none, animated: false, completionHandler: nil)
|
||||
case (.hide, _), (.hideAndFollowMarker, _):
|
||||
// In hideAndFollowMarker mode the camera following is handled by the coordinator.
|
||||
mapView.showsUserLocation = false
|
||||
mapView.setUserTrackingMode(.none, animated: false, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
extension MapLibreMapView {
|
||||
class Coordinator: NSObject, MLNMapViewDelegate {
|
||||
// MARK: - Properties
|
||||
|
||||
var mapLibreView: MapLibreMapView
|
||||
|
||||
private var previousUserLocation: MLNUserLocation?
|
||||
/// Tracks the last center coordinate reported by the map (or set programmatically),
|
||||
/// so that `updateUIView` can tell apart external binding changes from internal ones.
|
||||
var lastReportedCenter: CLLocationCoordinate2D?
|
||||
|
||||
/// The annotation the camera is currently locked on while in the `hideAndFollowMarker` mode.
|
||||
private weak var followedAnnotation: MapLibreAnnotation?
|
||||
/// Observes the followed annotation's coordinate, updated every frame by the `CoordinateAnimator`.
|
||||
private var followedAnnotationObservation: NSKeyValueObservation?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(_ mapLibreView: MapLibreMapView) {
|
||||
self.mapLibreView = mapLibreView
|
||||
}
|
||||
|
||||
// MARK: - Marker following
|
||||
|
||||
/// Keeps the camera locked on the marker with the given id, or stops following when nil.
|
||||
func updateMarkerFollowing(in mapView: MLNMapView, markerID: String?) {
|
||||
guard let markerID else {
|
||||
stopFollowingMarker()
|
||||
return
|
||||
}
|
||||
|
||||
guard let annotation = (mapView.annotations ?? [])
|
||||
.compactMap({ $0 as? MapLibreAnnotation })
|
||||
.first(where: { $0.id == markerID }) else {
|
||||
// The marker isn't on the map (yet), resolve it again on the next update.
|
||||
stopFollowingMarker()
|
||||
return
|
||||
}
|
||||
|
||||
guard annotation !== followedAnnotation else { return }
|
||||
startFollowingMarker(annotation, in: mapView)
|
||||
}
|
||||
|
||||
private func startFollowingMarker(_ annotation: MapLibreAnnotation, in mapView: MLNMapView) {
|
||||
followedAnnotation = annotation
|
||||
followedAnnotationObservation = nil
|
||||
|
||||
// Animate to the marker first, attaching the per-frame tracking only on completion
|
||||
// so that it doesn't cancel the transition.
|
||||
mapView.setCenter(annotation.coordinate,
|
||||
zoomLevel: mapView.zoomLevel,
|
||||
direction: -1, // negative value keeps the current direction
|
||||
animated: true) { [weak self, weak annotation, weak mapView] in
|
||||
guard let self, let annotation, let mapView, annotation === followedAnnotation else { return }
|
||||
|
||||
// Mirror every animated coordinate update to keep the camera as smooth as the marker.
|
||||
followedAnnotationObservation = annotation.observe(\.coordinate) { [weak mapView] annotation, _ in
|
||||
// The coordinate is only ever updated by SwiftUI so KVO fires on the main actor.
|
||||
MainActor.assumeIsolated {
|
||||
mapView?.setCenter(annotation.coordinate, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopFollowingMarker() {
|
||||
followedAnnotation = nil
|
||||
followedAnnotationObservation = nil
|
||||
}
|
||||
|
||||
// MARK: - MLNMapViewDelegate
|
||||
|
||||
func mapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? {
|
||||
guard let annotation = annotation as? MapLibreAnnotation else {
|
||||
return nil
|
||||
}
|
||||
return MapLibreAnnotationView(annotation: annotation, content: mapLibreView.configuration.markerView(annotation.id))
|
||||
}
|
||||
|
||||
func mapViewDidFailLoadingMap(_ mapView: MLNMapView, withError error: Error) {
|
||||
if mapLibreView.configuration.error.wrappedValue != .failedLoadingMap {
|
||||
mapLibreView.configuration.error.wrappedValue = .failedLoadingMap
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, didUpdate userLocation: MLNUserLocation?) {
|
||||
guard let userLocation else { return }
|
||||
mapLibreView.configuration.hasLoadedUserLocation.wrappedValue = true
|
||||
|
||||
if previousUserLocation == nil, mapLibreView.configuration.annotations.isEmpty {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
mapView.setCenter(userLocation.coordinate, zoomLevel: self.mapLibreView.configuration.zoomLevel, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
previousUserLocation = userLocation
|
||||
updateGeolocationUncertainty(location: userLocation)
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) {
|
||||
switch manager.authorizationStatus {
|
||||
case .denied, .restricted:
|
||||
mapLibreView.configuration.isLocationAuthorized.wrappedValue = false
|
||||
case .authorizedAlways, .authorizedWhenInUse:
|
||||
mapLibreView.configuration.isLocationAuthorized.wrappedValue = true
|
||||
case .notDetermined:
|
||||
mapLibreView.configuration.isLocationAuthorized.wrappedValue = nil
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) {
|
||||
// While following a marker, don't flood the center binding with per-frame camera updates.
|
||||
// Leaving `lastReportedCenter` untouched avoids an external re-centering when following stops.
|
||||
guard mapLibreView.configuration.showsUserLocationMode.wrappedValue.followedMarkerID == nil else { return }
|
||||
|
||||
// Avoid `Publishing changes from within view update` warnings
|
||||
DispatchQueue.main.async { [mapLibreView, weak self] in
|
||||
let center = mapView.centerCoordinate
|
||||
self?.lastReportedCenter = center
|
||||
mapLibreView.configuration.mapCenterCoordinate.wrappedValue = center
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, shouldChangeFrom oldCamera: MLNMapCamera, to newCamera: MLNMapCamera, reason: MLNCameraChangeReason) -> Bool {
|
||||
// Send userDidPan only for gestures that actually change the map center. The reason
|
||||
// is an option set and gestures can come combined with other reasons, so check for
|
||||
// containment instead of an exact match.
|
||||
let centerChangingGestures: MLNCameraChangeReason = [.gesturePan, .gesturePinch, .gestureRotate]
|
||||
if !reason.isDisjoint(with: centerChangingGestures) {
|
||||
// Stop following immediately, the camera would fight the gesture otherwise.
|
||||
stopFollowingMarker()
|
||||
mapLibreView.configuration.userDidPan?()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: Callout
|
||||
|
||||
func mapView(_ mapView: MLNMapView, annotationCanShowCallout annotation: MLNAnnotation) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func updateGeolocationUncertainty(location: MLNUserLocation) {
|
||||
guard let clLocation = location.location, clLocation.horizontalAccuracy >= 0 else {
|
||||
mapLibreView.configuration.geolocationUncertainty.wrappedValue = nil
|
||||
return
|
||||
}
|
||||
|
||||
mapLibreView.configuration.geolocationUncertainty.wrappedValue = clLocation.horizontalAccuracy
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import MapLibre
|
||||
import MapLibreInterface
|
||||
import SwiftUI
|
||||
|
||||
/// The shim's entry point, vending the interactive map view to the app.
|
||||
public final class MapLibreShim: NSObject, MapLibreShimProtocol {
|
||||
public func configureLogging(_ handler: @escaping @Sendable (MapLogSeverity, String) -> Void) {
|
||||
MLNLoggingConfiguration.shared.loggingLevel = .debug
|
||||
MLNLoggingConfiguration.shared.handler = { loggingLevel, _, _, message in
|
||||
switch loggingLevel {
|
||||
case .error:
|
||||
handler(.error, message)
|
||||
case .warning:
|
||||
handler(.warning, message)
|
||||
case .info:
|
||||
handler(.info, message)
|
||||
case .debug:
|
||||
handler(.debug, message)
|
||||
case .verbose:
|
||||
handler(.verbose, message)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func makeMapView(configuration: MapViewConfiguration) -> AnyView {
|
||||
AnyView(MapLibreMapView(configuration: configuration))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# The interactive map, split out of the app so MapLibre's static initialisers stay off the
|
||||
# launch path: the app links only the tiny MapLibreInterface framework, while MapLibreShim (the
|
||||
# only image linking MapLibre) is embedded unlinked and dlopen'd on first map use.
|
||||
name: MapLibreShim
|
||||
|
||||
targets:
|
||||
MapLibreShim:
|
||||
type: framework
|
||||
platform: iOS
|
||||
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: $(BASE_BUNDLE_IDENTIFIER).maplibre-shim
|
||||
GENERATE_INFOPLIST_FILE: YES
|
||||
SWIFT_VERSION: 6.2
|
||||
SWIFT_APPROACHABLE_CONCURRENCY: YES
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION: MainActor
|
||||
|
||||
dependencies:
|
||||
- target: MapLibreInterface
|
||||
embed: false
|
||||
- package: MapLibre
|
||||
embed: false
|
||||
|
||||
sources:
|
||||
- path: ../Sources
|
||||
@@ -8,9 +8,6 @@
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
#if IS_MAIN_APP
|
||||
import MapLibre
|
||||
#endif
|
||||
import MatrixRustSDK
|
||||
|
||||
nonisolated enum Target: String {
|
||||
@@ -65,8 +62,6 @@ nonisolated enum Target: String {
|
||||
|
||||
MXLog.configure(currentTarget: rawValue)
|
||||
|
||||
configureMapLibreIfAvailable()
|
||||
|
||||
let hookCancellable = rageshakeURL.publisher
|
||||
.sink { _ in
|
||||
appHooks.tracingHook.update(tracingConfiguration, with: rageshakeURL)
|
||||
@@ -75,28 +70,6 @@ nonisolated enum Target: String {
|
||||
return ConfigurationResult(hookCancellable: hookCancellable)
|
||||
}
|
||||
|
||||
private func configureMapLibreIfAvailable() {
|
||||
#if IS_MAIN_APP
|
||||
MLNLoggingConfiguration.shared.loggingLevel = .debug
|
||||
MLNLoggingConfiguration.shared.handler = { loggingLevel, _, _, message in
|
||||
switch loggingLevel {
|
||||
case .error:
|
||||
MXLog.error(message)
|
||||
case .warning:
|
||||
MXLog.warning(message)
|
||||
case .info:
|
||||
MXLog.info(message)
|
||||
case .debug:
|
||||
MXLog.debug(message)
|
||||
case .verbose:
|
||||
MXLog.verbose(message)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// The result of calling ``configure(logLevel:traceLogPacks:sentryURL:)``.
|
||||
/// This must be stored - see the docs on the configure method to learn more.
|
||||
struct ConfigurationResult {
|
||||
|
||||
@@ -8,57 +8,9 @@
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import MapLibre
|
||||
import SwiftUI
|
||||
|
||||
final class LocationAnnotation: NSObject, MLNAnnotation, Identifiable {
|
||||
struct LocationAnnotation: Identifiable {
|
||||
let id: String
|
||||
// @objc dynamic is required to make animations work
|
||||
@objc dynamic var coordinate: CLLocationCoordinate2D
|
||||
var kind: LocationMarkerKind
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(id: String, coordinate: CLLocationCoordinate2D, kind: LocationMarkerKind) {
|
||||
self.id = id
|
||||
self.coordinate = coordinate
|
||||
self.kind = kind
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
final class LocationAnnotationView: MLNUserLocationAnnotationView {
|
||||
private var hostingController: UIHostingController<AnyView>?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override init(annotation: MLNAnnotation?, reuseIdentifier: String?) {
|
||||
super.init(annotation: annotation, reuseIdentifier:
|
||||
reuseIdentifier)
|
||||
}
|
||||
|
||||
convenience init(annotation: LocationAnnotation, mediaProvider: MediaProviderProtocol?) {
|
||||
self.init(annotation: annotation, reuseIdentifier: "\(Self.self)")
|
||||
let markerView = LocationMarkerView(kind: annotation.kind, mediaProvider: mediaProvider)
|
||||
let hostingController = UIHostingController(rootView: AnyView(markerView))
|
||||
self.hostingController = hostingController
|
||||
let view: UIView = hostingController.view
|
||||
view.backgroundColor = .clear
|
||||
view.anchorPoint = .init(x: 0.5, y: 1.0)
|
||||
addSubview(view)
|
||||
view.bounds.size = view.intrinsicContentSize
|
||||
}
|
||||
|
||||
func updateContent(with kind: LocationMarkerKind, mediaProvider: MediaProviderProtocol?) {
|
||||
let markerView = LocationMarkerView(kind: kind, mediaProvider: mediaProvider)
|
||||
hostingController?.rootView = AnyView(markerView)
|
||||
if let hostedView = hostingController?.view {
|
||||
hostedView.bounds.size = hostedView.intrinsicContentSize
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
let coordinate: CLLocationCoordinate2D
|
||||
let kind: LocationMarkerKind
|
||||
}
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations Ltd.
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
// Copyright 2023-2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import MapLibre
|
||||
import Compound
|
||||
import CoreLocation
|
||||
import MapLibreInterface
|
||||
import MapLibreShim
|
||||
import SwiftUI
|
||||
|
||||
struct MapLibreMapView: UIViewRepresentable {
|
||||
/// The interactive map, rendered by the `MapLibreShim` framework.
|
||||
struct MapLibreMapView: View {
|
||||
/// Built once, so that MapLibre's logging is only configured once.
|
||||
private static let shim: MapLibreShimProtocol = {
|
||||
let shim = MapLibreShim()
|
||||
|
||||
shim.configureLogging { severity, message in
|
||||
switch severity {
|
||||
case .error:
|
||||
MXLog.error(message)
|
||||
case .warning:
|
||||
MXLog.warning(message)
|
||||
case .info:
|
||||
MXLog.info(message)
|
||||
case .debug:
|
||||
MXLog.debug(message)
|
||||
case .verbose:
|
||||
MXLog.verbose(message)
|
||||
}
|
||||
}
|
||||
|
||||
return shim
|
||||
}()
|
||||
|
||||
struct Options {
|
||||
/// the final zoom level used when the first user location emit
|
||||
let zoomLevel: Double
|
||||
@@ -32,10 +56,6 @@ struct MapLibreMapView: UIViewRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
let mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
|
||||
let options: Options
|
||||
@@ -56,295 +76,33 @@ struct MapLibreMapView: UIViewRepresentable {
|
||||
/// Called when the user pan on the map
|
||||
var userDidPan: (() -> Void)?
|
||||
|
||||
// MARK: - UIViewRepresentable
|
||||
|
||||
func makeUIView(context: Context) -> MLNMapView {
|
||||
let mapView = makeMapView()
|
||||
mapView.delegate = context.coordinator
|
||||
setupMap(mapView: mapView, with: options)
|
||||
return mapView
|
||||
var body: some View {
|
||||
Self.shim.makeMapView(configuration: configuration)
|
||||
}
|
||||
|
||||
func updateUIView(_ mapView: MLNMapView, context: Context) {
|
||||
// Don't set the same value twice. Otherwise, if there is an error loading the map, a loop
|
||||
// is caused as the `error` binding being set, which triggers this update, which sets a
|
||||
// new URL, which causes another error, and so it goes on round and round in a circle.
|
||||
let dynamicMapURL = mapURLBuilder.interactiveMapURL(for: .init(colorScheme))
|
||||
if mapView.styleURL != dynamicMapURL {
|
||||
mapView.styleURL = dynamicMapURL
|
||||
}
|
||||
/// The configuration for the shim, with the app-only pieces (marker views, tint, styles)
|
||||
/// resolved into the shared types it understands.
|
||||
private var configuration: MapViewConfiguration {
|
||||
let annotations = options.annotations
|
||||
let mapURLBuilder = mapURLBuilder
|
||||
let mediaProvider = mediaProvider
|
||||
|
||||
// If the center coordinate was updated externally (not by the map itself), move the map.
|
||||
// Not applied while following a marker, where the camera is driven by the marker's position.
|
||||
if showsUserLocationMode.followedMarkerID == nil,
|
||||
let newCenter = mapCenterCoordinate,
|
||||
newCenter != context.coordinator.lastReportedCenter {
|
||||
context.coordinator.lastReportedCenter = newCenter
|
||||
mapView.setCenter(newCenter, animated: true)
|
||||
}
|
||||
|
||||
// Update existing annotation views with fresh SwiftUI content.
|
||||
// This handles the case where the annotation's view data changes after
|
||||
// the annotation was initially placed (e.g. user avatar loads asynchronously).
|
||||
updateAnnotations(in: mapView)
|
||||
|
||||
showUserLocation(in: mapView)
|
||||
|
||||
context.coordinator.updateMarkerFollowing(in: mapView, markerID: showsUserLocationMode.followedMarkerID)
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupMap(mapView: MLNMapView, with options: Options) {
|
||||
mapView.addAnnotations(options.annotations)
|
||||
mapView.zoomLevel = options.annotations.isEmpty ? options.initialZoomLevel : options.zoomLevel
|
||||
mapView.centerCoordinate = options.mapCenter
|
||||
}
|
||||
|
||||
private func updateAnnotations(in mapView: MLNMapView) {
|
||||
let existingByID = Dictionary(uniqueKeysWithValues:
|
||||
(mapView.annotations ?? []).compactMap { $0 as? LocationAnnotation }.map { ($0.id, $0) })
|
||||
let updatedByID = Dictionary(uniqueKeysWithValues: options.annotations.map { ($0.id, $0) })
|
||||
|
||||
let existingIDs = Set(existingByID.keys)
|
||||
let updatedIDs = Set(updatedByID.keys)
|
||||
|
||||
// Remove annotations that are no longer present
|
||||
let removedIDs = existingIDs.subtracting(updatedIDs)
|
||||
if !removedIDs.isEmpty {
|
||||
let toRemove = removedIDs.compactMap { existingByID[$0] }
|
||||
mapView.removeAnnotations(toRemove)
|
||||
}
|
||||
|
||||
// Add new annotations
|
||||
let addedIDs = updatedIDs.subtracting(existingIDs)
|
||||
if !addedIDs.isEmpty {
|
||||
let toAdd = addedIDs.compactMap { updatedByID[$0] }
|
||||
mapView.addAnnotations(toAdd)
|
||||
}
|
||||
|
||||
// Update existing annotations that are still present
|
||||
let keptIDs = existingIDs.intersection(updatedIDs)
|
||||
for id in keptIDs {
|
||||
guard let existingAnnotation = existingByID[id],
|
||||
let updatedAnnotation = updatedByID[id] else {
|
||||
continue
|
||||
}
|
||||
CoordinateAnimator.animate(annotation: existingAnnotation,
|
||||
to: updatedAnnotation.coordinate,
|
||||
duration: 1.0)
|
||||
if let annotationView = mapView.view(for: existingAnnotation) as? LocationAnnotationView {
|
||||
annotationView.updateContent(with: updatedAnnotation.kind, mediaProvider: mediaProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeMapView() -> MLNMapView {
|
||||
let mapView = MLNMapView(frame: .zero, styleURL: mapURLBuilder.interactiveMapURL(for: colorScheme == .dark ? .dark : .light))
|
||||
mapView.logoViewPosition = .topLeft
|
||||
mapView.attributionButtonPosition = .topLeft
|
||||
mapView.attributionButtonMargins = .init(x: mapView.logoView.frame.maxX + 8, y: mapView.logoView.center.y / 2)
|
||||
mapView.tintColor = .compound.iconAccentPrimary
|
||||
mapView.allowsRotating = false
|
||||
mapView.allowsTilting = false
|
||||
return mapView
|
||||
}
|
||||
|
||||
private func showUserLocation(in mapView: MLNMapView) {
|
||||
switch (showsUserLocationMode, options.annotations) {
|
||||
case (.showAndFollow, _):
|
||||
mapView.userTrackingMode = .follow
|
||||
case (.show, let annotations) where !annotations.isEmpty:
|
||||
// In the show mode, if there are annotations, we check the authorizationStatus,
|
||||
// if it's not determined, we wont prompt the user with a request for permissions,
|
||||
// because they should be able to see the annotations without sharing their location information.
|
||||
guard mapView.locationManager.authorizationStatus != .notDetermined else { return }
|
||||
fallthrough
|
||||
case (.show, _):
|
||||
mapView.showsUserLocation = true
|
||||
mapView.setUserTrackingMode(.none, animated: false, completionHandler: nil)
|
||||
case (.hide, _), (.hideAndFollowMarker, _):
|
||||
// In hideAndFollowMarker mode the camera following is handled by the coordinator.
|
||||
mapView.showsUserLocation = false
|
||||
mapView.setUserTrackingMode(.none, animated: false, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
extension MapLibreMapView {
|
||||
class Coordinator: NSObject, MLNMapViewDelegate {
|
||||
// MARK: - Properties
|
||||
|
||||
var mapLibreView: MapLibreMapView
|
||||
|
||||
private var previousUserLocation: MLNUserLocation?
|
||||
/// Tracks the last center coordinate reported by the map (or set programmatically),
|
||||
/// so that `updateUIView` can tell apart external binding changes from internal ones.
|
||||
var lastReportedCenter: CLLocationCoordinate2D?
|
||||
|
||||
/// The annotation the camera is currently locked on while in the `hideAndFollowMarker` mode.
|
||||
private weak var followedAnnotation: LocationAnnotation?
|
||||
/// Observes the followed annotation's coordinate, updated every frame by the `CoordinateAnimator`.
|
||||
private var followedAnnotationObservation: NSKeyValueObservation?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(_ mapLibreView: MapLibreMapView) {
|
||||
self.mapLibreView = mapLibreView
|
||||
}
|
||||
|
||||
// MARK: - Marker following
|
||||
|
||||
/// Keeps the camera locked on the marker with the given id, or stops following when nil.
|
||||
func updateMarkerFollowing(in mapView: MLNMapView, markerID: String?) {
|
||||
guard let markerID else {
|
||||
stopFollowingMarker()
|
||||
return
|
||||
}
|
||||
|
||||
guard let annotation = (mapView.annotations ?? [])
|
||||
.compactMap({ $0 as? LocationAnnotation })
|
||||
.first(where: { $0.id == markerID }) else {
|
||||
// The marker isn't on the map (yet), resolve it again on the next update.
|
||||
stopFollowingMarker()
|
||||
return
|
||||
}
|
||||
|
||||
guard annotation !== followedAnnotation else { return }
|
||||
startFollowingMarker(annotation, in: mapView)
|
||||
}
|
||||
|
||||
private func startFollowingMarker(_ annotation: LocationAnnotation, in mapView: MLNMapView) {
|
||||
followedAnnotation = annotation
|
||||
followedAnnotationObservation = nil
|
||||
|
||||
// Animate to the marker first, attaching the per-frame tracking only on completion
|
||||
// so that it doesn't cancel the transition.
|
||||
mapView.setCenter(annotation.coordinate,
|
||||
zoomLevel: mapView.zoomLevel,
|
||||
direction: -1, // negative value keeps the current direction
|
||||
animated: true) { [weak self, weak annotation, weak mapView] in
|
||||
guard let self, let annotation, let mapView, annotation === followedAnnotation else { return }
|
||||
|
||||
// Mirror every animated coordinate update to keep the camera as smooth as the marker.
|
||||
followedAnnotationObservation = annotation.observe(\.coordinate) { [weak mapView] annotation, _ in
|
||||
// The coordinate is only ever updated by SwiftUI so KVO fires on the main actor.
|
||||
MainActor.assumeIsolated {
|
||||
mapView?.setCenter(annotation.coordinate, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopFollowingMarker() {
|
||||
followedAnnotation = nil
|
||||
followedAnnotationObservation = nil
|
||||
}
|
||||
|
||||
// MARK: - MLNMapViewDelegate
|
||||
|
||||
func mapView(_ mapView: MLNMapView, viewFor annotation: MLNAnnotation) -> MLNAnnotationView? {
|
||||
guard let annotation = annotation as? LocationAnnotation else {
|
||||
return nil
|
||||
}
|
||||
return LocationAnnotationView(annotation: annotation, mediaProvider: mapLibreView.mediaProvider)
|
||||
}
|
||||
|
||||
func mapViewDidFailLoadingMap(_ mapView: MLNMapView, withError error: Error) {
|
||||
if mapLibreView.error != .failedLoadingMap {
|
||||
mapLibreView.error = .failedLoadingMap
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, didUpdate userLocation: MLNUserLocation?) {
|
||||
guard let userLocation else { return }
|
||||
mapLibreView.hasLoadedUserLocation = true
|
||||
|
||||
if previousUserLocation == nil, mapLibreView.options.annotations.isEmpty {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
mapView.setCenter(userLocation.coordinate, zoomLevel: self.mapLibreView.options.zoomLevel, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
previousUserLocation = userLocation
|
||||
updateGeolocationUncertainty(location: userLocation)
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, didChangeLocationManagerAuthorization manager: MLNLocationManager) {
|
||||
switch manager.authorizationStatus {
|
||||
case .denied, .restricted:
|
||||
mapLibreView.isLocationAuthorized = false
|
||||
case .authorizedAlways, .authorizedWhenInUse:
|
||||
mapLibreView.isLocationAuthorized = true
|
||||
case .notDetermined:
|
||||
mapLibreView.isLocationAuthorized = nil
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) {
|
||||
// While following a marker, don't flood the center binding with per-frame camera updates.
|
||||
// Leaving `lastReportedCenter` untouched avoids an external re-centering when following stops.
|
||||
guard mapLibreView.showsUserLocationMode.followedMarkerID == nil else { return }
|
||||
|
||||
// Avoid `Publishing changes from within view update` warnings
|
||||
DispatchQueue.main.async { [mapLibreView, weak self] in
|
||||
let center = mapView.centerCoordinate
|
||||
self?.lastReportedCenter = center
|
||||
mapLibreView.mapCenterCoordinate = center
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MLNMapView, shouldChangeFrom oldCamera: MLNMapCamera, to newCamera: MLNMapCamera, reason: MLNCameraChangeReason) -> Bool {
|
||||
// Send userDidPan only for gestures that actually change the map center. The reason
|
||||
// is an option set and gestures can come combined with other reasons, so check for
|
||||
// containment instead of an exact match.
|
||||
let centerChangingGestures: MLNCameraChangeReason = [.gesturePan, .gesturePinch, .gestureRotate]
|
||||
if !reason.isDisjoint(with: centerChangingGestures) {
|
||||
// Stop following immediately, the camera would fight the gesture otherwise.
|
||||
stopFollowingMarker()
|
||||
mapLibreView.userDidPan?()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: Callout
|
||||
|
||||
func mapView(_ mapView: MLNMapView, annotationCanShowCallout annotation: MLNAnnotation) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func updateGeolocationUncertainty(location: MLNUserLocation) {
|
||||
guard let clLocation = location.location, clLocation.horizontalAccuracy >= 0 else {
|
||||
mapLibreView.geolocationUncertainty = nil
|
||||
return
|
||||
}
|
||||
|
||||
mapLibreView.geolocationUncertainty = clLocation.horizontalAccuracy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MLNMapView convenient methods
|
||||
|
||||
private extension MapTilerStyle {
|
||||
init(_ colorScheme: ColorScheme) {
|
||||
switch colorScheme {
|
||||
case .light:
|
||||
self = .light
|
||||
case .dark:
|
||||
self = .dark
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
return .init(zoomLevel: options.zoomLevel,
|
||||
initialZoomLevel: options.initialZoomLevel,
|
||||
mapCenter: options.mapCenter,
|
||||
annotations: annotations.map { .init(id: $0.id, coordinate: $0.coordinate) },
|
||||
styleURL: { mapURLBuilder.interactiveMapURL(for: $0 ? .dark : .light) },
|
||||
tintColor: .compound.iconAccentPrimary,
|
||||
markerView: { annotationID in
|
||||
guard let kind = annotations.first(where: { $0.id == annotationID })?.kind else { return nil }
|
||||
return AnyView(LocationMarkerView(kind: kind, mediaProvider: mediaProvider))
|
||||
},
|
||||
showsUserLocationMode: $showsUserLocationMode,
|
||||
error: $error,
|
||||
mapCenterCoordinate: $mapCenterCoordinate,
|
||||
hasLoadedUserLocation: $hasLoadedUserLocation,
|
||||
isLocationAuthorized: $isLocationAuthorized,
|
||||
geolocationUncertainty: $geolocationUncertainty,
|
||||
userDidPan: userDidPan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,8 @@ targets:
|
||||
- package: Mantis
|
||||
- target: MapLibreInterface
|
||||
embed: true
|
||||
- target: MapLibreShim
|
||||
embed: true
|
||||
- package: MapLibre
|
||||
- package: PostHog
|
||||
- package: SwiftState
|
||||
|
||||
@@ -60,6 +60,7 @@ settings:
|
||||
include:
|
||||
- path: app.yml
|
||||
- path: ElementX/MapLibre/MapLibreInterface/SupportingFiles/target.yml
|
||||
- path: ElementX/MapLibre/MapLibreShim/SupportingFiles/target.yml
|
||||
- path: ElementX/SupportingFiles/target.yml
|
||||
- path: NSE/SupportingFiles/target.yml
|
||||
- path: ShareExtension/SupportingFiles/target.yml
|
||||
|
||||
Reference in New Issue
Block a user