Skip to content

Commit

Permalink
feat(ios,v10): add new Image component, to generate mapbox images usi…
Browse files Browse the repository at this point in the history
…ng react-native
  • Loading branch information
mfazekas committed Mar 1, 2023
1 parent 13635e6 commit 8e7678a
Show file tree
Hide file tree
Showing 14 changed files with 361 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ const styles = StyleSheet.create({
- [Camera](docs/Camera.md)
- [UserLocation](docs/UserLocation.md)
- [Images](docs/Images.md)
- [Image](docs/Image.md)

### Sources

Expand Down
1 change: 1 addition & 0 deletions __tests__/interface.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('Public Interface', () => {
'ImageSource',
'RasterDemSource',
'Images',
'Image',

// constants
'UserTrackingModes',
Expand Down
14 changes: 14 additions & 0 deletions docs/Image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!-- This file was autogenerated from Image.tsx do not modify -->
# <MapboxGL.Image />


## props
| Prop | Type | Default | Required | Description |
| ---- | :-- | :----- | :------ | :---------- |
| name | `string` | `none` | `true` | Image name |
| sdf | `boolean` | `none` | `false` | Make image an sdf optional - see [SDF icons](https://docs.mapbox.com/help/troubleshooting/using-recolorable-images-in-mapbox-maps/) |
| stretchX | `Array` | `none` | `false` | stretch along x axis - optional |
| stretchY | `Array` | `none` | `false` | stretch along y axis - optional |
| children | `ReactElement` | `none` | `true` | Single react native view generating the image |


2 changes: 1 addition & 1 deletion docs/Images.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ Images defines the images used in Symbol etc. layers.
| nativeAssetImages | `Array` | `none` | `false` | If you have an asset under Image.xcassets on iOS and the drawables directory on android<br/>you can specify an array of string names with assets as the key `['pin']`.<br/>Additionally object with keys sdf, and strechX, strechY is supported for [SDF icons](https://docs.mapbox.com/help/troubleshooting/using-recolorable-images-in-mapbox-maps/) |
| onImageMissing | `func` | `none` | `false` | Gets called when a Layer is trying to render an image whose key is not present in<br/>any of the `Images` component of the Map.<br/>*signature:*`(imageKey:string) => void` |
| id | `string` | `none` | `false` | FIX ME NO DESCRIPTION |
| children | `ReactReactElement` | `none` | `false` | FIX ME NO DESCRIPTION |
| children | `ReactElement<T> \| Array<TypedReactNode<T>> \| never` | `none` | `false` | FIX ME NO DESCRIPTION |


46 changes: 45 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,50 @@
}
]
},
"Image": {
"description": "",
"displayName": "Image",
"methods": [],
"props": [
{
"name": "name",
"required": true,
"type": "string",
"default": "none",
"description": "Image name"
},
{
"name": "sdf",
"required": false,
"type": "boolean",
"default": "none",
"description": "Make image an sdf optional - see [SDF icons](https://docs.mapbox.com/help/troubleshooting/using-recolorable-images-in-mapbox-maps/)"
},
{
"name": "stretchX",
"required": false,
"type": "Array",
"default": "none",
"description": "stretch along x axis - optional"
},
{
"name": "stretchY",
"required": false,
"type": "Array",
"default": "none",
"description": "stretch along y axis - optional"
},
{
"name": "children",
"required": true,
"type": "ReactElement",
"default": "none",
"description": "Single react native view generating the image"
}
],
"fileNameWithExt": "Image.tsx",
"name": "Image"
},
"ImageSource": {
"description": "ImageSource is a content source that is used for a georeferenced raster image to be shown on the map.\nThe georeferenced image scales and rotates as the user zooms and rotates the map",
"displayName": "ImageSource",
Expand Down Expand Up @@ -2011,7 +2055,7 @@
{
"name": "children",
"required": false,
"type": "ReactReactElement",
"type": "ReactElement<T> \\| Array<TypedReactNode<T>> \\| never",
"default": "none",
"description": "FIX ME NO DESCRIPTION"
}
Expand Down
33 changes: 23 additions & 10 deletions example/src/examples/SymbolCircleLayer/SdfIcons.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from 'react';
import { View } from 'react-native';
import Mapbox from '@rnmapbox/maps';

import sheet from '../../styles/sheet';
import exampleIcon from '../../assets/example.png';
import pinIcon from '../../assets/pin.png';
import { BaseExampleProps } from '../common/BaseExamplePropTypes';
import Page from '../common/Page';

const styles = {
icon: {
iconImage: ['get', 'icon'],

iconColor: '#f0c',
iconColor: ['get', 'color'],

iconSize: [
'match',
Expand All @@ -32,7 +32,8 @@ const featureCollection: GeoJSON.FeatureCollection = {
type: 'Feature',
id: '9d10456e-bdda-4aa9-9269-04c1667d4552',
properties: {
icon: 'example',
icon: 'rn-image',
color: '#f0c',
},
geometry: {
type: 'Point',
Expand All @@ -43,7 +44,8 @@ const featureCollection: GeoJSON.FeatureCollection = {
type: 'Feature',
id: '9d10456e-bdda-4aa9-9269-04c1667d4552',
properties: {
icon: 'airport-15',
icon: 'rn-image',
color: '#0fc',
},
geometry: {
type: 'Point',
Expand All @@ -54,7 +56,8 @@ const featureCollection: GeoJSON.FeatureCollection = {
type: 'Feature',
id: '9d10456e-bdda-4aa9-9269-04c1667d4552',
properties: {
icon: 'pin',
icon: 'rn-image',
color: '#cf0',
},
geometry: {
type: 'Point',
Expand All @@ -65,7 +68,8 @@ const featureCollection: GeoJSON.FeatureCollection = {
type: 'Feature',
id: '9d10456e-bdda-4aa9-9269-04c1667d4553',
properties: {
icon: 'pin3',
icon: 'rn-image',
color: '#c00',
},
geometry: {
type: 'Point',
Expand Down Expand Up @@ -96,11 +100,20 @@ class SdfIcons extends React.PureComponent<BaseExampleProps> {
nativeAssetImages={[{ name: 'pin', sdf: true }]}
images={images}
onImageMissing={(imageKey: string) =>
this.setState({
images: { ...this.state.images, [imageKey]: pinIcon },
})
console.log('=> on image missing', imageKey)
}
/>
>
<Mapbox.Image name="rn-image" sdf>
<View
style={{
backgroundColor: 'red',
width: 40,
height: 40,
borderRadius: 10,
}}
/>
</Mapbox.Image>
</Mapbox.Images>
<Mapbox.ShapeSource id="exampleShapeSource" shape={featureCollection}>
<Mapbox.SymbolLayer id="exampleIconName" style={styles.icon} />
</Mapbox.ShapeSource>
Expand Down
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import {
type UserTrackingMode as _UserTrackingMode,
type UserTrackingModeChangeCallback as _UserTrackingModeChangeCallback,
} from './javascript/components/Camera';
import { Images as _Images } from './javascript/components/Images';
import _Images from './javascript/components/Images';
import _Image from './javascript/components/Image';
import { MarkerView as _MarkerView } from './javascript/components/MarkerView';
import { PointAnnotation as _PointAnnotation } from './javascript/components/PointAnnotation';
import { Atmosphere as _Atmosphere } from './javascript/components/Atmosphere';
Expand Down Expand Up @@ -358,6 +359,8 @@ declare namespace MapboxGL {
type Location = _Location;
type Images = _Images;
const Images = _Images;
type Image = _Image;
const Image = _Image;

/**
* Offline
Expand Down Expand Up @@ -780,6 +783,7 @@ export import AnimatedPoint = MapboxGL.AnimatedPoint;
export import AnimatedMapPoint = MapboxGL.AnimatedPoint;
export import AnimatedShape = MapboxGL.AnimatedShape;
export import Images = MapboxGL.Images;
export import Image = MapboxGL.Image;

export const { offlineManager } = MapboxGL;

Expand Down
93 changes: 93 additions & 0 deletions ios/RCTMGL-v10/RCMTGLImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import MapboxMaps

class RCTMGLImage : UIView {
@objc
var name: String = ""

var image: UIImage? = nil

var sdf: Bool? = nil
var stretchX: [[NSNumber]] = []
var stretchY: [[NSNumber]] = []

weak var images: RCTMGLImageSetter? = nil {
didSet {
DispatchQueue.main.async { self.setImage() }
}
}
weak var bridge : RCTBridge! = nil

var reactSubviews : [UIView] = []

// MARK: - subview management

@objc open override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
reactSubviews.insert(subview, at: atIndex)
if reactSubviews.count > 1 {
Logger.log(level: .error, message: "Image supports max 1 subview")
}
if image == nil {
DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(10)) {
self.setImage()
}
}
}

@objc
open override func removeReactSubview(_ subview: UIView!) {
reactSubviews.removeAll(where: { $0 == subview })
}

// MARK: - view shnapshot

func changeImage(_ image: UIImage, name: String) {
if let images = images {
let _ = images.addImage(name: name, image: image, sdf: sdf, stretchX:stretchX, stretchY:stretchY, log: "RCTMGLImage.addImage")
}
}

func setImage() {
if let image = _createViewSnapshot() {
changeImage(image, name: name)
}
}

func _createViewSnapshot() -> UIImage? {
let useDummyImage = false
if useDummyImage {
let size = CGSize(width: 32, height: 32)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
UIColor.darkGray.setStroke()
context.stroke(CGRect(x: 0, y:0, width: 32, height: 32))
UIColor(red: 158/255, green: 215/255, blue: 245/255, alpha: 1).setFill()
context.fill(CGRect(x: 2, y: 2, width: 30, height: 30))
}
return image
}
guard reactSubviews.count > 0 else {
return nil
}
return _createViewSnapshot(view: reactSubviews[0])
}

func _createViewSnapshot(view: UIView) -> UIImage? {
guard view.bounds.size.width > 0 && view.bounds.size.height > 0 else {
return nil
}

let roundUp = 4

let adjustedSize = CGSize(
width: ((Int(view.bounds.size.width)+roundUp-1)/roundUp)*roundUp,
height: ((Int(view.bounds.size.height)+roundUp-1)/roundUp)*roundUp
)

let renderer = UIGraphicsImageRenderer(size: adjustedSize)
let image = renderer.image { context in
view.layer.render(in: context.cgContext)
}
return image
}
}

9 changes: 9 additions & 0 deletions ios/RCTMGL-v10/RCMTGLImageManager.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>

@interface RCT_EXTERN_MODULE(RCTMGLImageManager, RCTViewManager)

RCT_EXPORT_VIEW_PROPERTY(name, NSString)

@end

14 changes: 14 additions & 0 deletions ios/RCTMGL-v10/RCMTGLImageManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

@objc(RCTMGLImageManager)
class RCTMGLImageManager : RCTViewManager {
@objc
override static func requiresMainQueueSetup() -> Bool {
return true
}

override func view() -> UIView! {
let layer = RCTMGLImage()
layer.bridge = self.bridge
return layer
}
}
Loading

0 comments on commit 8e7678a

Please sign in to comment.