├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── stale.yml
├── .gitignore
├── .npmignore
├── CONTRIBUTING.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── android
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── rumax
│ └── reactnative
│ └── saveview
│ ├── SaveView.java
│ ├── SaveViewModule.java
│ └── SaveViewPackage.java
├── ios
└── RNSaveView
│ ├── RNSaveView.xcodeproj
│ └── project.pbxproj
│ └── RNSaveView
│ ├── RNSaveViewConstants.h
│ ├── RNSaveViewConstants.m
│ ├── RNSaveViewManager.h
│ └── RNSaveViewManager.m
├── package.json
├── react-native-save-view.podspec
└── src
└── index.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | [**/{.js,package.json}]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.java]
16 | indent_style = space
17 | indent_size = 2
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 | **Describe the bug**
7 | A clear and concise description of what the bug is.
8 |
9 | **To Reproduce**
10 |
11 | - [ ] I checked the demo project and cannot reproduce the issue
12 | - [ ] I checked the demo project and the issue exists
13 |
14 | Steps to reproduce the behavior:
15 | 1. Render with '...' elements (Code example)
16 | 2. Other actions '...'
17 | 3. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Smartphone (please complete the following information):**
26 | - Device: [e.g. iPhone6]
27 | - OS: [e.g. iOS8.1]
28 | - Version [e.g. 22]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 15
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 5
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # OSX
3 | #
4 | .DS_Store
5 |
6 | # node.js
7 | #
8 | node_modules/
9 | npm-debug.log
10 | yarn-error.log
11 | react-native-save-view-*.tgz
12 | package/
13 |
14 | # Xcode
15 | #
16 | build/
17 | *.pbxuser
18 | !default.pbxuser
19 | *.mode1v3
20 | !default.mode1v3
21 | *.mode2v3
22 | !default.mode2v3
23 | *.perspectivev3
24 | !default.perspectivev3
25 | xcuserdata
26 | *.xccheckout
27 | *.moved-aside
28 | DerivedData
29 | *.hmap
30 | *.ipa
31 | *.xcuserstate
32 | project.xcworkspace
33 |
34 |
35 | # Android/IntelliJ
36 | #
37 | build/
38 | .idea
39 | .gradle
40 | local.properties
41 | *.iml
42 |
43 | # BUCK
44 | buck-out/
45 | \.buckd/
46 | *.keystore
47 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .circleci
2 | .DS_Store
3 | .editorconfig
4 | .github
5 | CONTRIBUTING.md
6 | node_modules
7 | package-lock.json
8 | PULL_REQUEST_TEMPLATE.md
9 | react-native-save-view-*.tgz
10 | android/*.iml
11 | android/build
12 | ios/RNSaveView/RNSaveView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
13 | ios/RNSaveView/RNSaveView.xcodeproj/project.xcworkspace/xcuserdata/*.xcuserdatad/UserInterfaceState.xcuserstate
14 | ios/RNSaveView/RNSaveView.xcodeproj/xcuserdata/*.xcuserdatad/xcschemes/xcschememanagement.plist
15 | ios/RNSaveView/RNSaveView.xcodeproj/project.xcworkspace/contents.xcworkspacedata
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to react-native-PDFView
2 |
3 | :+1::tada: Thanks for taking the time to contribute! :tada::+1:
4 |
5 | ## Styleguides
6 |
7 | Please, follow the current style of the code.
8 |
9 | ### Git Commit Messages
10 |
11 | * Use the present tense ("Add feature" not "Added feature")
12 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
13 | * Limit the first line to 72 characters or less
14 | * Reference issues and pull requests liberally after the first line
15 | * Consider starting the commit message with an applicable emoji:
16 | * :art: `:art:` when improving the format/structure of the code
17 | * :racehorse: `:racehorse:` when improving performance
18 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks
19 | * :memo: `:memo:` when writing docs
20 | * :lollipop: `:lollipop:` when fixing Android
21 | * :apple: `:apple:` when fixing iOS
22 | * :bug: `:bug:` when fixing a bug
23 | * :fire: `:fire:` when removing code or files
24 | * :green_heart: `:green_heart:` when fixing the CI build
25 | * :white_check_mark: `:white_check_mark:` when adding tests
26 | * :lock: `:lock:` when dealing with security
27 | * :arrow_up: `:arrow_up:` when upgrading dependencies
28 | * :arrow_down: `:arrow_down:` when downgrading dependencies
29 | * :shirt: `:shirt:` when removing linter warnings
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Maksym Rusynyk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description of changes
2 |
3 | ### I did Exploratory testing
4 | - [ ] android
5 | - [ ] ios
6 |
7 | ### Can it be published to NPM?
8 | - [ ] Breaking change
9 | - [ ] Documentation is updated
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # react-native-save-view
3 |
4 | [](https://www.codacy.com/app/rumax/react-native-SaveView?utm_source=github.com&utm_medium=referral&utm_content=rumax/react-native-SaveView&utm_campaign=Badge_Grade)
5 | [](https://github.com/rumax/react-native-SaveView)
6 | [](https://badge.fury.io/js/react-native-save-view)
7 | [](http://makeapullrequest.com)
8 |
9 | Library for saving React Native View/ScrollView elements
10 |
11 | ## Example
12 |
13 | ```js
14 | export default class App extends Component {
15 | componentDidMount() {
16 | this._saveView();
17 | }
18 |
19 | render() {
20 | return (
21 | this._viewRef = ref} collapsable={false}>
22 | Some content
23 |
24 | );
25 | }
26 |
27 | async _saveView() {
28 | await this._makeSnapshotPNG();
29 | await this._makeSnapshotPNGBase64();
30 | }
31 |
32 | async _makeSnapshotPNG() {
33 | await RNSaveView.saveToPNG(ref, '/sdcard/Download/view.png');
34 | // Check /sdcard/Download/view.png using Device File Explorer
35 | }
36 |
37 | async _makeSnapshotPNGBase64() {
38 | const base64 = await RNSaveView.saveToPNGBase64(ref);
39 | console.log('base64:', base64);
40 | }
41 | }
42 | ```
43 |
44 | ## Methods
45 | Name|Android|iOS|Description|
46 | ----|-------|---|-----------|
47 | saveToPNG|✓|✓|Save View to PNG file. Before the function is called, check that android has [write to file permissions](https://developer.android.com/training/data-storage/files)|
48 | saveToPNGBase64|✓|✓|Save View to PNG base64|
49 |
50 | ## License
51 |
52 | [MIT](https://opensource.org/licenses/MIT)
53 |
54 | ## Author
55 |
56 | - [rumax](https://github.com/rumax)
57 |
58 | ### Other information
59 |
60 | - Generated with [react-native-create-library](https://github.com/frostney/react-native-create-library)
61 | - Zero JavaScript dependency. Which means that you do not bring other dependencies to your project
62 | - If you think that something is missing or would like to propose new feature, please, discuss it with author
63 | - Please, feel free to ⭐️ the project. This gives the confidence that you like it and a great job was done by publishing and supporting it 🤩
64 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | def safeExtGet(prop, fallback) {
2 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
3 | }
4 |
5 | apply plugin: 'com.android.library'
6 |
7 | android {
8 | compileSdkVersion safeExtGet('compileSdkVersion', 26)
9 | buildToolsVersion safeExtGet('buildToolsVersion', '25.0.3')
10 |
11 | defaultConfig {
12 | minSdkVersion safeExtGet('minSdkVersion', 19)
13 | targetSdkVersion safeExtGet('targetSdkVersion', 25)
14 | }
15 | lintOptions {
16 | abortOnError false
17 | }
18 | }
19 |
20 | repositories {
21 | mavenCentral()
22 | }
23 |
24 | dependencies {
25 | //noinspection GradleDynamicVersion
26 | implementation 'com.facebook.react:react-native:+'
27 | }
28 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/java/com/rumax/reactnative/saveview/SaveView.java:
--------------------------------------------------------------------------------
1 | package com.rumax.reactnative.saveview;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Canvas;
5 | import android.graphics.drawable.Drawable;
6 | import android.util.Base64;
7 | import android.view.View;
8 | import android.widget.ScrollView;
9 |
10 | import androidx.annotation.NonNull;
11 |
12 | import com.facebook.react.bridge.ReactApplicationContext;
13 | import com.facebook.react.uimanager.NativeViewHierarchyManager;
14 | import com.facebook.react.uimanager.UIBlock;
15 | import com.facebook.react.uimanager.UIManagerModule;
16 |
17 | import java.io.ByteArrayOutputStream;
18 | import java.io.File;
19 | import java.io.FileOutputStream;
20 | import java.io.IOException;
21 |
22 | public class SaveView {
23 | private ReactApplicationContext context;
24 |
25 | public SaveView(@NonNull final ReactApplicationContext context) {
26 | this.context = context;
27 | }
28 |
29 | public void saveToPNG(final int reactTag, final File file, @NonNull final OnComplete callback) {
30 | context.getNativeModule(UIManagerModule.class).addUIBlock(new UIBlock() {
31 | @Override
32 | public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
33 | try {
34 | View view = nativeViewHierarchyManager.resolveView(reactTag);
35 | Bitmap bitmap = getBitmap(view);
36 | saveBitmap(bitmap, file);
37 | callback.onSuccess();
38 | } catch (Exception e) {
39 | callback.onFail(e);
40 | }
41 | }
42 | });
43 | }
44 |
45 | public void saveToPNGBase64(final int reactTag, @NonNull final OnCompleteBase64 callback) {
46 | context.getNativeModule(UIManagerModule.class).addUIBlock(new UIBlock() {
47 | @Override
48 | public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
49 | try {
50 | View view = nativeViewHierarchyManager.resolveView(reactTag);
51 | Bitmap bitmap = getBitmap(view);
52 | String base64 = bitmapToBase64(bitmap);
53 | callback.onSuccess(base64);
54 | } catch (Exception e) {
55 | callback.onFail(e);
56 | }
57 | }
58 | });
59 | }
60 |
61 | private Bitmap getBitmap(View view) {
62 | View viewToSave = view;
63 |
64 | if (view instanceof ScrollView) {
65 | ScrollView scrollView = (ScrollView) view;
66 | viewToSave = scrollView.getChildAt(0);
67 | }
68 |
69 | return getBitmapFromView(viewToSave, viewToSave.getHeight(), viewToSave.getWidth());
70 | }
71 |
72 | private Bitmap getBitmapFromView(View view, int height, int width) {
73 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
74 | Canvas canvas = new Canvas(bitmap);
75 | Drawable bgDrawable = view.getBackground();
76 |
77 | if (bgDrawable != null) {
78 | bgDrawable.draw(canvas);
79 | }
80 |
81 | view.draw(canvas);
82 |
83 | return bitmap;
84 | }
85 |
86 | private void saveBitmap(Bitmap bitmap, File file) throws IOException {
87 | if (file.exists()) {
88 | throw new IOException("File [" + file.getAbsolutePath() + "] already exits");
89 | }
90 |
91 | try (FileOutputStream output = new FileOutputStream(file)) {
92 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, output);
93 | }
94 | }
95 |
96 | private String bitmapToBase64(Bitmap bitmap) {
97 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
98 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
99 | byte[] byteArray = byteArrayOutputStream .toByteArray();
100 | return Base64.encodeToString(byteArray, Base64.DEFAULT);
101 | }
102 |
103 | public interface OnComplete {
104 | void onSuccess();
105 | void onFail(Exception exception);
106 | }
107 |
108 | public interface OnCompleteBase64 {
109 | void onSuccess(String base64);
110 | void onFail(Exception exception);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/android/src/main/java/com/rumax/reactnative/saveview/SaveViewModule.java:
--------------------------------------------------------------------------------
1 | package com.rumax.reactnative.saveview;
2 |
3 | import com.facebook.react.bridge.Promise;
4 | import com.facebook.react.bridge.ReactApplicationContext;
5 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
6 | import com.facebook.react.bridge.ReactMethod;
7 |
8 | import java.io.File;
9 |
10 | public class SaveViewModule extends ReactContextBaseJavaModule {
11 | private static final String NAME = "RNSaveView";
12 | private ReactApplicationContext context;
13 |
14 | SaveViewModule(ReactApplicationContext reactContext) {
15 | super(reactContext);
16 | context = reactContext;
17 | }
18 |
19 | @Override
20 | public String getName() {
21 | return NAME;
22 | }
23 |
24 | @ReactMethod
25 | public void saveToPNG(final int reactTag, final String filePath, final Promise promise) {
26 | SaveView saveView = new SaveView(context);
27 |
28 | File file = new File(filePath);
29 | if (file.exists()) {
30 | file.delete();
31 | file = new File(filePath);
32 | }
33 |
34 | saveView.saveToPNG(reactTag, file, new SaveView.OnComplete() {
35 | @Override
36 | public void onSuccess() {
37 | promise.resolve(true);
38 | }
39 |
40 | @Override
41 | public void onFail(Exception exception) {
42 | promise.reject(exception);
43 | }
44 | });
45 | }
46 |
47 | @ReactMethod
48 | public void saveToPNGBase64(final int reactTag, final Promise promise) {
49 | SaveView saveView = new SaveView(context);
50 |
51 | saveView.saveToPNGBase64(reactTag, new SaveView.OnCompleteBase64() {
52 | @Override
53 | public void onSuccess(String base64) {
54 | promise.resolve(base64);
55 | }
56 |
57 | @Override
58 | public void onFail(Exception exception) {
59 | promise.reject(exception);
60 | }
61 | });
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/android/src/main/java/com/rumax/reactnative/saveview/SaveViewPackage.java:
--------------------------------------------------------------------------------
1 | package com.rumax.reactnative.saveview;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.NativeModule;
5 | import com.facebook.react.bridge.ReactApplicationContext;
6 | import com.facebook.react.uimanager.ViewManager;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | public class SaveViewPackage implements ReactPackage {
13 | @Override
14 | public List createNativeModules(ReactApplicationContext reactContext) {
15 | List modules = new ArrayList<>();
16 | modules.add(new SaveViewModule(reactContext));
17 | return modules;
18 | }
19 |
20 | @Override
21 | @SuppressWarnings("rawtypes")
22 | public List createViewManagers(ReactApplicationContext reactContext) {
23 | return Collections.emptyList();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ios/RNSaveView/RNSaveView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 538815A22200AA4C00F84E15 /* RNSaveViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5388159E2200AA4C00F84E15 /* RNSaveViewManager.m */; };
11 | 538815A32200AA4C00F84E15 /* RNSaveViewConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5388159F2200AA4C00F84E15 /* RNSaveViewConstants.m */; };
12 | /* End PBXBuildFile section */
13 |
14 | /* Begin PBXCopyFilesBuildPhase section */
15 | 538815902200AA1900F84E15 /* CopyFiles */ = {
16 | isa = PBXCopyFilesBuildPhase;
17 | buildActionMask = 2147483647;
18 | dstPath = "include/$(PRODUCT_NAME)";
19 | dstSubfolderSpec = 16;
20 | files = (
21 | );
22 | runOnlyForDeploymentPostprocessing = 0;
23 | };
24 | /* End PBXCopyFilesBuildPhase section */
25 |
26 | /* Begin PBXFileReference section */
27 | 538815922200AA1900F84E15 /* libRNSaveView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNSaveView.a; sourceTree = BUILT_PRODUCTS_DIR; };
28 | 5388159E2200AA4C00F84E15 /* RNSaveViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSaveViewManager.m; sourceTree = ""; };
29 | 5388159F2200AA4C00F84E15 /* RNSaveViewConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSaveViewConstants.m; sourceTree = ""; };
30 | 538815A02200AA4C00F84E15 /* RNSaveViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSaveViewManager.h; sourceTree = ""; };
31 | 538815A12200AA4C00F84E15 /* RNSaveViewConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSaveViewConstants.h; sourceTree = ""; };
32 | /* End PBXFileReference section */
33 |
34 | /* Begin PBXFrameworksBuildPhase section */
35 | 5388158F2200AA1900F84E15 /* Frameworks */ = {
36 | isa = PBXFrameworksBuildPhase;
37 | buildActionMask = 2147483647;
38 | files = (
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 538815892200AA1900F84E15 = {
46 | isa = PBXGroup;
47 | children = (
48 | 538815942200AA1900F84E15 /* RNSaveView */,
49 | 538815932200AA1900F84E15 /* Products */,
50 | );
51 | sourceTree = "";
52 | };
53 | 538815932200AA1900F84E15 /* Products */ = {
54 | isa = PBXGroup;
55 | children = (
56 | 538815922200AA1900F84E15 /* libRNSaveView.a */,
57 | );
58 | name = Products;
59 | sourceTree = "";
60 | };
61 | 538815942200AA1900F84E15 /* RNSaveView */ = {
62 | isa = PBXGroup;
63 | children = (
64 | 538815A12200AA4C00F84E15 /* RNSaveViewConstants.h */,
65 | 5388159F2200AA4C00F84E15 /* RNSaveViewConstants.m */,
66 | 538815A02200AA4C00F84E15 /* RNSaveViewManager.h */,
67 | 5388159E2200AA4C00F84E15 /* RNSaveViewManager.m */,
68 | );
69 | path = RNSaveView;
70 | sourceTree = "";
71 | };
72 | /* End PBXGroup section */
73 |
74 | /* Begin PBXNativeTarget section */
75 | 538815912200AA1900F84E15 /* RNSaveView */ = {
76 | isa = PBXNativeTarget;
77 | buildConfigurationList = 5388159B2200AA1900F84E15 /* Build configuration list for PBXNativeTarget "RNSaveView" */;
78 | buildPhases = (
79 | 5388158E2200AA1900F84E15 /* Sources */,
80 | 5388158F2200AA1900F84E15 /* Frameworks */,
81 | 538815902200AA1900F84E15 /* CopyFiles */,
82 | );
83 | buildRules = (
84 | );
85 | dependencies = (
86 | );
87 | name = RNSaveView;
88 | productName = RNSaveView;
89 | productReference = 538815922200AA1900F84E15 /* libRNSaveView.a */;
90 | productType = "com.apple.product-type.library.static";
91 | };
92 | /* End PBXNativeTarget section */
93 |
94 | /* Begin PBXProject section */
95 | 5388158A2200AA1900F84E15 /* Project object */ = {
96 | isa = PBXProject;
97 | attributes = {
98 | LastUpgradeCheck = 1010;
99 | ORGANIZATIONNAME = "sander frenken";
100 | TargetAttributes = {
101 | 538815912200AA1900F84E15 = {
102 | CreatedOnToolsVersion = 10.1;
103 | };
104 | };
105 | };
106 | buildConfigurationList = 5388158D2200AA1900F84E15 /* Build configuration list for PBXProject "RNSaveView" */;
107 | compatibilityVersion = "Xcode 9.3";
108 | developmentRegion = en;
109 | hasScannedForEncodings = 0;
110 | knownRegions = (
111 | en,
112 | );
113 | mainGroup = 538815892200AA1900F84E15;
114 | productRefGroup = 538815932200AA1900F84E15 /* Products */;
115 | projectDirPath = "";
116 | projectRoot = "";
117 | targets = (
118 | 538815912200AA1900F84E15 /* RNSaveView */,
119 | );
120 | };
121 | /* End PBXProject section */
122 |
123 | /* Begin PBXSourcesBuildPhase section */
124 | 5388158E2200AA1900F84E15 /* Sources */ = {
125 | isa = PBXSourcesBuildPhase;
126 | buildActionMask = 2147483647;
127 | files = (
128 | 538815A22200AA4C00F84E15 /* RNSaveViewManager.m in Sources */,
129 | 538815A32200AA4C00F84E15 /* RNSaveViewConstants.m in Sources */,
130 | );
131 | runOnlyForDeploymentPostprocessing = 0;
132 | };
133 | /* End PBXSourcesBuildPhase section */
134 |
135 | /* Begin XCBuildConfiguration section */
136 | 538815992200AA1900F84E15 /* Debug */ = {
137 | isa = XCBuildConfiguration;
138 | buildSettings = {
139 | ALWAYS_SEARCH_USER_PATHS = NO;
140 | CLANG_ANALYZER_NONNULL = YES;
141 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
142 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
143 | CLANG_CXX_LIBRARY = "libc++";
144 | CLANG_ENABLE_MODULES = YES;
145 | CLANG_ENABLE_OBJC_ARC = YES;
146 | CLANG_ENABLE_OBJC_WEAK = YES;
147 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
148 | CLANG_WARN_BOOL_CONVERSION = YES;
149 | CLANG_WARN_COMMA = YES;
150 | CLANG_WARN_CONSTANT_CONVERSION = YES;
151 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
152 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
153 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
154 | CLANG_WARN_EMPTY_BODY = YES;
155 | CLANG_WARN_ENUM_CONVERSION = YES;
156 | CLANG_WARN_INFINITE_RECURSION = YES;
157 | CLANG_WARN_INT_CONVERSION = YES;
158 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
159 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
160 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
162 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
163 | CLANG_WARN_STRICT_PROTOTYPES = YES;
164 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
165 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
166 | CLANG_WARN_UNREACHABLE_CODE = YES;
167 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
168 | CODE_SIGN_IDENTITY = "iPhone Developer";
169 | COPY_PHASE_STRIP = NO;
170 | DEBUG_INFORMATION_FORMAT = dwarf;
171 | ENABLE_STRICT_OBJC_MSGSEND = YES;
172 | ENABLE_TESTABILITY = YES;
173 | GCC_C_LANGUAGE_STANDARD = gnu11;
174 | GCC_DYNAMIC_NO_PIC = NO;
175 | GCC_NO_COMMON_BLOCKS = YES;
176 | GCC_OPTIMIZATION_LEVEL = 0;
177 | GCC_PREPROCESSOR_DEFINITIONS = (
178 | "DEBUG=1",
179 | "$(inherited)",
180 | );
181 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
182 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
183 | GCC_WARN_UNDECLARED_SELECTOR = YES;
184 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
185 | GCC_WARN_UNUSED_FUNCTION = YES;
186 | GCC_WARN_UNUSED_VARIABLE = YES;
187 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
188 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
189 | MTL_FAST_MATH = YES;
190 | ONLY_ACTIVE_ARCH = YES;
191 | SDKROOT = iphoneos;
192 | };
193 | name = Debug;
194 | };
195 | 5388159A2200AA1900F84E15 /* Release */ = {
196 | isa = XCBuildConfiguration;
197 | buildSettings = {
198 | ALWAYS_SEARCH_USER_PATHS = NO;
199 | CLANG_ANALYZER_NONNULL = YES;
200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
202 | CLANG_CXX_LIBRARY = "libc++";
203 | CLANG_ENABLE_MODULES = YES;
204 | CLANG_ENABLE_OBJC_ARC = YES;
205 | CLANG_ENABLE_OBJC_WEAK = YES;
206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
207 | CLANG_WARN_BOOL_CONVERSION = YES;
208 | CLANG_WARN_COMMA = YES;
209 | CLANG_WARN_CONSTANT_CONVERSION = YES;
210 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
212 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
213 | CLANG_WARN_EMPTY_BODY = YES;
214 | CLANG_WARN_ENUM_CONVERSION = YES;
215 | CLANG_WARN_INFINITE_RECURSION = YES;
216 | CLANG_WARN_INT_CONVERSION = YES;
217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
218 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
222 | CLANG_WARN_STRICT_PROTOTYPES = YES;
223 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
225 | CLANG_WARN_UNREACHABLE_CODE = YES;
226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
227 | CODE_SIGN_IDENTITY = "iPhone Developer";
228 | COPY_PHASE_STRIP = NO;
229 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
230 | ENABLE_NS_ASSERTIONS = NO;
231 | ENABLE_STRICT_OBJC_MSGSEND = YES;
232 | GCC_C_LANGUAGE_STANDARD = gnu11;
233 | GCC_NO_COMMON_BLOCKS = YES;
234 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
235 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
236 | GCC_WARN_UNDECLARED_SELECTOR = YES;
237 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
238 | GCC_WARN_UNUSED_FUNCTION = YES;
239 | GCC_WARN_UNUSED_VARIABLE = YES;
240 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
241 | MTL_ENABLE_DEBUG_INFO = NO;
242 | MTL_FAST_MATH = YES;
243 | SDKROOT = iphoneos;
244 | VALIDATE_PRODUCT = YES;
245 | };
246 | name = Release;
247 | };
248 | 5388159C2200AA1900F84E15 /* Debug */ = {
249 | isa = XCBuildConfiguration;
250 | buildSettings = {
251 | CODE_SIGN_STYLE = Automatic;
252 | OTHER_LDFLAGS = "-ObjC";
253 | PRODUCT_NAME = "$(TARGET_NAME)";
254 | SKIP_INSTALL = YES;
255 | TARGETED_DEVICE_FAMILY = "1,2";
256 | };
257 | name = Debug;
258 | };
259 | 5388159D2200AA1900F84E15 /* Release */ = {
260 | isa = XCBuildConfiguration;
261 | buildSettings = {
262 | CODE_SIGN_STYLE = Automatic;
263 | OTHER_LDFLAGS = "-ObjC";
264 | PRODUCT_NAME = "$(TARGET_NAME)";
265 | SKIP_INSTALL = YES;
266 | TARGETED_DEVICE_FAMILY = "1,2";
267 | };
268 | name = Release;
269 | };
270 | /* End XCBuildConfiguration section */
271 |
272 | /* Begin XCConfigurationList section */
273 | 5388158D2200AA1900F84E15 /* Build configuration list for PBXProject "RNSaveView" */ = {
274 | isa = XCConfigurationList;
275 | buildConfigurations = (
276 | 538815992200AA1900F84E15 /* Debug */,
277 | 5388159A2200AA1900F84E15 /* Release */,
278 | );
279 | defaultConfigurationIsVisible = 0;
280 | defaultConfigurationName = Release;
281 | };
282 | 5388159B2200AA1900F84E15 /* Build configuration list for PBXNativeTarget "RNSaveView" */ = {
283 | isa = XCConfigurationList;
284 | buildConfigurations = (
285 | 5388159C2200AA1900F84E15 /* Debug */,
286 | 5388159D2200AA1900F84E15 /* Release */,
287 | );
288 | defaultConfigurationIsVisible = 0;
289 | defaultConfigurationName = Release;
290 | };
291 | /* End XCConfigurationList section */
292 | };
293 | rootObject = 5388158A2200AA1900F84E15 /* Project object */;
294 | }
295 |
--------------------------------------------------------------------------------
/ios/RNSaveView/RNSaveView/RNSaveViewConstants.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | extern NSString * const RNSV_ERROR_INVALID_REACT_TAG;
4 | extern NSString * const RNSV_ERROR_INVALID_BASE_64;
5 | extern NSString * const RNSV_ERROR_MESSAGE_INVALID_BASE_64;
6 |
--------------------------------------------------------------------------------
/ios/RNSaveView/RNSaveView/RNSaveViewConstants.m:
--------------------------------------------------------------------------------
1 | #import "RNSaveViewConstants.h"
2 |
3 | NSString *const RNSV_ERROR_INVALID_REACT_TAG = @"Invalid react tag";
4 | NSString *const RNSV_ERROR_INVALID_BASE_64 = @"Invalid base64";
5 | NSString *const RNSV_ERROR_MESSAGE_INVALID_BASE_64 = @"Image could not be converted to base64 representation";
6 |
--------------------------------------------------------------------------------
/ios/RNSaveView/RNSaveView/RNSaveViewManager.h:
--------------------------------------------------------------------------------
1 | #import "RNSaveViewConstants.h"
2 | #import
3 | #import
4 | #import
5 |
6 | @interface RNSaveViewManager : RCTViewManager
7 |
8 | @end
9 |
--------------------------------------------------------------------------------
/ios/RNSaveView/RNSaveView/RNSaveViewManager.m:
--------------------------------------------------------------------------------
1 | #import "RNSaveViewManager.h"
2 |
3 | @implementation RNSaveViewManager
4 |
5 | RCT_EXPORT_MODULE(RNSaveView)
6 |
7 | RCT_EXPORT_METHOD(saveToPNGBase64: (nonnull NSNumber *)reactTag resolver: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject) {
8 | dispatch_async(dispatch_get_main_queue(), ^{
9 | UIImage *snapshot = [self snapshotReactView: reactTag];
10 | if (snapshot != nil) {
11 | NSString* base64Image = [self imageToBase64: snapshot];
12 | if (base64Image != nil) {
13 | resolve(base64Image);
14 | } else {
15 | reject(RNSV_ERROR_INVALID_BASE_64, RNSV_ERROR_MESSAGE_INVALID_BASE_64, nil);
16 | }
17 | } else {
18 | reject(RNSV_ERROR_INVALID_REACT_TAG, [NSString stringWithFormat: @"ReactTag passed: %@", reactTag], nil);
19 | }
20 | });
21 | }
22 |
23 | RCT_EXPORT_METHOD(saveToPNG: (nonnull NSNumber *)reactTag filePath: (nonnull NSString *)filePath resolver: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject) {
24 | dispatch_async(dispatch_get_main_queue(), ^{
25 | UIImage *snapshot = [self snapshotReactView: reactTag];
26 | if (snapshot != nil) {
27 | [self saveImage: snapshot atFilepath: filePath];
28 | resolve(nil);
29 | } else {
30 | reject(RNSV_ERROR_INVALID_REACT_TAG, [NSString stringWithFormat: @"ReactTag passed: %@", reactTag], nil);
31 | }
32 | });
33 | }
34 |
35 | - (instancetype)init {
36 | self = [super init];
37 | return self;
38 | }
39 |
40 | + (BOOL)requiresMainQueueSetup {
41 | return YES;
42 | }
43 |
44 | - (UIImage *)snapshotReactView: (nonnull NSNumber *)reactTag {
45 | UIView *view = (UIView *)[self.bridge.uiManager viewForReactTag: reactTag];
46 |
47 | UIImage *snapshot;
48 | if ([view isKindOfClass: [RCTScrollView class]]) {
49 | RCTScrollView* rctScrollView = (RCTScrollView *)view;
50 | snapshot = [self snapshotScrollView: rctScrollView.scrollView];
51 | } else {
52 | snapshot = [self snapshotView: view withSize: view.frame.size];
53 | }
54 |
55 | return snapshot;
56 | }
57 |
58 | - (UIImage *)snapshotScrollView: (UIScrollView *)scrollView {
59 | // Store the original frame of the scrollview to restore later
60 | CGRect originalFrame = scrollView.frame;
61 |
62 | // Set the frame to it's contentSize
63 | scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
64 |
65 | UIImage *image = [self snapshotView: scrollView withSize: scrollView.contentSize];
66 |
67 | // Return to the original frame of the scrollview
68 | scrollView.frame = originalFrame;
69 |
70 | return image;
71 | }
72 |
73 | - (UIImage *)snapshotView: (UIView *)view withSize: (CGSize)size {
74 | UIGraphicsBeginImageContextWithOptions(size, NO, 0);
75 | [view drawViewHierarchyInRect: CGRectMake(0, 0, size.width, size.height) afterScreenUpdates: YES];
76 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
77 | UIGraphicsEndImageContext();
78 |
79 | return image;
80 | }
81 |
82 | - (void) saveImage: (UIImage *)image atFilepath: (NSString *)filePath {
83 | NSFileManager *fileManager = [NSFileManager defaultManager];
84 | NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
85 | [fileManager createFileAtPath: filePath contents: imageData attributes: nil];
86 | }
87 |
88 | - (NSString *)imageToBase64: (UIImage *)image {
89 | NSData *imageData = UIImagePNGRepresentation(image);
90 | return [imageData base64EncodedStringWithOptions: 0];
91 | }
92 |
93 | @end
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-save-view",
3 | "version": "0.2.3",
4 | "description": "React native save View implementation",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "react-native react-component view android ios screenshot viewshot"
11 | ],
12 | "author": "Maksym Rusynyk ",
13 | "license": "MIT",
14 | "peerDependencies": {
15 | "react-native": "0.*"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+ssh://git@github.com/rumax/react-native-SaveView.git"
20 | },
21 | "bugs": {
22 | "url": "https://github.com/rumax/react-native-SaveView/issues"
23 | },
24 | "homepage": "https://github.com/rumax/react-native-SaveView#readme"
25 | }
26 |
--------------------------------------------------------------------------------
/react-native-save-view.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = package['name']
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.license = package['license']
10 |
11 | s.authors = package['author']
12 | s.homepage = package['homepage']
13 | s.platform = :ios, "9.0"
14 |
15 | s.source = { :git => "https://github.com/rumax/react-native-SaveView.git", :tag => "v#{s.version}" }
16 | s.source_files = "ios/**/*.{h,m}"
17 |
18 | s.dependency 'React'
19 | end
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { NativeModules, findNodeHandle } from 'react-native';
3 |
4 | const { RNSaveView } = NativeModules;
5 |
6 | const getReactTag = (view: ?React$ElementRef<*>) => {
7 | if (!view) {
8 | throw new Error('View has to be defined');
9 | }
10 |
11 | const reactTag = findNodeHandle(view);
12 |
13 | if (!reactTag) {
14 | throw new Error('Invalid view provided, cannot get reactTag');
15 | }
16 |
17 | return reactTag;
18 | }
19 |
20 | export default {
21 | saveToPNG: async (view: ?React$ElementRef<*>, path: string) => {
22 | await RNSaveView.saveToPNG(getReactTag(view), path);
23 | },
24 | saveToPNGBase64: async (view: ?React$ElementRef<*>) => {
25 | const base64 = await RNSaveView.saveToPNGBase64(getReactTag(view));
26 | return base64;
27 | },
28 | };
29 |
--------------------------------------------------------------------------------