diff --git a/Examples.xcodeproj/project.pbxproj b/Examples.xcodeproj/project.pbxproj index 9b200993b1a7..c580f73d8aa4 100644 --- a/Examples.xcodeproj/project.pbxproj +++ b/Examples.xcodeproj/project.pbxproj @@ -131,6 +131,7 @@ D9297596469F9B31C2350B43 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A615EFC3D6CF2A25C9864086 /* UIViewController+Extensions.swift */; platformFilters = (ios, ); }; D94672F30272E31087AB5DDD /* NavigationSimulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC5980DD30479F30127BA71 /* NavigationSimulator.swift */; platformFilters = (ios, ); }; D98624793DA36578289F02FF /* MapScrollExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65535FB9F190778001AB847A /* MapScrollExample.swift */; }; + DA109856E64BBD8071DF0619 /* ColorThemeExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DD4C2F0049E575A6B5BF66 /* ColorThemeExample.swift */; }; DA69CB0BD9F0DDA0FD1387B0 /* DataJoinExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D0CD9C2D04EA5B12E7F84C /* DataJoinExample.swift */; platformFilters = (ios, ); }; DCA54F7383085A8FD822F0BF /* GeofencingPlayground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7613C4E19DCD679A2620223C /* GeofencingPlayground.swift */; }; DFC64A62538E787D57B6514D /* DynamicViewAnnotationExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333EF3E0F1C789809F385AF /* DynamicViewAnnotationExample.swift */; platformFilters = (ios, ); }; @@ -198,6 +199,7 @@ 274D496EC7E47F63FD0D1337 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 289434058C4AB25A17655FEF /* PointClusteringExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointClusteringExample.swift; sourceTree = ""; }; 28CE7DA39D29A8311E4A58A4 /* 34M_17.dae */ = {isa = PBXFileReference; lastKnownFileType = text.xml.dae; path = 34M_17.dae; sourceTree = ""; }; + 29DD4C2F0049E575A6B5BF66 /* ColorThemeExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorThemeExample.swift; sourceTree = ""; }; 2C957F9CA07061B793C2DD4A /* Custom3DPuckExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Custom3DPuckExample.swift; sourceTree = ""; }; 2D91A8B64951711546335530 /* VoiceOverAccessibilityExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceOverAccessibilityExample.swift; sourceTree = ""; }; 2DD8B1D25297B7433F4AAF35 /* GradientLine.geojson */ = {isa = PBXFileReference; path = GradientLine.geojson; sourceTree = ""; }; @@ -402,6 +404,7 @@ F890746B56E20150A053B41B /* AnnotationsExample.swift */, 63A3027A7DA59E090DAD25F1 /* ClipLayerExample.swift */, 46CE3D9C2873C0767DD76D85 /* ClusteringExample.swift */, + 29DD4C2F0049E575A6B5BF66 /* ColorThemeExample.swift */, C61CC711054A032EE0446036 /* DynamicStylingExample.swift */, A6B06A1D70F479D8DC5C375A /* FeaturesQueryExample.swift */, 7613C4E19DCD679A2620223C /* GeofencingPlayground.swift */, @@ -730,7 +733,6 @@ mainGroup = AFDB1EA82615CFDF02CE1D4D; packageReferences = ( B50D5CC28BF0DFBA55456D89 /* XCRemoteSwiftPackageReference "Fingertips" */, - 4F0A03F138FCA51E80A1893D /* XCLocalSwiftPackageReference "." */, ); projectDirPath = ""; projectRoot = ""; @@ -856,6 +858,7 @@ 3B4862E6832F23CB115D444A /* ClipLayerExample.swift in Sources */, 1DAE02D73D16E543777C2025 /* ClusteringExample.swift in Sources */, 5A28C124249725578389175A /* ColorExpressionExample.swift in Sources */, + DA109856E64BBD8071DF0619 /* ColorThemeExample.swift in Sources */, C664365A373267B564EC84EE /* CombineExample.swift in Sources */, 215230836B6AD1040D3DA547 /* CombineLocationExample.swift in Sources */, 3E515D1DD1D9CA02F3E95AA2 /* Constants.swift in Sources */, @@ -1353,13 +1356,6 @@ }; /* End XCRemoteSwiftPackageReference section */ -/* Begin XCLocalSwiftPackageReference section */ - 4F0A03F138FCA51E80A1893D /* XCLocalSwiftPackageReference "." */ = { - isa = XCLocalSwiftPackageReference; - relativePath = .; - }; -/* End XCLocalSwiftPackageReference section */ - /* Begin XCSwiftPackageProductDependency section */ 0AF5F744C6369BF1FB233FB6 /* MapboxMaps */ = { isa = XCSwiftPackageProductDependency; diff --git a/Sources/Examples/Assets.xcassets/monochrome_lut.imageset/Contents.json b/Sources/Examples/Assets.xcassets/monochrome_lut.imageset/Contents.json new file mode 100644 index 000000000000..2bfefeaf6193 --- /dev/null +++ b/Sources/Examples/Assets.xcassets/monochrome_lut.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "monochrome_lut.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Examples/Assets.xcassets/monochrome_lut.imageset/monochrome_lut.png b/Sources/Examples/Assets.xcassets/monochrome_lut.imageset/monochrome_lut.png new file mode 100644 index 000000000000..c62191b2dfd8 Binary files /dev/null and b/Sources/Examples/Assets.xcassets/monochrome_lut.imageset/monochrome_lut.png differ diff --git a/Sources/Examples/SwiftUI Examples/ColorThemeExample.swift b/Sources/Examples/SwiftUI Examples/ColorThemeExample.swift new file mode 100644 index 000000000000..8be5bf3b5fbe --- /dev/null +++ b/Sources/Examples/SwiftUI Examples/ColorThemeExample.swift @@ -0,0 +1,116 @@ +import SwiftUI +@_spi(Experimental) import MapboxMaps + +struct ColorThemeExample: View { + enum Theme: String { + case `default` + case red + case monochrome + } + + @State private var theme: Theme = .red + @State private var panelHeight: CGFloat = 0 + + var body: some View { + Map(initialViewport: .camera(center: .init(latitude: 40.72, longitude: -73.99), zoom: 11, pitch: 45)) { + switch theme { + case .default: + EmptyMapContent() + case .red: + ColorTheme(base64: redTheme) + case .monochrome: + ColorTheme(uiimage: monochromeTheme) + } + + /// Defines a custom layer and source to draw the border line. + NYNJBorder() + } + .mapStyle(.streets) /// In standard style it's possible to provide custom theme using `.standard(themeData: "base64String")` + .additionalSafeAreaInsets(.bottom, panelHeight) + .ignoresSafeArea() + .overlay(alignment: .bottom) { + VStack(alignment: .center) { + Group { + HStack { + ColorButton(color: .white, isOn: Binding(get: { theme == .default }, set: { _, _ in theme = .default })) + ColorButton(color: .red, isOn: Binding(get: { theme == .red }, set: { _, _ in theme = .red })) + ColorButton(color: .secondaryLabel, isOn: Binding(get: { theme == .monochrome }, set: { _, _ in theme = .monochrome })) + } + } + .floating() + } + .padding(.bottom, 30) + } + } +} + +private struct ColorButton: View { + let color1: UIColor + let color2: UIColor + let isOn: Binding + + init(color: UIColor, isOn: Binding) { + self.color1 = color + self.color2 = color + self.isOn = isOn + } + + init(color1: UIColor, color2: UIColor, isOn: Binding) { + self.color1 = color1 + self.color2 = color2 + self.isOn = isOn + } + + var body: some View { + Button { + isOn.wrappedValue.toggle() + } label: { + ZStack { + Circle() + .fill( + LinearGradient( + gradient: Gradient(colors: [Color(color1), Color(color2)]), + startPoint: .leading, + endPoint: .trailing + ) + ) + Circle().strokeBorder(Color(color1.darker), lineWidth: 2) + } + } + .opacity(isOn.wrappedValue ? 1.0 : 0.2) + .frame(width: 50, height: 50) + } +} + +private struct NYNJBorder: MapContent { + var body: some MapContent { + GeoJSONSource(id: "border") + .data(.geometry(.lineString(LineString([ + CLLocationCoordinate2D(latitude: 40.913503418907936, longitude: -73.91912400100642), + CLLocationCoordinate2D(latitude: 40.82943110786286, longitude: -73.9615887363045), + CLLocationCoordinate2D(latitude: 40.75461056309348, longitude: -74.01409059085539), + CLLocationCoordinate2D(latitude: 40.69522028220487, longitude: -74.02798814058939), + CLLocationCoordinate2D(latitude: 40.65188756398558, longitude: -74.05655532615407), + CLLocationCoordinate2D(latitude: 40.64339339389301, longitude: -74.13916853846217), + ])))) + + LineLayer(id: "border", source: "border") + .lineColor(.orange) + .lineWidth(8) + .slot(.bottom) + } +} + +private let styleURL = Bundle.main.url(forResource: "fragment-realestate-NY", withExtension: "json")! +private let monochromeTheme = UIImage(named: "monochrome_lut")! +private let redTheme = "iVBORw0KGgoAAAANSUhEUgAABAAAAAAgCAYAAACM/gqmAAAAAXNSR0IArs4c6QAABSFJREFUeF7t3cFO40AQAFHnBv//wSAEEgmJPeUDsid5h9VqtcMiZsfdPdXVzmVZlo+3ZVm+fr3//L7257Lm778x+prL1ff0/b//H+z/4/M4OkuP/n70Nc7f+nnb+yzb//sY6vxt5xXPn+dP/aH+GsXJekb25izxR/ypZ6ucUefv9g4z2jPP3/HPHwAAgABAABgACIACkAAsAL1SD4yKWQAUAHUBdAG8buKNYoYL8PEX4FcHQAAAAAAAAAAAAAAAAAAAAAAA8LAeGF1mABAABAABQACQbZP7+hk5AwACAAAAAAAAAAAAAAAAAAAAAAAA4EE9AICMx4QBAAAAAAAANgvJsxGQV1dA/PxmMEtxU9YoABQACoC5CgDxX/wvsb2sEf/Ff/Ff/N96l5n73+/5YAB4CeBqx2VvMqXgUfD2npkzBCAXEBeQcrkoa5x/FxAXEBcQF5A2Wy3/t32qNYr8I//Mln+MABgBMAJgBMAIgBEAIwBGAIwAGAEwAmAE4K4eAGCNQIw+qQ0AmQ+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/6gEABAB5RgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN/UAAPKcAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEFNODICRtDkDO/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOhvlPUWem+h9xKQ+V4CUt9wO6KZnn/Pv+ff8z/bW5DFP59CUnJbWSP+iX/iX78znqED/urxnwHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADoNMcHUAdQAQcAUfAe8xEwH0O86t3IPz8OvClu17WqD/UH+oP9cf1Gdia01d/LQsDgAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABkCnSQwABgACj8Aj8D1mItAMAB1wHfDS3S5r5F/5V/6Vf3XAW12h/mIArHY89iZTAAQA2XtmBKAWqOslyf4rgBXACmAFcIur8k/bJ/mnQTr5V/6Vf+fKv0YAjAAYATACYATACIARACMARgCMABgBMAJgBMAIgBEAIwCdZuiA64AjwAgwAtxjpg6cDlztLlLA7/Pr1gueyr56/jx/5ZzUNeof9Y/6R/0zk4HGAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHQaQ4DgAGAgCPgCHiPmTqQOpC1u8gAYACMjAf5V/6Vf+XfmTrQ8l97v8Z/5X8GAAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADIBO0xgADAAdCB0IHYgeMxkADAAdkGM7IPbf/pfuWlmj/lH/qH/UPzMZGAwABgADgAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAJ3mMAAYAAg4Ao6A95jJAGAA6EDrQJfuclkj/8q/8q/8O1MHWv47Nv8xABgADAAGAAOAAcAAYAAwABgADAAGAAOAAcAAYAB0msYAYADoQOhA6ED0mMkAYADogBzbAbH/9r/YFWWN+kf9o/5R/8xkYDAAGAAMAAYAA4ABwABgADAAGAAMAAYAA4ABwABgAHSawwBgACDgCDgC3mMmA4ABoAOtA126y2WN/Cv/yr/y70wdaPnv2PzHAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHQaRoDgAGgA6EDoQPRYyYDgAGgA3JsB8T+2/9iV5Q16h/1j/pH/TOTgcEAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAPgyQ2AT4NBIB3ew5dkAAAAAElFTkSuQmCC" + +private extension StandardTheme { + static let red = StandardTheme(rawValue: "red") +} + +struct ColorThemeExample_Previews: PreviewProvider { + static var previews: some View { + StandardStyleImportExample() + } +} diff --git a/Sources/Examples/SwiftUI Examples/SwiftUIRoot.swift b/Sources/Examples/SwiftUI Examples/SwiftUIRoot.swift index dda69219360c..f7c1bf1825c3 100644 --- a/Sources/Examples/SwiftUI Examples/SwiftUIRoot.swift +++ b/Sources/Examples/SwiftUI Examples/SwiftUIRoot.swift @@ -31,11 +31,9 @@ struct SwiftUIRoot: View { ExampleLink("Query Rendered Features on tap", note: "Use MapReader and MapboxMap to query rendered features.", destination: FeaturesQueryExample()) #endif ExampleLink("Clustering data", note: "Display GeoJSON data with clustering using custom layers and handle interactions with them.", destination: ClusteringExample()) - } header: { Text("Use cases") } - - Section { - ExampleLink("GeofencingUserLocation", note: "Set geofence on user initial location.", destination: GeofencingUserLocation()) - ExampleLink("GeofencingPlayground", note: "Showcase isochrone API together with geofences.", destination: GeofencingPlayground()) + ExampleLink("Geofencing User Location", note: "Set geofence on user initial location.", destination: GeofencingUserLocation()) + ExampleLink("Geofencing Playground", note: "Showcase isochrone API together with geofences.", destination: GeofencingPlayground()) + ExampleLink("Color Themes", note: "Showcase the Color Theme API", destination: ColorThemeExample()) } header: { Text("Use cases") } Section { diff --git a/Sources/MapboxMaps/ContentBuilders/MapContent/MapContentUniqueProperties.swift b/Sources/MapboxMaps/ContentBuilders/MapContent/MapContentUniqueProperties.swift index b2ee493c6816..6a3c70a9ece3 100644 --- a/Sources/MapboxMaps/ContentBuilders/MapContent/MapContentUniqueProperties.swift +++ b/Sources/MapboxMaps/ContentBuilders/MapContent/MapContentUniqueProperties.swift @@ -15,8 +15,10 @@ struct MapContentUniqueProperties: Decodable { var projection: StyleProjection? var snow: Snow? var rain: Rain? + var colorTheme: ColorTheme? var transition: TransitionOptions? var location: LocationOptions? + var lights = Lights() private func update(_ label: String, old: T?, new: T?, initial: T?, setter: (Any) -> Expected) { @@ -42,8 +44,8 @@ struct MapContentUniqueProperties: Decodable { update("terrain", old: old.terrain, new: terrain, initial: initial?.terrain, setter: style.setStyleTerrainForProperties(_:)) update("snow", old: old.snow, new: snow, initial: initial?.snow, setter: style.setStyleSnowForProperties(_:)) update("rain", old: old.rain, new: rain, initial: initial?.rain, setter: style.setStyleRainForProperties(_:)) - lights.update(from: old.lights, style: style, initialLights: initial?.lights) + update(from: old.colorTheme, to: colorTheme, style: style) if old.location != location { locationManager?.options = location ?? LocationOptions() @@ -96,6 +98,20 @@ extension MapContentUniqueProperties { } } +private extension MapContentUniqueProperties { + func update(from oldColorTheme: ColorTheme?, to newColorTheme: ColorTheme?, style: StyleManagerProtocol) { + wrapStyleDSLError { + if newColorTheme != oldColorTheme { + if let newColorTheme { + try handleExpected { style.setStyleColorThemeFor(newColorTheme.core) } + } else { + style.setInitialStyleColorTheme() + } + } + } + } +} + private extension MapContentUniqueProperties.Lights { func update(from old: Self, style: StyleManagerProtocol, initialLights: Self?) { if self != old { diff --git a/Sources/MapboxMaps/Documentation.docc/API Catalogs/Style.md b/Sources/MapboxMaps/Documentation.docc/API Catalogs/Style.md index 074b55f9d6b7..ba36f300946d 100644 --- a/Sources/MapboxMaps/Documentation.docc/API Catalogs/Style.md +++ b/Sources/MapboxMaps/Documentation.docc/API Catalogs/Style.md @@ -28,6 +28,7 @@ - ``TransitionOptions-struct`` - ``Rain`` - ``Snow`` +- ``ColorTheme`` ### Declarative Map Styling diff --git a/Sources/MapboxMaps/Foundation/CoreAliases.swift b/Sources/MapboxMaps/Foundation/CoreAliases.swift index ac90f87e6143..f609721066f1 100644 --- a/Sources/MapboxMaps/Foundation/CoreAliases.swift +++ b/Sources/MapboxMaps/Foundation/CoreAliases.swift @@ -45,3 +45,4 @@ typealias CoreRenderedQueryGeometry = MapboxCoreMaps_Private.RenderedQueryGeomet typealias CoreFeaturesetFeatureId = MapboxCoreMaps_Private.FeaturesetFeatureId typealias CoreFeaturesetQueryTarget = MapboxCoreMaps_Private.FeaturesetQueryTarget typealias CoreFeaturesetDescriptor = MapboxCoreMaps_Private.FeaturesetDescriptor +typealias CoreColorTheme = MapboxCoreMaps_Private.ColorTheme diff --git a/Sources/MapboxMaps/Style/ColorTheme.swift b/Sources/MapboxMaps/Style/ColorTheme.swift new file mode 100644 index 000000000000..000014a239eb --- /dev/null +++ b/Sources/MapboxMaps/Style/ColorTheme.swift @@ -0,0 +1,81 @@ +import UIKit + +/// Map color theme. +/// +/// A color theme modifies the global colors of a style using a LUT (lookup table) for color grading. +/// To use a custom color theme, provide a LUT image. The image must be ≤32 pixels in height and have a width equal to the square of its height. +/// +/// Pass the image either as a base64-encoded string: +/// ```swift +/// let mapView = MapView() +/// mapView.mapboxMap.setMapStyleContent { +/// ColorTheme(base64: "base64EncodedImage") +/// } +/// ``` +/// +/// Or as a `UIImage` for easier asset integration: +/// ```swift +/// let mapView = MapView() +/// let lutImage = UIImage(named: "monochrome_lut")! +/// mapView.mapboxMap.setMapStyleContent { +/// ColorTheme(uiimage: lutImage) +/// } +/// ``` +/// +/// Note: Each style can have only one `ColorTheme`. Setting a new theme overwrites the previous one. +/// Additional information [Mapbox Style Specification](https://docs.mapbox.com/style-spec/reference/root/#color-theme) +@_documentation(visibility: public) +@_spi(Experimental) +public struct ColorTheme: Equatable { + var base64: StylePropertyValue? + var uiimage: UIImage? + + /// Creates a ``ColorTheme`` using base64 encoded LUT image. + /// + /// - Important: Image height must be less or equal to 32 pixels and width of the image should be equal to the height squared. + /// - Parameters: + /// - base64: base64 encoded LUT image. + public init(base64: String) { + self.base64 = StylePropertyValue(value: base64, kind: .constant) + self.uiimage = nil + } + + /// Creates a ``ColorTheme`` using base64 encoded LUT image. + /// + /// - Important: Image height must be less or equal to 32 pixels and width of the image should be equal to the height squared. + /// - Parameters: + /// - base64: base64 encoded LUT image. + public init(base64: Exp) { + self.base64 = base64.asCore.flatMap { StylePropertyValue(value: $0, kind: .expression) } + self.uiimage = nil + } + + /// Creates a ``ColorTheme`` using base64 encoded LUT image. + /// + /// - Important: Image height must be less or equal to 32 pixels and width of the image should be equal to the height squared. + /// - Parameters: + /// - uiimage: UIImage instance which represents color grading LUT. + public init(uiimage: UIImage) { + self.uiimage = uiimage + self.base64 = nil + } +} + +@available(iOS 13.0, *) +extension ColorTheme: MapStyleContent, PrimitiveMapContent { + func visit(_ node: MapContentNode) { + node.mount(MountedUniqueProperty(keyPath: \.colorTheme, value: self)) + } +} + +extension ColorTheme { + var core: CoreColorTheme? { + if let base64 { + return .fromStylePropertyValue(base64) + } else if let uiimage, let coreImage = CoreMapsImage(uiImage: uiimage) { + return .fromImage(coreImage) + } else { + return nil + } + } +} diff --git a/Sources/MapboxMaps/Style/StyleManager.swift b/Sources/MapboxMaps/Style/StyleManager.swift index 76f17693240e..86456d9de183 100644 --- a/Sources/MapboxMaps/Style/StyleManager.swift +++ b/Sources/MapboxMaps/Style/StyleManager.swift @@ -1619,7 +1619,9 @@ extension StyleManager { extension StyleManager { /// Set the snow parameters to animate snowfall. /// ``Snow`` object can be used to set the snow parameters. - @_spi(Experimental) public func setSnow(_ snow: Snow) throws { + @_spi(Experimental) + @_documentation(visibility: public) + public func setSnow(_ snow: Snow) throws { let snowDictionary = try snow.allStyleProperties() let expected = styleManager.setStyleSnowForProperties(snowDictionary) @@ -1629,7 +1631,9 @@ extension StyleManager { } /// Remove snow effect from the style. - @_spi(Experimental) public func removeSnow() throws { + @_spi(Experimental) + @_documentation(visibility: public) + public func removeSnow() throws { let expected = styleManager.setStyleSnowForProperties(NSNull()) if expected.isError() { @@ -1639,7 +1643,9 @@ extension StyleManager { /// Set the rain parameters to animate rain drops. /// ``Rain`` object can be used to set the rain parameters. - @_spi(Experimental) public func setRain(_ rain: Rain) throws { + @_spi(Experimental) + @_documentation(visibility: public) + public func setRain(_ rain: Rain) throws { let rainDictionary = try rain.allStyleProperties() let expected = styleManager.setStyleRainForProperties(rainDictionary) @@ -1649,7 +1655,9 @@ extension StyleManager { } /// Remove rain effect from the style. - @_spi(Experimental) public func removeRain() throws { + @_spi(Experimental) + @_documentation(visibility: public) + public func removeRain() throws { let expected = styleManager.setStyleRainForProperties(NSNull()) if expected.isError() { @@ -1657,6 +1665,36 @@ extension StyleManager { } } + /// Set color theme for style. + /// ``ColorTheme`` is unique per style and setting a new one will effectively overwrite any previous theme. + /// - Parameters: + /// - colorTheme: Color theme to apply on the style. + /// - Throws: ``StyleError`` if the color theme could not be applied. + @_spi(Experimental) + @_documentation(visibility: public) + public func setColorTheme(_ colorTheme: ColorTheme) throws { + guard let coreTheme = colorTheme.core else { + throw StyleError(message: "Cannot construct UIImage object.") + } + + let expected = styleManager.setStyleColorThemeFor(coreTheme) + + if expected.isError() { + throw StyleError(message: expected.error as String) + } + } + + /// Remove color theme from the style. + /// - Throws: ``StyleError`` if the color theme could not be removed. + @_spi(Experimental) + @_documentation(visibility: public) + public func removeColorTheme() throws { + let expected = styleManager.setStyleColorThemeFor(nil) + + if expected.isError() { + throw StyleError(message: expected.error as String) + } + } } // MARK: - Featuresets diff --git a/Sources/MapboxMaps/Style/StyleManagerProtocol.swift b/Sources/MapboxMaps/Style/StyleManagerProtocol.swift index ae317dfc6ff1..240dc7cab53d 100644 --- a/Sources/MapboxMaps/Style/StyleManagerProtocol.swift +++ b/Sources/MapboxMaps/Style/StyleManagerProtocol.swift @@ -211,6 +211,9 @@ internal protocol StyleManagerProtocol { featureIds: [String]) -> Expected func getStyleFeaturesets() -> [CoreFeaturesetDescriptor] + + func setStyleColorThemeFor(_ colorTheme: CoreColorTheme?) -> Expected + func setInitialStyleColorTheme() } // MARK: Conformance diff --git a/Tests/MapboxMapsTests/Foundation/Mocks/MockStyleManager.swift b/Tests/MapboxMapsTests/Foundation/Mocks/MockStyleManager.swift index 629f9098c994..3a51317bf810 100644 --- a/Tests/MapboxMapsTests/Foundation/Mocks/MockStyleManager.swift +++ b/Tests/MapboxMapsTests/Foundation/Mocks/MockStyleManager.swift @@ -3,6 +3,18 @@ import Foundation @_implementationOnly import MapboxCommon_Private class MockStyleManager: StyleManagerProtocol { + let setStyleColorThemeForStub = Stub>( + defaultReturnValue: .init(value: NSNull()) + ) + func setStyleColorThemeFor(_ colorTheme: CoreColorTheme?) -> Expected { + setStyleColorThemeForStub(with: colorTheme) + } + + let setInitialStyleColorThemeStub = Stub() + func setInitialStyleColorTheme() { + setInitialStyleColorThemeStub() + } + let getFeaturesetsStub = Stub(defaultReturnValue: []) func getStyleFeaturesets() -> [CoreFeaturesetDescriptor] { getFeaturesetsStub.call() diff --git a/scripts/api-compatibility-check/breakage_allowlist.txt b/scripts/api-compatibility-check/breakage_allowlist.txt index 455a50b6fde1..f25273f8c284 100644 --- a/scripts/api-compatibility-check/breakage_allowlist.txt +++ b/scripts/api-compatibility-check/breakage_allowlist.txt @@ -2057,3 +2057,9 @@ Func MapStyle.standardSatellite(lightPreset:font:showPointOfInterestLabels:showT # Add ViewAnnotationOptions.min/maxZoom to the constructor Constructor ViewAnnotationOptions.init(annotatedFeature:width:height:allowOverlap:allowOverlapWithPuck:visible:selected:variableAnchors:ignoreCameraPadding:) has been removed + +# Add missed @_documentation annotations +Func StyleManager.removeRain() is now with @_documentation +Func StyleManager.removeSnow() is now with @_documentation +Func StyleManager.setRain(_:) is now with @_documentation +Func StyleManager.setSnow(_:) is now with @_documentation \ No newline at end of file