jibon57 / nativescript-mediafilepicker

A complete file picker solution for NativeScript
Apache License 2.0
51 stars 39 forks source link

When run though TestFlight app closes the moment the plugin page is clicked #63

Closed YaakovDavid closed 5 years ago

YaakovDavid commented 5 years ago

Love the plugin! It works great on Android as well as on the ios simulator or on a ios device when I run it directly from Xcode but when I run it from TestFlight on devices the app closes as soon as I go to the page with this plugin.

when I run it on the ios simulator I noticed I get the below printed to the terminal

(UIKitCore) [com.apple.UIKit:Animation] +[UIView setAnimationsEnabled:] being called from a background thread. Performing any operation from a background thread on UIView or a subclass is not supported and may result in unexpected and insidious behavior. trace=(
0   UIKitCore                           0x000000010ffc9146 kFixedAnimationDuration_block_invoke_5 + 125
1   libdispatch.dylib                   0x000000010eba154b _dispatch_client_callout + 8
2   libdispatch.dylib                   0x000000010eba2d52 _dispatch_once_callout + 20
3   UIKitCore                           0x000000010ffc90c7 +[UIView(Animation) setAnimationsEnabled:] + 76
4   UIKitCore                           0x000000010ffc922f +[UIView(Animation) performWithoutAnimation:] + 84
5   UIKitCore                           0x000000010f221b83 -[UIVisualEffectView _updateSubviews] + 325
6   UIKitCore                           0x000000010f2229e8 -[UIVisualEffectView _configureAllEffects] + 1436
7   UIKitCore                           0x000000010f220e22 -[UIVisualEffectView setBackgroundEffects:] + 396
8   UIKitCore                           0x000000010f22b1b8 -[_UIBarBackground updateBackground] + 229
9   UIKitCore                           0x000000010f22b467 -[_UIBarBackground transitionBackgroundViews] + 208
10  UIKitCore                           0x000000010f2b570c -[_UINavigationBarVisualProviderModernIOS _updateBackgrounds] + 851
11  UIKitCore                           0x000000010f2b594e -[_UINavigationBarVisualProviderModernIOS layoutSubviews] + 272
12  UIKitCore                           0x000000010f26a691 -[UINavigationBar layoutSubviews] + 256
13  UIKitCore                           0x000000010ffd7795 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1441
14  UIKitCore                           0x000000010f26a3e8 -[UINavigationBar layoutSublayersOfLayer:] + 248
15  QuartzCore                          0x000000011155fb19 -[CALayer layoutSublayers] + 175
16  QuartzCore                          0x00000001115649d3 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 395
17  UIKitCore                           0x000000010ffc2077 -[UIView(Hierarchy) layoutBelowIfNeeded] + 1429
18  UIKitCore                           0x000000010f41bd87 -[UINavigationController _positionNavigationBarHidden:edge:initialOffset:] + 800
19  UIKitCore                           0x000000010f41c024 -[UINavigationController _positionNavigationBarHidden:edge:] + 388
20  UIKitCore                           0x000000010f41b6d7 -[UINavigationController setNavigationBar:] + 1430
21  UIKitCore                           0x000000010f41b010 -[UINavigationController _navigationBarHiddenByDefault:] + 145
22  UIKitCore                           0x000000010f4234f1 -[UINavigationController loadView] + 176
23  UIKitCore                           0x000000010f4ce0ee -[UIViewController loadViewIfRequired] + 175
24  UIKitCore                           0x000000010f4ce940 -[UIViewController view] + 27
25  UIKitCore                           0x000000010f3f14ee -[_UIFullscreenPresentationController _setPresentedViewController:] + 89
26  UIKitCore                           0x000000010f3e4edf -[UIPresentationController initWithPresentedViewController:presentingViewController:] + 133
27  UIKitCore                           0x000000010f4e19ad -[UIViewController _presentViewController:withAnimationController:completion:] + 3713
28  UIKitCore                           0x000000010f4e474b __63-[UIViewController _presentViewController:animated:completion:]_block_invoke + 99
29  UIKitCore                           0x000000010f4e4dd9 -[UIViewController _performCoordinatedPresentOrDismiss:animated:] + 511
30  UIKitCore                           0x000000010f4e46b1 -[UIViewController _presentViewController:animated:completion:] + 173
31  UIKitCore                           0x000000010f4e49f0 -[UIViewController presentViewController:animated:completion:] + 150
32  NativeScript                        0x000000010b04c56d ffi_call_unix64 + 85
33  ???                                 0x000000013e79f150 0x0 + 5343146320
)
CONSOLE LOG file:///app/app.js:57:20: Resume UIApplication: <UIApplication: 0x7fb06a4917f0>

which makes me believe that the issue is that it's not running on the main thread. I found this StackOverflow post which I think is related to this https://stackoverflow.com/questions/52448204/uiimageview-setimage-crashes-on-background-thread-since-xcode-10

Would someone be able to take a look at the Swift code in this plugin and see if this works? I would do it but I'm not familiar with Swift at all

jibon57 commented 5 years ago

You haven't provided code that you are using. It doesn't seem to related with this plugin.

YaakovDavid commented 5 years ago

My viewModel looks like this

import { AudioPickerOptions,
        FilePickerOptions,
        ImagePickerOptions,
        Mediafilepicker,
        VideoPickerOptions
    } from "nativescript-mediafilepicker";
import * as app from "tns-core-modules/application";
import { Observable } from "tns-core-modules/data/observable";
import { SelectedPageService } from "../shared/selected-page-service";

declare const AVCaptureSessionPreset1920x1080;
declare const AVCaptureSessionPresetHigh;
declare const AVCaptureSessionPresetLow;
declare const kUTTypePDF;
declare const kUTTypeText;

export class MediaPickerViewModel extends Observable {

    constructor() {
        super();
        SelectedPageService.getInstance().updateSelectedPage("MediaPicker");
    }

    /**
     * openImagePicker
     */
    openImagePicker() {
        const options: ImagePickerOptions = {
            android: {
                isCaptureMood: false,
                isNeedCamera: true,
                maxNumberFiles: 10,
                isNeedFolderList: true
            },
            ios: {
                isCaptureMood: false,
                maxNumberFiles: 10
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openImagePicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.dir(results);
            if (results) {
                results.forEach((result) => {
                    const file = result.file;
                    console.log("image file: " + file);
                    if (result.file && app.ios && !options.ios.isCaptureMood) {
                        // We can copy the image to app directory for futher proccess.
                        // This will create a new directory name "filepicker".
                        // So, after your work you can delete it for reducing memory use.
                        const fileName = file.replace(/^.*[\/]/, "");
                        mediafilepicker.copyPHImageToAppDirectory(result.rawData, fileName).then((dirRes: any) => {
                            console.dir(dirRes);
                        }).catch((e) => {
                            console.dir(e);
                        });

                        // or can get UIImage to display
                        mediafilepicker.convertPHImageToUIImage(result.rawData).then((uiRes) => {
                            console.log(uiRes);
                        });
                    } else if (result.file && app.ios) {
                        // So we have taken image & will get UIImage
                        // We can copy it to app directory, if need
                        const fileName = "myTmpImage.jpg";
                        mediafilepicker.copyUIImageToAppDirectory(result.rawData, fileName).then((dirRes: any) => {
                            console.dir(dirRes);
                        }).catch((e) => {
                            console.dir(e);
                        });
                    }
                });
            }
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }

    /**
     * openVideoPicker
     */
    openVideoPicker() {
        let allowedVideoQualities = [];
        if (app.ios) {
            allowedVideoQualities = [AVCaptureSessionPreset1920x1080, AVCaptureSessionPresetHigh];
        //     get more from here:
        //     https://developer.apple.com/documentation/avfoundation/avcapturesessionpreset?language=objc
        }
        const options: VideoPickerOptions = {
            android: {
                isCaptureMood: false,
                isNeedCamera: true,
                maxNumberFiles: 2,
                isNeedFolderList: true,
                maxDuration: 20
            },
            ios: {
                isCaptureMood: false
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openVideoPicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.dir(results);
            if (results) {
                results.forEach((result) => {
                    console.dir(result);
                    const file = result.file;
                    console.log("video file: " + file);
                    if (result.file && app.ios && !options.ios.isCaptureMood) {
                        const fileName = file.replace(/^.*[\/]/, "");
                        setTimeout(() => {
                            mediafilepicker.copyPHVideoToAppDirectory(result.urlAsset, fileName).then((appRes) => {
                                console.dir(appRes);
                            }).catch((e) => {
                                console.dir(e);
                            });
                        }, 1000);
                    } else if (result.file && app.ios) {
                        // or we will get our own recorded video :)
                        console.log(file);
                    }
                });
            }
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }

    /**
     * audio
     */
    openAudioPicker() {
        const options: AudioPickerOptions = {
            android: {
                isCaptureMood: false,
                isNeedRecorder: true,
                maxNumberFiles: 2,
                isNeedFolderList: true,
                maxSize: 102400 // Maximum size in bytes
            },
            ios: {
                isCaptureMood: false,
                maxNumberFiles: 5,
                audioMaximumDuration: 10
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openAudioPicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.dir(results);
            if (results) {
                results.forEach((result) => {
                    console.log("audio file: " + result);
                    if (result.file && app.ios && !options.ios.isCaptureMood) {
                        // We can copy the audio to app directory for futher proccess.
                        // This will create a new directory name "filepicker".
                        // So, after your work you can delete it for reducing memory use.
                        const fileName = "tmpFile.m4a"; // use .m4a
                        // copying file will require some time
                        mediafilepicker.copyMPMediaFileToAPPDirectory(result.rawData, fileName).then((appRes) => {
                            console.dir(appRes);
                        }).catch((err) => {
                            console.dir(err);
                        });
                    } else if (result.file && app.ios && options.ios.isCaptureMood) {
                        // So we have recorded file in APP directory
                        console.log(result.file);
                    }
                });
            }
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }

    /**
     * openCustomFiles
     */
    openCustomFilesPicker() {
        let extensionTypes = [];
        extensionTypes = (app.ios)
            ? [kUTTypePDF, kUTTypeText]
                // you can get more types from here:
                // https://developer.apple.com/documentation/mobilecoreservices/uttype
            : ["txt", "pdf"];

        const options: FilePickerOptions = {
            android: {
                extensions: extensionTypes,
                maxNumberFiles: 2
            },
            ios: {
                extensions: extensionTypes,
                multipleSelection: true
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openFilePicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.dir(results);
            if (results) {
                results.forEach((result) => {
                    console.log("custom file: " + result.file);
                });
            }
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }

    /**
     * imageCapture
     */
    imageCapture() {
        const options: ImagePickerOptions = {
            android: {
                isCaptureMood: true
            },
            ios: {
                isCaptureMood: true
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openImagePicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.log("image file:");
            console.dir(results);
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }

    /**
     * videoCapture
     */
    videoCapture() {
        let allowedQualities = [];
        if (app.ios) {
            allowedQualities = [AVCaptureSessionPreset1920x1080, AVCaptureSessionPresetHigh];
            // get more from here:
            // https://developer.apple.com/documentation/avfoundation/avcapturesessionpreset?language=objc
        }

        const options: VideoPickerOptions = {
            android: {
                isCaptureMood: true,
                maxDuration: 20,
                videoQuality: 1
            },
            ios: {
                isCaptureMood: true,
                videoMaximumDuration: 10,
                allowedVideoQualities: allowedQualities
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openVideoPicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.log("video capture:");
            console.dir(results);
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }

    /**
     * audioCapture
     */
    audioCapture() {
        const options: AudioPickerOptions = {
            android: {
                isCaptureMood: true,
                maxSize: 102400 // Maximum size of recorded file in bytes. 5900 = ~ 1 second
            },
            ios: {
                isCaptureMood: true,
                maxNumberFiles: 5,
                audioMaximumDuration: 10
            }
        };

        const mediafilepicker = new Mediafilepicker();
        mediafilepicker.openAudioPicker(options);
        mediafilepicker.on("getFiles", (res) => {
            const results = res.object.get("results");
            console.log("audio capture:");
            console.dir(results);
        });

        mediafilepicker.on("error", (res) => {
            const msg = res.object.get("msg");
            console.log("error: " + msg);
        });

        mediafilepicker.on("cancel", (res) => {
            const msg = res.object.get("msg");
            console.log("cancel: " + msg);
        });
    }
}

My xml

<Page class="page"
    navigatingTo="onNavigatingTo"
    xmlns:ui="nativescript-mediafilepicker"
    xmlns="http://schemas.nativescript.org/tns.xsd">
  <ActionBar class="action-bar">
    <NavigationButton
        android:icon="res://menu"
        android:tap="onDrawerButtonTap"
        ios:tap="onBackButtonTap">
    </NavigationButton>

    <ActionItem
        ios:icon="res://navigation/menu"
        tap="onDrawerButtonTap">
    </ActionItem>
    <Label class="action-bar-title header-text" text="Media Picker"></Label>
  </ActionBar>

    <GridLayout id="account-top-gridlayout" rows="auto, *">
      <ScrollView row="1" id="message-center-scrollview">
        <StackLayout class="message-center-menu-options body-text">

          <Label style="text-align:center; margin-bottom: 10; font-size: 24;" text="Picker"></Label>
          <Button height="44" tap="{{openImagePicker}}" text="Open ImagePicker"></Button>
          <Button height="44" tap="{{openVideoPicker}}" text="Open VideoPicker"></Button>
          <Button height="44" tap="{{openAudioPicker}}" text="Open AudioPicker"></Button>
          <Button height="44" tap="{{openCustomFilesPicker}}" text="Open CustomFilePicker"></Button>

          <Label style="text-align:center; margin-top: 10; font-size: 24;" text="Capture"></Label>
          <Button height="44" tap="{{imageCapture}}" text="Take Picture"></Button>
          <Button height="44" tap="{{videoCapture}}" text="Record Video"></Button>
          <Button height="44" tap="{{audioCapture}}" text="Record Audio"></Button>
        </StackLayout>
      </ScrollView>
    </GridLayout>
</Page>

And my ts

import { RadSideDrawer } from "nativescript-ui-sidedrawer";
import * as app from "tns-core-modules/application";
import { EventData, NavigatedData, Page, topmost } from "tns-core-modules/ui/frame";
import { MediaPickerViewModel } from "./media-picker-view-model";

// Event handler for Page 'loaded' event attached in main-page.xml
export function onNavigatingTo(args: NavigatedData) {
    if (args.isBackNavigation === false) {
        const page = <Page>args.object;
        page.bindingContext = new MediaPickerViewModel();
    }
}

export function onDrawerButtonTap(args: EventData) {
    const sideDrawer = <RadSideDrawer>app.getRootView();
    sideDrawer.showDrawer();
  }

export function onBackButtonTap() {
        topmost().goBack();
}
jibon57 commented 5 years ago

I can't see any error regarding this plugin. May be someone who have more experience can find the reason. You can try to get support from NativeScript core developer.

YaakovDavid commented 5 years ago

Thanks, you are right it wasn't an issue with the plugin or my code! It just started working for me, for the first time in about three weeks. The only thing I did differently was instead of doing tns run ios and then bring the code into Xcode and make the build, instead I rantns build ios --release and then did the build in Xcode and it works now

Thank you for the plugin, it's really good!

jibon57 commented 5 years ago

Glad to hear that it's working..